Spaces:
Running
Running
NERDDISCO
commited on
feat: added node support (#8)
Browse files* feat: change everything
* feat: update example; added proper docs; aligned both web and node packages
This view is limited to 50 files because it contains too many changes.
See raw diff
- README.md +6 -4
- docs/conventions.md +195 -51
- docs/getting_started_with_so100.md +2 -2
- docs/planning/006_packages_node.md +541 -0
- examples/cyberpunk-standalone/src/components/calibration-view.tsx +3 -3
- examples/cyberpunk-standalone/src/components/docs-section.tsx +2 -2
- examples/cyberpunk-standalone/src/lib/unified-storage.ts +3 -3
- examples/cyberpunk-standalone/src/types/robot.ts +1 -1
- examples/node-quick-start/.gitignore +30 -0
- examples/node-quick-start/README.md +130 -0
- examples/node-quick-start/package.json +36 -0
- examples/node-quick-start/pnpm-lock.yaml +1174 -0
- examples/node-quick-start/src/demo-calibrate.ts +125 -0
- examples/node-quick-start/src/demo-find-port.ts +90 -0
- examples/node-quick-start/src/demo-teleoperate.ts +207 -0
- examples/node-quick-start/src/main.ts +183 -0
- examples/node-quick-start/test-homing-offsets.json +44 -0
- tsconfig.cli.json → examples/node-quick-start/tsconfig.json +13 -6
- examples/node-quick-start/vite.config.ts +39 -0
- package.json +5 -9
- packages/cli/.gitignore +36 -0
- packages/cli/README.md +191 -0
- packages/cli/package.json +52 -0
- packages/cli/pnpm-lock.yaml +1167 -0
- packages/cli/src/cli.ts +441 -0
- packages/cli/src/index.ts +13 -0
- packages/cli/tsconfig.json +20 -0
- packages/cli/vite.config.ts +34 -0
- packages/node/.eslintrc.json +23 -0
- packages/node/.gitignore +36 -0
- packages/node/README.md +408 -0
- packages/node/package.json +76 -0
- packages/node/pnpm-lock.yaml +2396 -0
- packages/node/src/calibrate.ts +288 -0
- packages/node/src/find_port.ts +299 -0
- packages/node/src/index.ts +64 -0
- packages/node/src/release_motors.ts +70 -0
- packages/node/src/robots/so100_config.ts +98 -0
- packages/node/src/teleoperate.ts +223 -0
- packages/node/src/teleoperators/base-teleoperator.ts +62 -0
- packages/node/src/teleoperators/direct-teleoperator.ts +112 -0
- packages/node/src/teleoperators/index.ts +14 -0
- packages/node/src/teleoperators/keyboard-teleoperator.ts +292 -0
- packages/node/src/types/calibration.ts +48 -0
- packages/node/src/types/port-discovery.ts +47 -0
- packages/node/src/types/robot-config.ts +40 -0
- packages/node/src/types/robot-connection.ts +61 -0
- packages/node/src/types/teleoperation.ts +95 -0
- {src/lerobot/node → packages/node/src}/utils/constants.ts +2 -6
- packages/node/src/utils/motor-calibration.ts +188 -0
README.md
CHANGED
|
@@ -5,15 +5,17 @@
|
|
| 5 |
## Install
|
| 6 |
|
| 7 |
```bash
|
| 8 |
-
# Web library
|
| 9 |
npm install @lerobot/web
|
| 10 |
|
| 11 |
-
# Node.js library
|
| 12 |
-
|
| 13 |
```
|
| 14 |
|
| 15 |
## Resources
|
| 16 |
|
| 17 |
- **LeRobot.js**: [Introduction post on Hugging Face](https://huggingface.co/blog/NERDDISCO/lerobotjs)
|
| 18 |
-
- **Documentation**:
|
|
|
|
|
|
|
| 19 |
- **Live Demo**: Try it online at [huggingface.co/spaces/NERDDISCO/LeRobot.js](https://huggingface.co/spaces/NERDDISCO/LeRobot.js)
|
|
|
|
| 5 |
## Install
|
| 6 |
|
| 7 |
```bash
|
| 8 |
+
# Web library
|
| 9 |
npm install @lerobot/web
|
| 10 |
|
| 11 |
+
# Node.js library
|
| 12 |
+
npm install @lerobot/node
|
| 13 |
```
|
| 14 |
|
| 15 |
## Resources
|
| 16 |
|
| 17 |
- **LeRobot.js**: [Introduction post on Hugging Face](https://huggingface.co/blog/NERDDISCO/lerobotjs)
|
| 18 |
+
- **Documentation**:
|
| 19 |
+
- [`@lerobot/web` README](./packages/web/README.md) - Browser (WebSerial + WebUSB)
|
| 20 |
+
- [`@lerobot/node` README](./packages/node/README.md) - Node.js (Serialport)
|
| 21 |
- **Live Demo**: Try it online at [huggingface.co/spaces/NERDDISCO/LeRobot.js](https://huggingface.co/spaces/NERDDISCO/LeRobot.js)
|
docs/conventions.md
CHANGED
|
@@ -11,6 +11,7 @@
|
|
| 11 |
## Core Rules
|
| 12 |
|
| 13 |
- **Never Start/Stop Dev Server**: The development server is already managed by the user - never run commands to start, stop, or restart the server
|
|
|
|
| 14 |
- **Python lerobot Faithfulness**: Maintain exact UX/API compatibility with Python lerobot - commands, terminology, and workflows must match identically
|
| 15 |
- **Serial API Separation**: Always use `serialport` package for Node.js and Web Serial API for browsers - never mix or bridge these incompatible APIs
|
| 16 |
- **Minimal Console Output**: Only show essential information - reduce cognitive load for users
|
|
@@ -74,7 +75,7 @@
|
|
| 74 |
|
| 75 |
- **Identical Commands**: `npx lerobot find-port` matches `python -m lerobot.find_port`
|
| 76 |
- **Same Terminology**: Use "MotorsBus", not "robot arms" - keep Python's exact wording
|
| 77 |
-
- **Matching Output**: Error messages, prompts, and flow identical to Python
|
| 78 |
- **Familiar Workflows**: Python lerobot users should feel immediately at home
|
| 79 |
- **CLI Compatibility**: Direct migration path from Python CLI
|
| 80 |
|
|
@@ -193,7 +194,7 @@ lerobot/
|
|
| 193 |
- **Integration Tests**: Test component interactions
|
| 194 |
- **E2E Tests**: Playwright for full workflow testing
|
| 195 |
- **Hardware Tests**: Mock/stub hardware interfaces for CI
|
| 196 |
-
- **UX Compatibility Tests**: Verify outputs match Python
|
| 197 |
|
| 198 |
## Package Structure
|
| 199 |
|
|
@@ -218,6 +219,69 @@ lerobot/
|
|
| 218 |
- **Hardware**: Platform-specific libraries for device access
|
| 219 |
- **Development**: Vitest, ESLint, Prettier
|
| 220 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
## Platform-Specific Implementation
|
| 222 |
|
| 223 |
### Node.js Implementation (Python-Compatible Foundation)
|
|
@@ -239,6 +303,45 @@ lerobot/
|
|
| 239 |
- **Port Discovery**: Programmatic port enumeration without user dialogs
|
| 240 |
- **Process Management**: Direct process control and system integration
|
| 241 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
### Web Implementation (Modern Robotics Interface)
|
| 243 |
|
| 244 |
**Web provides superior robotics UX by building on Node.js's proven hardware protocols**
|
|
@@ -583,83 +686,124 @@ const STS3215_REGISTERS = {
|
|
| 583 |
|
| 584 |
**This sequence debugging took extensive analysis to solve. Future implementations MUST follow this exact pattern to maintain Python compatibility.**
|
| 585 |
|
| 586 |
-
#### CRITICAL:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 587 |
|
| 588 |
-
**
|
| 589 |
|
| 590 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 591 |
|
| 592 |
-
**
|
| 593 |
|
| 594 |
-
- **✅ PERFECT**: `
|
| 595 |
-
- **❌ WRONG**: `
|
|
|
|
| 596 |
|
| 597 |
-
**
|
| 598 |
|
| 599 |
-
- **✅ PERFECT**: `
|
| 600 |
-
- **❌ WRONG**: `
|
| 601 |
|
| 602 |
-
|
| 603 |
|
| 604 |
-
|
| 605 |
-
- **✅ PERFECT**: Use `0.5` unit threshold to detect meaningful changes
|
| 606 |
-
- **❌ WRONG**: Send ALL motor positions every time (causes serial bus conflicts)
|
| 607 |
|
| 608 |
-
|
| 609 |
|
| 610 |
-
|
| 611 |
-
|
| 612 |
|
| 613 |
-
|
| 614 |
|
| 615 |
-
**
|
| 616 |
|
| 617 |
-
-
|
| 618 |
-
-
|
|
|
|
| 619 |
|
| 620 |
-
|
| 621 |
|
| 622 |
-
|
| 623 |
-
- **❌ WRONG**: Polling-based input with timers (adds delay)
|
| 624 |
|
| 625 |
-
|
|
|
|
| 626 |
|
| 627 |
-
**
|
| 628 |
|
| 629 |
-
- **✅ PERFECT**:
|
| 630 |
-
- **❌ WRONG**:
|
| 631 |
|
| 632 |
-
**
|
| 633 |
|
| 634 |
-
- **✅ PERFECT**:
|
| 635 |
-
- **❌ WRONG**:
|
| 636 |
|
| 637 |
-
##### 🎮
|
| 638 |
|
| 639 |
-
**
|
|
|
|
|
|
|
|
|
|
|
|
|
| 640 |
|
| 641 |
-
|
| 642 |
-
- `1ms` motor communication delay (so100_follower.ts)
|
| 643 |
-
- `0.5` unit change detection threshold
|
| 644 |
-
- `100ms` teleoperation loop delay
|
| 645 |
|
| 646 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 647 |
|
| 648 |
-
|
| 649 |
-
2. **❌ Continuous/Velocity Control**: Adds complexity without benefit for keyboard input
|
| 650 |
-
3. **❌ All-Motor Updates**: Sends unnecessary commands, overwhelms serial bus
|
| 651 |
-
4. **❌ Long Communication Delays**: 5ms+ delays cause stuttering
|
| 652 |
-
5. **❌ Complex Interpolation**: Adds latency for simple step-based control
|
| 653 |
-
6. **❌ No Change Detection**: Spams motors with identical positions
|
| 654 |
|
| 655 |
-
|
| 656 |
|
| 657 |
-
- **
|
| 658 |
-
- **
|
| 659 |
-
- **
|
| 660 |
-
- **
|
|
|
|
| 661 |
|
| 662 |
-
**Golden Rule**:
|
| 663 |
|
| 664 |
## Clean Library Architecture (Critical Lessons)
|
| 665 |
|
|
|
|
| 11 |
## Core Rules
|
| 12 |
|
| 13 |
- **Never Start/Stop Dev Server**: The development server is already managed by the user - never run commands to start, stop, or restart the server
|
| 14 |
+
- **Package Manager**: Always use `pnpm` for package management - never use `npm` or `yarn` in documentation, scripts, or commands
|
| 15 |
- **Python lerobot Faithfulness**: Maintain exact UX/API compatibility with Python lerobot - commands, terminology, and workflows must match identically
|
| 16 |
- **Serial API Separation**: Always use `serialport` package for Node.js and Web Serial API for browsers - never mix or bridge these incompatible APIs
|
| 17 |
- **Minimal Console Output**: Only show essential information - reduce cognitive load for users
|
|
|
|
| 75 |
|
| 76 |
- **Identical Commands**: `npx lerobot find-port` matches `python -m lerobot.find_port`
|
| 77 |
- **Same Terminology**: Use "MotorsBus", not "robot arms" - keep Python's exact wording
|
| 78 |
+
- **Matching Output**: Error messages, prompts, and flow identical to Python lerobot
|
| 79 |
- **Familiar Workflows**: Python lerobot users should feel immediately at home
|
| 80 |
- **CLI Compatibility**: Direct migration path from Python CLI
|
| 81 |
|
|
|
|
| 194 |
- **Integration Tests**: Test component interactions
|
| 195 |
- **E2E Tests**: Playwright for full workflow testing
|
| 196 |
- **Hardware Tests**: Mock/stub hardware interfaces for CI
|
| 197 |
+
- **UX Compatibility Tests**: Verify outputs match Python lerobot
|
| 198 |
|
| 199 |
## Package Structure
|
| 200 |
|
|
|
|
| 219 |
- **Hardware**: Platform-specific libraries for device access
|
| 220 |
- **Development**: Vitest, ESLint, Prettier
|
| 221 |
|
| 222 |
+
### Package Architecture Standards
|
| 223 |
+
|
| 224 |
+
**Multi-Platform Package Structure:**
|
| 225 |
+
|
| 226 |
+
```
|
| 227 |
+
packages/
|
| 228 |
+
├── web/ # Browser-focused package (@lerobot/web)
|
| 229 |
+
│ ├── src/
|
| 230 |
+
│ ├── package.json # Web Serial API, browser dependencies
|
| 231 |
+
│ └── README.md # Browser-specific examples
|
| 232 |
+
├── node/ # Node.js-focused package (@lerobot/node)
|
| 233 |
+
│ ├── src/
|
| 234 |
+
│ ├── package.json # serialport dependency, library only
|
| 235 |
+
│ └── README.md # Node.js library examples
|
| 236 |
+
└── cli/ # CLI package (lerobot)
|
| 237 |
+
├── src/
|
| 238 |
+
├── package.json # CLI binary, depends on @lerobot/node
|
| 239 |
+
└── README.md # Python lerobot compatible commands
|
| 240 |
+
```
|
| 241 |
+
|
| 242 |
+
**API Consistency Rules:**
|
| 243 |
+
|
| 244 |
+
- Identical function signatures across packages where possible
|
| 245 |
+
- Platform-specific adaptations in types and implementations only
|
| 246 |
+
- Shared constants and protocols via dedicated utils
|
| 247 |
+
- Cross-platform compatibility for data formats (calibration files, etc.)
|
| 248 |
+
|
| 249 |
+
**CLI Architecture & Separation of Concerns:**
|
| 250 |
+
|
| 251 |
+
- **Library (`@lerobot/node`)**: Pure programmatic API for Node.js applications
|
| 252 |
+
|
| 253 |
+
- `findPort()` returns robot connections programmatically
|
| 254 |
+
- No interactive prompts, CLI output, or user input handling
|
| 255 |
+
- Matches `@lerobot/web` API design for consistency
|
| 256 |
+
|
| 257 |
+
- **CLI (`lerobot`)**: Python lerobot compatible command-line interface
|
| 258 |
+
|
| 259 |
+
- Separate package in `packages/cli/` with `npx lerobot` binary
|
| 260 |
+
- Uses `@lerobot/node` library internally for all functionality
|
| 261 |
+
- Handles interactive prompts, user input, and CLI-specific UX
|
| 262 |
+
- Interactive by default - no flags required for standard workflows
|
| 263 |
+
- Identical command syntax and behavior to Python lerobot
|
| 264 |
+
|
| 265 |
+
- **Architectural Principle**: Libraries provide capabilities, CLIs provide experience
|
| 266 |
+
- Interactive behavior belongs in CLI commands, not library functions
|
| 267 |
+
- Library users get clean APIs, CLI users get Python-compatible workflows
|
| 268 |
+
|
| 269 |
+
### Local Package References
|
| 270 |
+
|
| 271 |
+
When creating examples that depend on workspace packages, use `file:` references:
|
| 272 |
+
|
| 273 |
+
```json
|
| 274 |
+
{
|
| 275 |
+
"dependencies": {
|
| 276 |
+
"@lerobot/web": "file:../../packages/web",
|
| 277 |
+
"@lerobot/node": "file:../../packages/node",
|
| 278 |
+
"lerobot": "file:../../packages/cli"
|
| 279 |
+
}
|
| 280 |
+
}
|
| 281 |
+
```
|
| 282 |
+
|
| 283 |
+
**Never use `workspace:*` in examples** - this is only for the root workspace `package.json`.
|
| 284 |
+
|
| 285 |
## Platform-Specific Implementation
|
| 286 |
|
| 287 |
### Node.js Implementation (Python-Compatible Foundation)
|
|
|
|
| 303 |
- **Port Discovery**: Programmatic port enumeration without user dialogs
|
| 304 |
- **Process Management**: Direct process control and system integration
|
| 305 |
|
| 306 |
+
#### Node.js Serial Communication (Critical Implementation Details)
|
| 307 |
+
|
| 308 |
+
**Event-Driven Communication (Proven Working Approach):**
|
| 309 |
+
|
| 310 |
+
- **Event-Based Reading**: Use `port.once('data')` with timeout promises
|
| 311 |
+
- **Never Use Wrapper Polling**: Avoid `port.read(timeout)` wrappers - they add latency and unreliability
|
| 312 |
+
- **Direct SerialPort Access**: Expose underlying `SerialPort` instance for event listening
|
| 313 |
+
|
| 314 |
+
**Timing Constants for STS3215 Motors:**
|
| 315 |
+
|
| 316 |
+
```typescript
|
| 317 |
+
const STS3215_PROTOCOL = {
|
| 318 |
+
WRITE_TO_READ_DELAY: 0, // No delay before read - immediate event listening
|
| 319 |
+
RETRY_DELAY: 50, // Base retry delay (multiplied by attempt number)
|
| 320 |
+
INTER_MOTOR_DELAY: 10, // Small delay between motor operations
|
| 321 |
+
MAX_RETRIES: 3,
|
| 322 |
+
};
|
| 323 |
+
```
|
| 324 |
+
|
| 325 |
+
**Progressive Timeout Pattern:**
|
| 326 |
+
|
| 327 |
+
```typescript
|
| 328 |
+
// Timeout increases with retry attempts: 100ms, 200ms, 300ms
|
| 329 |
+
const timeout = 100 * attempts;
|
| 330 |
+
const response = await new Promise((resolve, reject) => {
|
| 331 |
+
const timer = setTimeout(() => reject(new Error("Read timeout")), timeout);
|
| 332 |
+
underlyingPort.once("data", (data) => {
|
| 333 |
+
clearTimeout(timer);
|
| 334 |
+
resolve(new Uint8Array(data));
|
| 335 |
+
});
|
| 336 |
+
});
|
| 337 |
+
```
|
| 338 |
+
|
| 339 |
+
**Connection Architecture:**
|
| 340 |
+
|
| 341 |
+
- **Only `findPort()` Creates Connections**: No other high-level function should create new serial connections
|
| 342 |
+
- **Initialized Port Returns**: `findPort()` must return ready-to-use, initialized ports
|
| 343 |
+
- **Connection Reuse**: All functions (`calibrate`, `teleoperate`, `releaseMotors`) use existing connection from `findPort`
|
| 344 |
+
|
| 345 |
### Web Implementation (Modern Robotics Interface)
|
| 346 |
|
| 347 |
**Web provides superior robotics UX by building on Node.js's proven hardware protocols**
|
|
|
|
| 686 |
|
| 687 |
**This sequence debugging took extensive analysis to solve. Future implementations MUST follow this exact pattern to maintain Python compatibility.**
|
| 688 |
|
| 689 |
+
#### CRITICAL: Node.js Teleoperation Patterns (PROVEN WORKING)
|
| 690 |
+
|
| 691 |
+
**These patterns provide smooth, responsive teleoperation in Node.js. Deviating from this recipe causes delays, stuttering, or poor user experience.**
|
| 692 |
+
|
| 693 |
+
##### 🚀 Node.js Keyboard Control Architecture (FINAL SOLUTION)
|
| 694 |
|
| 695 |
+
**The Challenge: Node.js stdin vs Browser Keyboard Events**
|
| 696 |
|
| 697 |
+
- **Browser**: Has real `keydown` and `keyup` events → perfect control
|
| 698 |
+
- **Node.js**: Only has `keypress` events → must simulate keyup with timeouts
|
| 699 |
+
- **OS Keyboard Repeat**: ~250-500ms delay between first press and repeat stream
|
| 700 |
+
|
| 701 |
+
**✅ PROVEN WORKING SOLUTION:**
|
| 702 |
+
|
| 703 |
+
```typescript
|
| 704 |
+
// Optimal configuration values (DO NOT CHANGE without extensive testing)
|
| 705 |
+
export const KEYBOARD_TELEOPERATOR_DEFAULTS = {
|
| 706 |
+
stepSize: 8, // Match browser demo step size
|
| 707 |
+
updateRate: 120, // High frequency for smooth movement (120 Hz)
|
| 708 |
+
keyTimeout: 150, // Balance single taps vs continuous movement
|
| 709 |
+
} as const;
|
| 710 |
+
```
|
| 711 |
+
|
| 712 |
+
**1. Hybrid Movement Pattern**
|
| 713 |
+
|
| 714 |
+
- **✅ PERFECT**: Immediate movement on first keypress + continuous interval updates
|
| 715 |
+
- **❌ WRONG**: Only immediate movement (no continuous) or only interval movement (has delay)
|
| 716 |
+
|
| 717 |
+
```typescript
|
| 718 |
+
// On keypress: Move immediately + start/refresh continuous movement
|
| 719 |
+
private handleKeyboardInput(key: string): void {
|
| 720 |
+
const keyName = this.mapKeyToName(key);
|
| 721 |
+
if (keyName && this.keyboardControls[keyName]) {
|
| 722 |
+
if (this.keyStates[keyName]) {
|
| 723 |
+
// Key repeat - just refresh timestamp
|
| 724 |
+
this.keyStates[keyName].timestamp = Date.now();
|
| 725 |
+
} else {
|
| 726 |
+
// New keypress - immediate movement + start continuous
|
| 727 |
+
this.updateKeyState(keyName, true);
|
| 728 |
+
this.moveMotorForKey(keyName); // ← IMMEDIATE, no delay
|
| 729 |
+
}
|
| 730 |
+
}
|
| 731 |
+
}
|
| 732 |
+
```
|
| 733 |
|
| 734 |
+
**2. Optimal Key Timeout Balance**
|
| 735 |
|
| 736 |
+
- **✅ PERFECT**: `150ms` - Good single taps, minimal continuous gap
|
| 737 |
+
- **❌ WRONG**: `50ms` (single taps too short) or `600ms` (single taps too long)
|
| 738 |
+
- **Why 150ms**: Bridges most OS keyboard repeat delay without making single taps sluggish
|
| 739 |
|
| 740 |
+
**3. High-Frequency Updates**
|
| 741 |
|
| 742 |
+
- **✅ PERFECT**: `120 Hz` update rate for smooth continuous movement
|
| 743 |
+
- **❌ WRONG**: `60 Hz` (visible stuttering) or `200+ Hz` (unnecessary CPU load)
|
| 744 |
|
| 745 |
+
##### 🎯 Development Workflow Optimization
|
| 746 |
|
| 747 |
+
**Package Development Without Constant Rebuilding:**
|
|
|
|
|
|
|
| 748 |
|
| 749 |
+
**✅ PERFECT Development Setup:**
|
| 750 |
|
| 751 |
+
1. **Terminal 1**: `cd packages/node && pnpm dev` (watch mode)
|
| 752 |
+
2. **Terminal 2**: `cd packages/cli && pnpm dev teleoperate ...` (direct TypeScript execution)
|
| 753 |
|
| 754 |
+
**❌ WRONG**: Constantly running `pnpm build` and clearing `node_modules`
|
| 755 |
|
| 756 |
+
**Why This Works:**
|
| 757 |
|
| 758 |
+
- Node package rebuilds automatically on changes
|
| 759 |
+
- CLI dev mode uses `vite-node` to run TypeScript directly
|
| 760 |
+
- No package caching issues, immediate feedback
|
| 761 |
|
| 762 |
+
##### 🔧 CLI Architecture Lessons
|
| 763 |
|
| 764 |
+
**1. No User-Facing Configuration**
|
|
|
|
| 765 |
|
| 766 |
+
- **✅ PERFECT**: `stepSize` handled internally by teleoperator defaults
|
| 767 |
+
- **❌ WRONG**: Exposing `--step-size` CLI parameter (users don't understand motor units)
|
| 768 |
|
| 769 |
+
**2. Python lerobot Parameter Compatibility**
|
| 770 |
|
| 771 |
+
- **✅ PERFECT**: `--robot.type`, `--robot.port`, `--robot.id`, `--teleop.type`
|
| 772 |
+
- **❌ WRONG**: Different parameter names or structure than Python lerobot
|
| 773 |
|
| 774 |
+
**3. Library vs CLI Separation**
|
| 775 |
|
| 776 |
+
- **✅ PERFECT**: Library provides capabilities, CLI provides Python-compatible UX
|
| 777 |
+
- **❌ WRONG**: Library handling CLI concerns or CLI reimplementing library logic
|
| 778 |
|
| 779 |
+
##### 🎮 Performance Characteristics (When Working Right)
|
| 780 |
|
| 781 |
+
- **First Keypress Response**: Immediate (0ms delay)
|
| 782 |
+
- **Continuous Movement**: Smooth 120 Hz updates
|
| 783 |
+
- **Single Tap Duration**: ~150ms (1-2 motor movements)
|
| 784 |
+
- **Key Repeat Transition**: Seamless (no gap)
|
| 785 |
+
- **User Experience**: "Almost perfect", "way better", "smooth movement"
|
| 786 |
|
| 787 |
+
##### ⚠️ Node.js Teleoperation Anti-Patterns (NEVER DO THESE)
|
|
|
|
|
|
|
|
|
|
| 788 |
|
| 789 |
+
1. **❌ Only Timeout-Based Movement**: Causes initial delay on every keypress
|
| 790 |
+
2. **❌ Only Immediate Movement**: No continuous movement when holding keys
|
| 791 |
+
3. **❌ Long Key Timeouts (>300ms)**: Makes single taps feel sluggish
|
| 792 |
+
4. **❌ Short Key Timeouts (<100ms)**: Breaks continuous movement due to OS repeat delay
|
| 793 |
+
5. **❌ Low Update Rates (<100Hz)**: Visible stuttering during continuous movement
|
| 794 |
+
6. **❌ Exposing stepSize to CLI**: Users can't meaningfully configure motor position units
|
| 795 |
|
| 796 |
+
##### 📊 Debugging Keyboard Issues
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 797 |
|
| 798 |
+
**Symptoms and Solutions:**
|
| 799 |
|
| 800 |
+
- **"Initial delay when holding key"** → OS keyboard repeat delay, increase keyTimeout
|
| 801 |
+
- **"Single taps move too far"** → keyTimeout too long, reduce to 150ms or less
|
| 802 |
+
- **"Stuttering during continuous movement"** → updateRate too low, increase to 120Hz
|
| 803 |
+
- **"No continuous movement"** → keyTimeout too short, increase above OS repeat delay
|
| 804 |
+
- **"Immediate movement missing"** → Must call `moveMotorForKey()` on first keypress
|
| 805 |
|
| 806 |
+
**Golden Rule**: The 150ms keyTimeout + 120Hz updateRate + immediate first movement pattern was achieved through extensive testing. Don't change these values without thorough hardware validation.
|
| 807 |
|
| 808 |
## Clean Library Architecture (Critical Lessons)
|
| 809 |
|
docs/getting_started_with_so100.md
CHANGED
|
@@ -34,13 +34,13 @@ Reconnect the USB cable.
|
|
| 34 |
### check follower arm
|
| 35 |
|
| 36 |
```
|
| 37 |
-
python -m lerobot.
|
| 38 |
```
|
| 39 |
|
| 40 |
### check leader arm
|
| 41 |
|
| 42 |
```
|
| 43 |
-
python -m lerobot.
|
| 44 |
```
|
| 45 |
|
| 46 |
**If you see this - you're lucky! Skip to calibration:**
|
|
|
|
| 34 |
### check follower arm
|
| 35 |
|
| 36 |
```
|
| 37 |
+
python -m lerobot.setup_motors --robot.port=COM4 --robot.type=so100_follower
|
| 38 |
```
|
| 39 |
|
| 40 |
### check leader arm
|
| 41 |
|
| 42 |
```
|
| 43 |
+
python -m lerobot.setup_motors --teleop.port=COM3 --robot.type=so100_leader
|
| 44 |
```
|
| 45 |
|
| 46 |
**If you see this - you're lucky! Skip to calibration:**
|
docs/planning/006_packages_node.md
ADDED
|
@@ -0,0 +1,541 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# User Story 006: Node.js Package Architecture
|
| 2 |
+
|
| 3 |
+
## Story
|
| 4 |
+
|
| 5 |
+
**As a** robotics developer building server-side applications, CLI tools, and desktop robotics software
|
| 6 |
+
**I want** to use lerobot.js functionality directly from Node.js with the same API as the web version
|
| 7 |
+
**So that** I can build Node.js applications, command-line tools, and desktop software without browser constraints while maintaining familiar APIs
|
| 8 |
+
|
| 9 |
+
## Background
|
| 10 |
+
|
| 11 |
+
We have successfully implemented `packages/web` that provides `findPort`, `calibrate`, `releaseMotors`, and `teleoperate` functionality using Web APIs (Web Serial, Web USB). This package is published as `@lerobot/web` and provides a clean, typed API for browser-based robotics applications.
|
| 12 |
+
|
| 13 |
+
We also have existing Node.js code in `src/lerobot/node` that was working but abandoned when we focused on getting the web version right. Now that the web version is stable and proven, we want to create a proper `packages/node` package that:
|
| 14 |
+
|
| 15 |
+
1. **Mirrors the Web API**: Provides the same function signatures and behavior as `@lerobot/web`
|
| 16 |
+
2. **Uses Node.js APIs**: Leverages `serialport` instead of Web Serial for hardware communication
|
| 17 |
+
3. **Python lerobot Faithfulness**: Maintains exact compatibility with Python lerobot CLI commands and behavior
|
| 18 |
+
4. **Server-Side Ready**: Enables robotics applications in Node.js servers, CLI tools, and desktop applications
|
| 19 |
+
5. **Reuses Proven Logic**: Builds on existing `src/lerobot/node` code that was already working
|
| 20 |
+
|
| 21 |
+
This will enable developers to use the same lerobot.js API in both browser and Node.js environments, choosing the appropriate platform based on their application needs.
|
| 22 |
+
|
| 23 |
+
## Acceptance Criteria
|
| 24 |
+
|
| 25 |
+
### Core Functionality
|
| 26 |
+
|
| 27 |
+
- [ ] **Same API Surface**: Mirror `@lerobot/web` API with identical function signatures where possible
|
| 28 |
+
- [ ] **Four Core Functions**: Implement `findPort`, `calibrate`, `releaseMotors`, and `teleoperate`
|
| 29 |
+
- [ ] **SerialPort Integration**: Use `serialport` package instead of Web Serial API
|
| 30 |
+
- [ ] **TypeScript Support**: Full TypeScript coverage with strict type checking
|
| 31 |
+
- [ ] **NPM Package**: Published as `@lerobot/node` with proper package.json
|
| 32 |
+
|
| 33 |
+
### Platform Requirements
|
| 34 |
+
|
| 35 |
+
- [ ] **Node.js 18+**: Support current LTS and newer versions
|
| 36 |
+
- [ ] **Cross-Platform**: Work on Windows, macOS, and Linux
|
| 37 |
+
- [ ] **ES Modules**: Use ES module format for consistency with web package
|
| 38 |
+
- [ ] **CLI Integration**: Enable `npx lerobot` commands using this package
|
| 39 |
+
- [ ] **No Browser Dependencies**: No Web API dependencies or browser-specific code
|
| 40 |
+
|
| 41 |
+
### API Alignment
|
| 42 |
+
|
| 43 |
+
- [ ] **Same Types**: Reuse or mirror types from `@lerobot/web` where appropriate
|
| 44 |
+
- [ ] **Same Exports**: Mirror the export structure of `@lerobot/web/index.ts`
|
| 45 |
+
- [ ] **Same Behavior**: Identical behavior for shared functionality (calibration algorithms, motor control)
|
| 46 |
+
- [ ] **Platform-Specific Adaptations**: Handle Node.js-specific differences (file system, process management)
|
| 47 |
+
|
| 48 |
+
### Code Quality
|
| 49 |
+
|
| 50 |
+
- [ ] **Reuse Existing Code**: Build on proven `src/lerobot/node` implementations
|
| 51 |
+
- [ ] **No Code Duplication**: Share logic with web package where possible (copy for now, per requirements)
|
| 52 |
+
- [ ] **Clean Architecture**: Follow the same patterns as `packages/web`
|
| 53 |
+
- [ ] **Comprehensive Testing**: Unit tests for all core functionality
|
| 54 |
+
|
| 55 |
+
## Expected User Flow
|
| 56 |
+
|
| 57 |
+
### Installation and Usage
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
# Install the Node.js package
|
| 61 |
+
npm install @lerobot/node
|
| 62 |
+
|
| 63 |
+
# Use in Node.js applications
|
| 64 |
+
import { findPort, calibrate, teleoperate } from "@lerobot/node";
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
### Find Port (Node.js)
|
| 68 |
+
|
| 69 |
+
```typescript
|
| 70 |
+
// Node.js - programmatic usage
|
| 71 |
+
import { findPort } from "@lerobot/node";
|
| 72 |
+
|
| 73 |
+
const portProcess = await findPort();
|
| 74 |
+
const availablePorts = await portProcess.getAvailablePorts();
|
| 75 |
+
console.log("Available ports:", availablePorts);
|
| 76 |
+
|
| 77 |
+
// Interactive mode (CLI-like) - matches Python lerobot exactly
|
| 78 |
+
const portProcess = await findPort({
|
| 79 |
+
interactive: true, // shows "disconnect cable" prompts like Python
|
| 80 |
+
});
|
| 81 |
+
const detectedPort = await portProcess.detectPort();
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
### Calibration (Node.js)
|
| 85 |
+
|
| 86 |
+
```typescript
|
| 87 |
+
// Node.js - same API as web
|
| 88 |
+
import { calibrate } from "@lerobot/node";
|
| 89 |
+
|
| 90 |
+
const calibrationProcess = await calibrate({
|
| 91 |
+
robot: {
|
| 92 |
+
type: "so100_follower",
|
| 93 |
+
port: "/dev/ttyUSB0", // or "COM4" on Windows
|
| 94 |
+
robotId: "my_follower_arm",
|
| 95 |
+
},
|
| 96 |
+
onLiveUpdate: (data) => {
|
| 97 |
+
console.log("Live calibration data:", data);
|
| 98 |
+
},
|
| 99 |
+
});
|
| 100 |
+
|
| 101 |
+
const results = await calibrationProcess.result;
|
| 102 |
+
console.log("Calibration completed:", results);
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
### Teleoperation (Node.js)
|
| 106 |
+
|
| 107 |
+
```typescript
|
| 108 |
+
// Node.js - same API as web
|
| 109 |
+
import { teleoperate } from "@lerobot/node";
|
| 110 |
+
|
| 111 |
+
const teleoperationProcess = await teleoperate({
|
| 112 |
+
robot: {
|
| 113 |
+
type: "so100_follower",
|
| 114 |
+
port: "/dev/ttyUSB0",
|
| 115 |
+
robotId: "my_follower_arm",
|
| 116 |
+
},
|
| 117 |
+
teleop: {
|
| 118 |
+
type: "keyboard",
|
| 119 |
+
stepSize: 25,
|
| 120 |
+
},
|
| 121 |
+
calibrationData: loadedCalibrationData,
|
| 122 |
+
onStateUpdate: (state) => {
|
| 123 |
+
console.log("Robot state:", state);
|
| 124 |
+
},
|
| 125 |
+
});
|
| 126 |
+
|
| 127 |
+
teleoperationProcess.start();
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
### Release Motors (Node.js)
|
| 131 |
+
|
| 132 |
+
```typescript
|
| 133 |
+
// Node.js - same API as web
|
| 134 |
+
import { releaseMotors } from "@lerobot/node";
|
| 135 |
+
|
| 136 |
+
await releaseMotors({
|
| 137 |
+
robot: {
|
| 138 |
+
type: "so100_follower",
|
| 139 |
+
port: "/dev/ttyUSB0",
|
| 140 |
+
robotId: "my_follower_arm",
|
| 141 |
+
},
|
| 142 |
+
});
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
### CLI Integration
|
| 146 |
+
|
| 147 |
+
```bash
|
| 148 |
+
# Python lerobot compatibility - same commands work
|
| 149 |
+
npx lerobot find-port
|
| 150 |
+
npx lerobot calibrate --robot.type=so100_follower --robot.port=/dev/ttyUSB0
|
| 151 |
+
npx lerobot teleoperate --robot.type=so100_follower --robot.port=/dev/ttyUSB0
|
| 152 |
+
|
| 153 |
+
# Global installation also works
|
| 154 |
+
npm install -g @lerobot/node
|
| 155 |
+
lerobot find-port
|
| 156 |
+
lerobot calibrate --robot.type=so100_follower --robot.port=/dev/ttyUSB0
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
## Implementation Details
|
| 160 |
+
|
| 161 |
+
### File Structure
|
| 162 |
+
|
| 163 |
+
```
|
| 164 |
+
packages/node/
|
| 165 |
+
├── package.json # NPM package configuration
|
| 166 |
+
├── tsconfig.build.json # TypeScript build configuration
|
| 167 |
+
├── README.md # Package documentation
|
| 168 |
+
├── CHANGELOG.md # Version history
|
| 169 |
+
└── src/
|
| 170 |
+
├── index.ts # Main exports (mirror web package)
|
| 171 |
+
├── find_port.ts # Port discovery using serialport
|
| 172 |
+
├── calibrate.ts # Calibration using Node.js APIs
|
| 173 |
+
├── teleoperate.ts # Teleoperation using Node.js APIs
|
| 174 |
+
├── release_motors.ts # Motor release using Node.js APIs
|
| 175 |
+
├── types/
|
| 176 |
+
│ ├── robot-connection.ts # Robot connection types
|
| 177 |
+
│ ├── port-discovery.ts # Port discovery types
|
| 178 |
+
│ ├── calibration.ts # Calibration types
|
| 179 |
+
│ ├── teleoperation.ts # Teleoperation types
|
| 180 |
+
│ └── robot-config.ts # Robot configuration types
|
| 181 |
+
├── utils/
|
| 182 |
+
│ ├── serial-port-wrapper.ts # SerialPort wrapper
|
| 183 |
+
│ ├── motor-communication.ts # Motor communication utilities
|
| 184 |
+
│ ├── motor-calibration.ts # Calibration utilities
|
| 185 |
+
│ └── sts3215-protocol.ts # Protocol constants
|
| 186 |
+
├── robots/
|
| 187 |
+
│ └── so100_config.ts # SO-100 configuration
|
| 188 |
+
└── teleoperators/
|
| 189 |
+
├── index.ts # Teleoperator exports
|
| 190 |
+
├── base-teleoperator.ts # Base teleoperator class
|
| 191 |
+
└── keyboard-teleoperator.ts # Keyboard teleoperator
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
### Key Dependencies
|
| 195 |
+
|
| 196 |
+
#### Core Dependencies
|
| 197 |
+
|
| 198 |
+
- **serialport**: Node.js serial communication (replaces Web Serial API)
|
| 199 |
+
- **chalk**: Terminal colors and formatting
|
| 200 |
+
- **commander**: CLI argument parsing
|
| 201 |
+
|
| 202 |
+
#### Development Dependencies
|
| 203 |
+
|
| 204 |
+
- **typescript**: TypeScript compiler
|
| 205 |
+
- **@types/node**: Node.js type definitions
|
| 206 |
+
- **vitest**: Testing framework
|
| 207 |
+
|
| 208 |
+
### Migration Strategy
|
| 209 |
+
|
| 210 |
+
#### Phase 1: Package Setup
|
| 211 |
+
|
| 212 |
+
- [ ] Create `packages/node` directory structure
|
| 213 |
+
- [ ] Set up package.json with proper exports
|
| 214 |
+
- [ ] Configure TypeScript build process
|
| 215 |
+
- [ ] Set up testing infrastructure
|
| 216 |
+
|
| 217 |
+
#### Phase 2: Core Function Migration
|
| 218 |
+
|
| 219 |
+
- [ ] Migrate `src/lerobot/node/find_port.ts` to `packages/node/src/find_port.ts`
|
| 220 |
+
- [ ] Migrate `src/lerobot/node/calibrate.ts` to `packages/node/src/calibrate.ts`
|
| 221 |
+
- [ ] Migrate `src/lerobot/node/teleoperate.ts` to `packages/node/src/teleoperate.ts`
|
| 222 |
+
- [ ] Create `release_motors.ts` using existing motor communication code
|
| 223 |
+
|
| 224 |
+
#### Phase 3: API Alignment
|
| 225 |
+
|
| 226 |
+
- [ ] Ensure all functions match `@lerobot/web` signatures
|
| 227 |
+
- [ ] Copy and adapt types from `packages/web/src/types/`
|
| 228 |
+
- [ ] Update utilities to use serialport instead of Web Serial
|
| 229 |
+
- [ ] Test API compatibility with existing web examples
|
| 230 |
+
|
| 231 |
+
#### Phase 4: Testing and Documentation
|
| 232 |
+
|
| 233 |
+
- [ ] Create comprehensive tests for all functions
|
| 234 |
+
- [ ] Update documentation and examples
|
| 235 |
+
- [ ] Validate Python lerobot CLI compatibility
|
| 236 |
+
- [ ] Test cross-platform compatibility
|
| 237 |
+
|
| 238 |
+
### Core Functions to Implement
|
| 239 |
+
|
| 240 |
+
#### Package Exports (Mirror Web Package)
|
| 241 |
+
|
| 242 |
+
```typescript
|
| 243 |
+
// packages/node/src/index.ts
|
| 244 |
+
export { calibrate } from "./calibrate.js";
|
| 245 |
+
export { teleoperate } from "./teleoperate.js";
|
| 246 |
+
export { findPort } from "./find_port.js";
|
| 247 |
+
export { releaseMotors } from "./release_motors.js";
|
| 248 |
+
|
| 249 |
+
// Types (mirror web package)
|
| 250 |
+
export type {
|
| 251 |
+
RobotConnection,
|
| 252 |
+
RobotConfig,
|
| 253 |
+
SerialPort,
|
| 254 |
+
SerialPortInfo,
|
| 255 |
+
SerialOptions,
|
| 256 |
+
} from "./types/robot-connection.js";
|
| 257 |
+
|
| 258 |
+
export type {
|
| 259 |
+
FindPortConfig,
|
| 260 |
+
FindPortProcess,
|
| 261 |
+
} from "./types/port-discovery.js";
|
| 262 |
+
|
| 263 |
+
export type {
|
| 264 |
+
CalibrateConfig,
|
| 265 |
+
CalibrationResults,
|
| 266 |
+
LiveCalibrationData,
|
| 267 |
+
CalibrationProcess,
|
| 268 |
+
} from "./types/calibration.js";
|
| 269 |
+
|
| 270 |
+
export type {
|
| 271 |
+
MotorConfig,
|
| 272 |
+
TeleoperationState,
|
| 273 |
+
TeleoperationProcess,
|
| 274 |
+
TeleoperateConfig,
|
| 275 |
+
TeleoperatorConfig,
|
| 276 |
+
} from "./types/teleoperation.js";
|
| 277 |
+
|
| 278 |
+
// Node.js utilities
|
| 279 |
+
export { NodeSerialPortWrapper } from "./utils/serial-port-wrapper.js";
|
| 280 |
+
export { createSO100Config } from "./robots/so100_config.js";
|
| 281 |
+
```
|
| 282 |
+
|
| 283 |
+
#### SerialPort Wrapper (Node.js)
|
| 284 |
+
|
| 285 |
+
```typescript
|
| 286 |
+
// packages/node/src/utils/serial-port-wrapper.ts
|
| 287 |
+
import { SerialPort } from "serialport";
|
| 288 |
+
|
| 289 |
+
export class NodeSerialPortWrapper {
|
| 290 |
+
private port: SerialPort;
|
| 291 |
+
private isConnected: boolean = false;
|
| 292 |
+
|
| 293 |
+
constructor(path: string, options: any = {}) {
|
| 294 |
+
this.port = new SerialPort({
|
| 295 |
+
path,
|
| 296 |
+
baudRate: options.baudRate || 1000000,
|
| 297 |
+
dataBits: options.dataBits || 8,
|
| 298 |
+
parity: options.parity || "none",
|
| 299 |
+
stopBits: options.stopBits || 1,
|
| 300 |
+
autoOpen: false,
|
| 301 |
+
});
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
async initialize(): Promise<void> {
|
| 305 |
+
return new Promise((resolve, reject) => {
|
| 306 |
+
this.port.open((err) => {
|
| 307 |
+
if (err) {
|
| 308 |
+
reject(err);
|
| 309 |
+
} else {
|
| 310 |
+
this.isConnected = true;
|
| 311 |
+
resolve();
|
| 312 |
+
}
|
| 313 |
+
});
|
| 314 |
+
});
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
async writeAndRead(data: Uint8Array): Promise<Uint8Array> {
|
| 318 |
+
return new Promise((resolve, reject) => {
|
| 319 |
+
this.port.write(Buffer.from(data), (err) => {
|
| 320 |
+
if (err) {
|
| 321 |
+
reject(err);
|
| 322 |
+
return;
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
// Wait for response
|
| 326 |
+
setTimeout(() => {
|
| 327 |
+
this.port.read((readErr, readData) => {
|
| 328 |
+
if (readErr) {
|
| 329 |
+
reject(readErr);
|
| 330 |
+
} else {
|
| 331 |
+
resolve(new Uint8Array(readData || []));
|
| 332 |
+
}
|
| 333 |
+
});
|
| 334 |
+
}, 10); // 10ms delay for response
|
| 335 |
+
});
|
| 336 |
+
});
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
async close(): Promise<void> {
|
| 340 |
+
return new Promise((resolve) => {
|
| 341 |
+
this.port.close(() => {
|
| 342 |
+
this.isConnected = false;
|
| 343 |
+
resolve();
|
| 344 |
+
});
|
| 345 |
+
});
|
| 346 |
+
}
|
| 347 |
+
}
|
| 348 |
+
```
|
| 349 |
+
|
| 350 |
+
#### Find Port Implementation
|
| 351 |
+
|
| 352 |
+
```typescript
|
| 353 |
+
// packages/node/src/find_port.ts - Build on existing code
|
| 354 |
+
import { SerialPort } from "serialport";
|
| 355 |
+
|
| 356 |
+
export interface FindPortConfig {
|
| 357 |
+
interactive?: boolean;
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
export interface FindPortProcess {
|
| 361 |
+
getAvailablePorts(): Promise<string[]>;
|
| 362 |
+
detectPort(): Promise<string>; // Interactive cable detection like Python
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
export async function findPort(
|
| 366 |
+
config: FindPortConfig = {}
|
| 367 |
+
): Promise<FindPortProcess> {
|
| 368 |
+
const { interactive = false } = config;
|
| 369 |
+
|
| 370 |
+
return {
|
| 371 |
+
async getAvailablePorts(): Promise<string[]> {
|
| 372 |
+
// Use existing implementation from src/lerobot/node/find_port.ts
|
| 373 |
+
const ports = await SerialPort.list();
|
| 374 |
+
return ports.map((port) => port.path);
|
| 375 |
+
},
|
| 376 |
+
|
| 377 |
+
async detectPort(): Promise<string> {
|
| 378 |
+
if (interactive) {
|
| 379 |
+
// Existing Python-compatible implementation from src/lerobot/node/find_port.ts
|
| 380 |
+
// Shows "disconnect cable" prompts and detects port automatically
|
| 381 |
+
console.log("Finding all available ports for the MotorsBus.");
|
| 382 |
+
|
| 383 |
+
const portsBefore = await this.getAvailablePorts();
|
| 384 |
+
console.log(
|
| 385 |
+
"Remove the USB cable from your MotorsBus and press Enter when done."
|
| 386 |
+
);
|
| 387 |
+
// ... wait for user input ...
|
| 388 |
+
|
| 389 |
+
const portsAfter = await this.getAvailablePorts();
|
| 390 |
+
const portsDiff = portsBefore.filter(
|
| 391 |
+
(port) => !portsAfter.includes(port)
|
| 392 |
+
);
|
| 393 |
+
|
| 394 |
+
if (portsDiff.length === 1) {
|
| 395 |
+
return portsDiff[0];
|
| 396 |
+
} else {
|
| 397 |
+
throw new Error("Could not detect port");
|
| 398 |
+
}
|
| 399 |
+
} else {
|
| 400 |
+
// Programmatic mode - return first available port
|
| 401 |
+
const ports = await this.getAvailablePorts();
|
| 402 |
+
return ports[0];
|
| 403 |
+
}
|
| 404 |
+
},
|
| 405 |
+
};
|
| 406 |
+
}
|
| 407 |
+
```
|
| 408 |
+
|
| 409 |
+
### Technical Considerations
|
| 410 |
+
|
| 411 |
+
#### API Compatibility with Web Package
|
| 412 |
+
|
| 413 |
+
The Node.js package should maintain the same API surface as the web package where possible:
|
| 414 |
+
|
| 415 |
+
```typescript
|
| 416 |
+
// Same function signatures
|
| 417 |
+
await calibrate(config); // Both packages
|
| 418 |
+
await teleoperate(config); // Both packages
|
| 419 |
+
await findPort(config); // Both packages
|
| 420 |
+
await releaseMotors(config); // Both packages
|
| 421 |
+
```
|
| 422 |
+
|
| 423 |
+
#### Platform-Specific Adaptations
|
| 424 |
+
|
| 425 |
+
**File System Access:**
|
| 426 |
+
|
| 427 |
+
- Node.js: Direct file system access for calibration data
|
| 428 |
+
- Web: localStorage/IndexedDB for calibration data
|
| 429 |
+
|
| 430 |
+
**Process Management:**
|
| 431 |
+
|
| 432 |
+
- Node.js: Process signals, stdin/stdout handling
|
| 433 |
+
- Web: Browser events, DOM keyboard handling
|
| 434 |
+
|
| 435 |
+
**Error Handling:**
|
| 436 |
+
|
| 437 |
+
- Node.js: Process exit codes, console.error
|
| 438 |
+
- Web: User-friendly error dialogs
|
| 439 |
+
|
| 440 |
+
#### Python lerobot CLI Compatibility
|
| 441 |
+
|
| 442 |
+
The Node.js package must maintain exact Python lerobot CLI compatibility:
|
| 443 |
+
|
| 444 |
+
```bash
|
| 445 |
+
# These commands must work identically
|
| 446 |
+
npx lerobot find-port
|
| 447 |
+
npx lerobot calibrate --robot.type=so100_follower --robot.port=/dev/ttyUSB0
|
| 448 |
+
npx lerobot teleoperate --robot.type=so100_follower --robot.port=/dev/ttyUSB0
|
| 449 |
+
```
|
| 450 |
+
|
| 451 |
+
#### Calibration Data Storage Location
|
| 452 |
+
|
| 453 |
+
The CLI should store calibration data in the same location as Python lerobot:
|
| 454 |
+
|
| 455 |
+
```bash
|
| 456 |
+
# Default location (matches Python lerobot)
|
| 457 |
+
~/.cache/huggingface/lerobot/calibration/robots/
|
| 458 |
+
```
|
| 459 |
+
|
| 460 |
+
This ensures calibration files are compatible between Python lerobot and Node.js lerobot:
|
| 461 |
+
|
| 462 |
+
```typescript
|
| 463 |
+
// Use HF_HOME environment variable like Python lerobot
|
| 464 |
+
const HF_HOME =
|
| 465 |
+
process.env.HF_HOME || path.join(os.homedir(), ".cache", "huggingface");
|
| 466 |
+
const CALIBRATION_DIR = path.join(HF_HOME, "lerobot", "calibration", "robots");
|
| 467 |
+
```
|
| 468 |
+
|
| 469 |
+
### Package Configuration
|
| 470 |
+
|
| 471 |
+
#### package.json
|
| 472 |
+
|
| 473 |
+
```json
|
| 474 |
+
{
|
| 475 |
+
"name": "@lerobot/node",
|
| 476 |
+
"version": "0.1.0",
|
| 477 |
+
"description": "Node.js-based robotics control using SerialPort",
|
| 478 |
+
"type": "module",
|
| 479 |
+
"main": "./dist/index.js",
|
| 480 |
+
"types": "./dist/index.d.ts",
|
| 481 |
+
"bin": {
|
| 482 |
+
"lerobot": "./dist/cli.js"
|
| 483 |
+
},
|
| 484 |
+
"exports": {
|
| 485 |
+
".": {
|
| 486 |
+
"import": "./dist/index.js",
|
| 487 |
+
"types": "./dist/index.d.ts"
|
| 488 |
+
},
|
| 489 |
+
"./calibrate": {
|
| 490 |
+
"import": "./dist/calibrate.js",
|
| 491 |
+
"types": "./dist/calibrate.d.ts"
|
| 492 |
+
},
|
| 493 |
+
"./teleoperate": {
|
| 494 |
+
"import": "./dist/teleoperate.js",
|
| 495 |
+
"types": "./dist/teleoperate.d.ts"
|
| 496 |
+
},
|
| 497 |
+
"./find-port": {
|
| 498 |
+
"import": "./dist/find_port.js",
|
| 499 |
+
"types": "./dist/find_port.d.ts"
|
| 500 |
+
}
|
| 501 |
+
},
|
| 502 |
+
"files": ["dist/**/*", "README.md"],
|
| 503 |
+
"keywords": [
|
| 504 |
+
"robotics",
|
| 505 |
+
"serialport",
|
| 506 |
+
"hardware-control",
|
| 507 |
+
"nodejs",
|
| 508 |
+
"typescript"
|
| 509 |
+
],
|
| 510 |
+
"scripts": {
|
| 511 |
+
"build": "tsc --project tsconfig.build.json",
|
| 512 |
+
"prepublishOnly": "npm run build"
|
| 513 |
+
},
|
| 514 |
+
"dependencies": {
|
| 515 |
+
"serialport": "^12.0.0",
|
| 516 |
+
"chalk": "^5.3.0",
|
| 517 |
+
"commander": "^11.0.0"
|
| 518 |
+
},
|
| 519 |
+
"peerDependencies": {
|
| 520 |
+
"typescript": ">=4.5.0"
|
| 521 |
+
},
|
| 522 |
+
"engines": {
|
| 523 |
+
"node": ">=18.0.0"
|
| 524 |
+
}
|
| 525 |
+
}
|
| 526 |
+
```
|
| 527 |
+
|
| 528 |
+
## Definition of Done
|
| 529 |
+
|
| 530 |
+
- [ ] **Package Structure**: Complete `packages/node` directory with proper NPM package setup
|
| 531 |
+
- [ ] **API Mirror**: All four core functions (`findPort`, `calibrate`, `releaseMotors`, `teleoperate`) implemented with same API as web package
|
| 532 |
+
- [ ] **SerialPort Integration**: All hardware communication uses `serialport` package instead of Web Serial
|
| 533 |
+
- [ ] **Type Safety**: Full TypeScript coverage with strict type checking
|
| 534 |
+
- [ ] **Code Migration**: Existing `src/lerobot/node` code successfully migrated and enhanced
|
| 535 |
+
- [ ] **Cross-Platform**: Works on Windows, macOS, and Linux with Node.js 18+
|
| 536 |
+
- [ ] **CLI Integration**: `npx lerobot` commands work using the Node.js package
|
| 537 |
+
- [ ] **Python Compatibility**: CLI commands match Python lerobot behavior exactly
|
| 538 |
+
- [ ] **NPM Ready**: Package published as `@lerobot/node` with proper versioning
|
| 539 |
+
- [ ] **Documentation**: Complete README with usage examples and API documentation
|
| 540 |
+
- [ ] **Testing**: Comprehensive test suite covering all core functionality
|
| 541 |
+
- [ ] **No Regressions**: All existing Node.js functionality preserved and enhanced
|
examples/cyberpunk-standalone/src/components/calibration-view.tsx
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
| 19 |
releaseMotors,
|
| 20 |
type CalibrationProcess,
|
| 21 |
type LiveCalibrationData,
|
| 22 |
-
type
|
| 23 |
type RobotConnection,
|
| 24 |
} from "@lerobot/web";
|
| 25 |
import {
|
|
@@ -42,7 +42,7 @@ export function CalibrationView({ robot }: CalibrationViewProps) {
|
|
| 42 |
const [calibrationProcess, setCalibrationProcess] =
|
| 43 |
useState<CalibrationProcess | null>(null);
|
| 44 |
const [calibrationResults, setCalibrationResults] =
|
| 45 |
-
useState<
|
| 46 |
const { toast } = useToast();
|
| 47 |
|
| 48 |
// Load existing calibration data from unified storage
|
|
@@ -165,7 +165,7 @@ export function CalibrationView({ robot }: CalibrationViewProps) {
|
|
| 165 |
readCount: Object.keys(liveData || {}).length > 0 ? 100 : 0,
|
| 166 |
};
|
| 167 |
|
| 168 |
-
// Use the result directly as
|
| 169 |
saveCalibrationData(robot.serialNumber, result, metadata);
|
| 170 |
}
|
| 171 |
|
|
|
|
| 19 |
releaseMotors,
|
| 20 |
type CalibrationProcess,
|
| 21 |
type LiveCalibrationData,
|
| 22 |
+
type CalibrationResults,
|
| 23 |
type RobotConnection,
|
| 24 |
} from "@lerobot/web";
|
| 25 |
import {
|
|
|
|
| 42 |
const [calibrationProcess, setCalibrationProcess] =
|
| 43 |
useState<CalibrationProcess | null>(null);
|
| 44 |
const [calibrationResults, setCalibrationResults] =
|
| 45 |
+
useState<CalibrationResults | null>(null);
|
| 46 |
const { toast } = useToast();
|
| 47 |
|
| 48 |
// Load existing calibration data from unified storage
|
|
|
|
| 165 |
readCount: Object.keys(liveData || {}).length > 0 ? 100 : 0,
|
| 166 |
};
|
| 167 |
|
| 168 |
+
// Use the result directly as CalibrationResults
|
| 169 |
saveCalibrationData(robot.serialNumber, result, metadata);
|
| 170 |
}
|
| 171 |
|
examples/cyberpunk-standalone/src/components/docs-section.tsx
CHANGED
|
@@ -259,8 +259,8 @@ const calibrationData = await calibrationProcess.result;`}
|
|
| 259 |
</h5>
|
| 260 |
<ul className="mt-1 ml-4 space-y-1 text-sm text-muted-foreground">
|
| 261 |
<li>
|
| 262 |
-
• <code>result: Promise<
|
| 263 |
-
|
| 264 |
</li>
|
| 265 |
<li>
|
| 266 |
• <code>stop(): void</code> - Stop calibration process
|
|
|
|
| 259 |
</h5>
|
| 260 |
<ul className="mt-1 ml-4 space-y-1 text-sm text-muted-foreground">
|
| 261 |
<li>
|
| 262 |
+
• <code>result: Promise<CalibrationResults></code> -
|
| 263 |
+
Python-compatible format
|
| 264 |
</li>
|
| 265 |
<li>
|
| 266 |
• <code>stop(): void</code> - Stop calibration process
|
examples/cyberpunk-standalone/src/lib/unified-storage.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
* Manages device persistence using localStorage with serial numbers as keys
|
| 4 |
*/
|
| 5 |
|
| 6 |
-
import type {
|
| 7 |
|
| 8 |
export interface DeviceInfo {
|
| 9 |
serialNumber: string;
|
|
@@ -25,7 +25,7 @@ export interface CalibrationMetadata {
|
|
| 25 |
|
| 26 |
export interface UnifiedRobotData {
|
| 27 |
device_info: DeviceInfo;
|
| 28 |
-
calibration?:
|
| 29 |
device_type?: string;
|
| 30 |
device_id?: string;
|
| 31 |
calibrated_at?: string;
|
|
@@ -65,7 +65,7 @@ export function saveUnifiedRobotData(
|
|
| 65 |
|
| 66 |
export function saveCalibrationData(
|
| 67 |
serialNumber: string,
|
| 68 |
-
calibrationData:
|
| 69 |
metadata: CalibrationMetadata
|
| 70 |
): void {
|
| 71 |
try {
|
|
|
|
| 3 |
* Manages device persistence using localStorage with serial numbers as keys
|
| 4 |
*/
|
| 5 |
|
| 6 |
+
import type { CalibrationResults } from "@lerobot/web";
|
| 7 |
|
| 8 |
export interface DeviceInfo {
|
| 9 |
serialNumber: string;
|
|
|
|
| 25 |
|
| 26 |
export interface UnifiedRobotData {
|
| 27 |
device_info: DeviceInfo;
|
| 28 |
+
calibration?: CalibrationResults & {
|
| 29 |
device_type?: string;
|
| 30 |
device_id?: string;
|
| 31 |
calibrated_at?: string;
|
|
|
|
| 65 |
|
| 66 |
export function saveCalibrationData(
|
| 67 |
serialNumber: string,
|
| 68 |
+
calibrationData: CalibrationResults,
|
| 69 |
metadata: CalibrationMetadata
|
| 70 |
): void {
|
| 71 |
try {
|
examples/cyberpunk-standalone/src/types/robot.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
export type {
|
| 2 |
RobotConnection,
|
| 3 |
LiveCalibrationData,
|
| 4 |
-
|
| 5 |
TeleoperationState,
|
| 6 |
} from "@lerobot/web";
|
|
|
|
| 1 |
export type {
|
| 2 |
RobotConnection,
|
| 3 |
LiveCalibrationData,
|
| 4 |
+
CalibrationResults,
|
| 5 |
TeleoperationState,
|
| 6 |
} from "@lerobot/web";
|
examples/node-quick-start/.gitignore
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dependencies
|
| 2 |
+
node_modules/
|
| 3 |
+
|
| 4 |
+
# Build outputs
|
| 5 |
+
dist/
|
| 6 |
+
*.tsbuildinfo
|
| 7 |
+
|
| 8 |
+
# Environment files
|
| 9 |
+
.env
|
| 10 |
+
.env.local
|
| 11 |
+
|
| 12 |
+
# IDE files
|
| 13 |
+
.vscode/
|
| 14 |
+
.idea/
|
| 15 |
+
*.swp
|
| 16 |
+
*.swo
|
| 17 |
+
|
| 18 |
+
# OS files
|
| 19 |
+
.DS_Store
|
| 20 |
+
Thumbs.db
|
| 21 |
+
|
| 22 |
+
# Logs
|
| 23 |
+
*.log
|
| 24 |
+
npm-debug.log*
|
| 25 |
+
|
| 26 |
+
# Runtime data
|
| 27 |
+
pids
|
| 28 |
+
*.pid
|
| 29 |
+
*.seed
|
| 30 |
+
*.pid.lock
|
examples/node-quick-start/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Node.js Quick Start Example (Vite)
|
| 2 |
+
|
| 3 |
+
This example demonstrates the complete workflow of using `@lerobot/node` to control robotics hardware from Node.js applications, using Vite for fast development and building. Follows the same pattern as the web Quick Start guide.
|
| 4 |
+
|
| 5 |
+
## What This Example Does
|
| 6 |
+
|
| 7 |
+
1. **Find Port**: Discovers and connects to your robot hardware
|
| 8 |
+
2. **Release Motors**: Puts motors in free-movement mode for setup
|
| 9 |
+
3. **Calibrate**: Records motor ranges and sets homing positions
|
| 10 |
+
4. **Teleoperate**: Enables keyboard control of the robot
|
| 11 |
+
|
| 12 |
+
## Hardware Requirements
|
| 13 |
+
|
| 14 |
+
- SO-100 robotic arm with STS3215 servos
|
| 15 |
+
- USB connection to your computer
|
| 16 |
+
- Compatible with Windows (COM ports), macOS, and Linux (/dev/tty\* ports)
|
| 17 |
+
|
| 18 |
+
## Installation
|
| 19 |
+
|
| 20 |
+
```bash
|
| 21 |
+
# Install dependencies (from project root)
|
| 22 |
+
pnpm install
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
## Running the Examples
|
| 26 |
+
|
| 27 |
+
### Full Workflow Demo
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
# Complete robot setup workflow (interactive)
|
| 31 |
+
pnpm demo:full-workflow
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
### Individual Component Demos
|
| 35 |
+
|
| 36 |
+
```bash
|
| 37 |
+
# Test port discovery
|
| 38 |
+
pnpm demo:find-port
|
| 39 |
+
|
| 40 |
+
# Test calibration (requires connected robot)
|
| 41 |
+
pnpm demo:calibrate
|
| 42 |
+
|
| 43 |
+
# Test keyboard control (requires calibrated robot)
|
| 44 |
+
pnpm demo:teleoperate
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
## Example Usage
|
| 48 |
+
|
| 49 |
+
The main demo (`src/main.ts`) shows the complete workflow:
|
| 50 |
+
|
| 51 |
+
```typescript
|
| 52 |
+
import { findPort, connectPort, releaseMotors, calibrate, teleoperate } from "@lerobot/node";
|
| 53 |
+
|
| 54 |
+
// 1. Find available robots
|
| 55 |
+
const findProcess = await findPort();
|
| 56 |
+
const robots = await findProcess.result;
|
| 57 |
+
|
| 58 |
+
// 2. Connect to first robot found
|
| 59 |
+
const robot = await connectPort(robots[0].path, "so100_follower", "my_robot_arm");
|
| 60 |
+
|
| 61 |
+
// 3. Release motors for manual positioning
|
| 62 |
+
await releaseMotors(robot);
|
| 63 |
+
|
| 64 |
+
// 4. Calibrate motor ranges
|
| 65 |
+
const calibrationProcess = await calibrate({
|
| 66 |
+
robot,
|
| 67 |
+
onProgress: (message) => console.log(message),
|
| 68 |
+
});
|
| 69 |
+
|
| 70 |
+
// 5. Control robot with keyboard
|
| 71 |
+
const teleop = await teleoperate({
|
| 72 |
+
robot,
|
| 73 |
+
teleop: { type: "keyboard" },
|
| 74 |
+
});
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
## CLI Commands
|
| 78 |
+
|
| 79 |
+
You can also use the CLI directly:
|
| 80 |
+
|
| 81 |
+
```bash
|
| 82 |
+
# Find available ports
|
| 83 |
+
npx lerobot find-port
|
| 84 |
+
|
| 85 |
+
# Calibrate robot
|
| 86 |
+
npx lerobot calibrate --robot.type so100_follower --robot.port /dev/ttyUSB0 --robot.id my_robot
|
| 87 |
+
|
| 88 |
+
# Control robot
|
| 89 |
+
npx lerobot teleoperate --robot.type so100_follower --robot.port /dev/ttyUSB0 --robot.id my_robot
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
## Development
|
| 93 |
+
|
| 94 |
+
```bash
|
| 95 |
+
# Run with Vite Node (faster development with hot reload)
|
| 96 |
+
pnpm dev
|
| 97 |
+
|
| 98 |
+
# Build with Vite and run compiled version
|
| 99 |
+
pnpm build && pnpm start
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
## Safety Notes
|
| 103 |
+
|
| 104 |
+
⚠️ **Important Safety Guidelines:**
|
| 105 |
+
|
| 106 |
+
- Always ensure robot is in a safe position before running examples
|
| 107 |
+
- Keep emergency stop accessible (ESC key during teleoperation)
|
| 108 |
+
- Start with small movements to test calibration
|
| 109 |
+
- Ensure robot has adequate workspace clearance
|
| 110 |
+
|
| 111 |
+
## Troubleshooting
|
| 112 |
+
|
| 113 |
+
**Port Not Found:**
|
| 114 |
+
|
| 115 |
+
- Check USB connection
|
| 116 |
+
- Verify robot is powered on
|
| 117 |
+
- Try different USB ports/cables
|
| 118 |
+
- On Linux: Check user permissions for serial ports
|
| 119 |
+
|
| 120 |
+
**Calibration Issues:**
|
| 121 |
+
|
| 122 |
+
- Ensure motors are released and can move freely
|
| 123 |
+
- Move each joint through its full range slowly
|
| 124 |
+
- Avoid forcing motors past mechanical limits
|
| 125 |
+
|
| 126 |
+
**Control Problems:**
|
| 127 |
+
|
| 128 |
+
- Verify calibration completed successfully
|
| 129 |
+
- Check that calibration file was saved
|
| 130 |
+
- Restart the teleoperation if motors don't respond
|
examples/node-quick-start/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "node-quick-start",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"description": "Node.js Quick Start example for @lerobot/node package",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite-node src/main.ts",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"start": "node dist/main.js",
|
| 10 |
+
"demo:find-port": "vite-node src/demo-find-port.ts",
|
| 11 |
+
"demo:calibrate": "vite-node src/demo-calibrate.ts",
|
| 12 |
+
"demo:teleoperate": "vite-node src/demo-teleoperate.ts",
|
| 13 |
+
"demo:full-workflow": "vite-node src/main.ts"
|
| 14 |
+
},
|
| 15 |
+
"dependencies": {
|
| 16 |
+
"@lerobot/node": "file:../../packages/node"
|
| 17 |
+
},
|
| 18 |
+
"devDependencies": {
|
| 19 |
+
"vite": "^6.3.5",
|
| 20 |
+
"vite-node": "^2.0.0",
|
| 21 |
+
"typescript": "^5.3.0",
|
| 22 |
+
"@types/node": "^18.0.0"
|
| 23 |
+
},
|
| 24 |
+
"engines": {
|
| 25 |
+
"node": ">=18.0.0"
|
| 26 |
+
},
|
| 27 |
+
"keywords": [
|
| 28 |
+
"robotics",
|
| 29 |
+
"lerobot",
|
| 30 |
+
"nodejs",
|
| 31 |
+
"example",
|
| 32 |
+
"demo"
|
| 33 |
+
],
|
| 34 |
+
"author": "LeRobot.js Team",
|
| 35 |
+
"license": "Apache-2.0"
|
| 36 |
+
}
|
examples/node-quick-start/pnpm-lock.yaml
ADDED
|
@@ -0,0 +1,1174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
lockfileVersion: '9.0'
|
| 2 |
+
|
| 3 |
+
settings:
|
| 4 |
+
autoInstallPeers: true
|
| 5 |
+
excludeLinksFromLockfile: false
|
| 6 |
+
|
| 7 |
+
importers:
|
| 8 |
+
|
| 9 |
+
.:
|
| 10 |
+
dependencies:
|
| 11 |
+
'@lerobot/node':
|
| 12 |
+
specifier: file:../../packages/node
|
| 13 |
+
version: file:../../packages/node([email protected])
|
| 14 |
+
devDependencies:
|
| 15 |
+
'@types/node':
|
| 16 |
+
specifier: ^18.0.0
|
| 17 |
+
version: 18.19.120
|
| 18 |
+
typescript:
|
| 19 |
+
specifier: ^5.3.0
|
| 20 |
+
version: 5.8.3
|
| 21 |
+
vite:
|
| 22 |
+
specifier: ^6.3.5
|
| 23 |
+
version: 6.3.5(@types/[email protected])([email protected])
|
| 24 |
+
vite-node:
|
| 25 |
+
specifier: ^2.0.0
|
| 26 |
+
version: 2.1.9(@types/[email protected])
|
| 27 |
+
|
| 28 |
+
packages:
|
| 29 |
+
|
| 30 |
+
'@esbuild/[email protected]':
|
| 31 |
+
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
| 32 |
+
engines: {node: '>=12'}
|
| 33 |
+
cpu: [ppc64]
|
| 34 |
+
os: [aix]
|
| 35 |
+
|
| 36 |
+
'@esbuild/[email protected]':
|
| 37 |
+
resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==}
|
| 38 |
+
engines: {node: '>=18'}
|
| 39 |
+
cpu: [ppc64]
|
| 40 |
+
os: [aix]
|
| 41 |
+
|
| 42 |
+
'@esbuild/[email protected]':
|
| 43 |
+
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
|
| 44 |
+
engines: {node: '>=12'}
|
| 45 |
+
cpu: [arm64]
|
| 46 |
+
os: [android]
|
| 47 |
+
|
| 48 |
+
'@esbuild/[email protected]':
|
| 49 |
+
resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==}
|
| 50 |
+
engines: {node: '>=18'}
|
| 51 |
+
cpu: [arm64]
|
| 52 |
+
os: [android]
|
| 53 |
+
|
| 54 |
+
'@esbuild/[email protected]':
|
| 55 |
+
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
|
| 56 |
+
engines: {node: '>=12'}
|
| 57 |
+
cpu: [arm]
|
| 58 |
+
os: [android]
|
| 59 |
+
|
| 60 |
+
'@esbuild/[email protected]':
|
| 61 |
+
resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==}
|
| 62 |
+
engines: {node: '>=18'}
|
| 63 |
+
cpu: [arm]
|
| 64 |
+
os: [android]
|
| 65 |
+
|
| 66 |
+
'@esbuild/[email protected]':
|
| 67 |
+
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
|
| 68 |
+
engines: {node: '>=12'}
|
| 69 |
+
cpu: [x64]
|
| 70 |
+
os: [android]
|
| 71 |
+
|
| 72 |
+
'@esbuild/[email protected]':
|
| 73 |
+
resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==}
|
| 74 |
+
engines: {node: '>=18'}
|
| 75 |
+
cpu: [x64]
|
| 76 |
+
os: [android]
|
| 77 |
+
|
| 78 |
+
'@esbuild/[email protected]':
|
| 79 |
+
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
|
| 80 |
+
engines: {node: '>=12'}
|
| 81 |
+
cpu: [arm64]
|
| 82 |
+
os: [darwin]
|
| 83 |
+
|
| 84 |
+
'@esbuild/[email protected]':
|
| 85 |
+
resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==}
|
| 86 |
+
engines: {node: '>=18'}
|
| 87 |
+
cpu: [arm64]
|
| 88 |
+
os: [darwin]
|
| 89 |
+
|
| 90 |
+
'@esbuild/[email protected]':
|
| 91 |
+
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
|
| 92 |
+
engines: {node: '>=12'}
|
| 93 |
+
cpu: [x64]
|
| 94 |
+
os: [darwin]
|
| 95 |
+
|
| 96 |
+
'@esbuild/[email protected]':
|
| 97 |
+
resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==}
|
| 98 |
+
engines: {node: '>=18'}
|
| 99 |
+
cpu: [x64]
|
| 100 |
+
os: [darwin]
|
| 101 |
+
|
| 102 |
+
'@esbuild/[email protected]':
|
| 103 |
+
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
|
| 104 |
+
engines: {node: '>=12'}
|
| 105 |
+
cpu: [arm64]
|
| 106 |
+
os: [freebsd]
|
| 107 |
+
|
| 108 |
+
'@esbuild/[email protected]':
|
| 109 |
+
resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==}
|
| 110 |
+
engines: {node: '>=18'}
|
| 111 |
+
cpu: [arm64]
|
| 112 |
+
os: [freebsd]
|
| 113 |
+
|
| 114 |
+
'@esbuild/[email protected]':
|
| 115 |
+
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
|
| 116 |
+
engines: {node: '>=12'}
|
| 117 |
+
cpu: [x64]
|
| 118 |
+
os: [freebsd]
|
| 119 |
+
|
| 120 |
+
'@esbuild/[email protected]':
|
| 121 |
+
resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==}
|
| 122 |
+
engines: {node: '>=18'}
|
| 123 |
+
cpu: [x64]
|
| 124 |
+
os: [freebsd]
|
| 125 |
+
|
| 126 |
+
'@esbuild/[email protected]':
|
| 127 |
+
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
|
| 128 |
+
engines: {node: '>=12'}
|
| 129 |
+
cpu: [arm64]
|
| 130 |
+
os: [linux]
|
| 131 |
+
|
| 132 |
+
'@esbuild/[email protected]':
|
| 133 |
+
resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==}
|
| 134 |
+
engines: {node: '>=18'}
|
| 135 |
+
cpu: [arm64]
|
| 136 |
+
os: [linux]
|
| 137 |
+
|
| 138 |
+
'@esbuild/[email protected]':
|
| 139 |
+
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
|
| 140 |
+
engines: {node: '>=12'}
|
| 141 |
+
cpu: [arm]
|
| 142 |
+
os: [linux]
|
| 143 |
+
|
| 144 |
+
'@esbuild/[email protected]':
|
| 145 |
+
resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==}
|
| 146 |
+
engines: {node: '>=18'}
|
| 147 |
+
cpu: [arm]
|
| 148 |
+
os: [linux]
|
| 149 |
+
|
| 150 |
+
'@esbuild/[email protected]':
|
| 151 |
+
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
|
| 152 |
+
engines: {node: '>=12'}
|
| 153 |
+
cpu: [ia32]
|
| 154 |
+
os: [linux]
|
| 155 |
+
|
| 156 |
+
'@esbuild/[email protected]':
|
| 157 |
+
resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==}
|
| 158 |
+
engines: {node: '>=18'}
|
| 159 |
+
cpu: [ia32]
|
| 160 |
+
os: [linux]
|
| 161 |
+
|
| 162 |
+
'@esbuild/[email protected]':
|
| 163 |
+
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
|
| 164 |
+
engines: {node: '>=12'}
|
| 165 |
+
cpu: [loong64]
|
| 166 |
+
os: [linux]
|
| 167 |
+
|
| 168 |
+
'@esbuild/[email protected]':
|
| 169 |
+
resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==}
|
| 170 |
+
engines: {node: '>=18'}
|
| 171 |
+
cpu: [loong64]
|
| 172 |
+
os: [linux]
|
| 173 |
+
|
| 174 |
+
'@esbuild/[email protected]':
|
| 175 |
+
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
|
| 176 |
+
engines: {node: '>=12'}
|
| 177 |
+
cpu: [mips64el]
|
| 178 |
+
os: [linux]
|
| 179 |
+
|
| 180 |
+
'@esbuild/[email protected]':
|
| 181 |
+
resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==}
|
| 182 |
+
engines: {node: '>=18'}
|
| 183 |
+
cpu: [mips64el]
|
| 184 |
+
os: [linux]
|
| 185 |
+
|
| 186 |
+
'@esbuild/[email protected]':
|
| 187 |
+
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
|
| 188 |
+
engines: {node: '>=12'}
|
| 189 |
+
cpu: [ppc64]
|
| 190 |
+
os: [linux]
|
| 191 |
+
|
| 192 |
+
'@esbuild/[email protected]':
|
| 193 |
+
resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==}
|
| 194 |
+
engines: {node: '>=18'}
|
| 195 |
+
cpu: [ppc64]
|
| 196 |
+
os: [linux]
|
| 197 |
+
|
| 198 |
+
'@esbuild/[email protected]':
|
| 199 |
+
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
|
| 200 |
+
engines: {node: '>=12'}
|
| 201 |
+
cpu: [riscv64]
|
| 202 |
+
os: [linux]
|
| 203 |
+
|
| 204 |
+
'@esbuild/[email protected]':
|
| 205 |
+
resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==}
|
| 206 |
+
engines: {node: '>=18'}
|
| 207 |
+
cpu: [riscv64]
|
| 208 |
+
os: [linux]
|
| 209 |
+
|
| 210 |
+
'@esbuild/[email protected]':
|
| 211 |
+
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
|
| 212 |
+
engines: {node: '>=12'}
|
| 213 |
+
cpu: [s390x]
|
| 214 |
+
os: [linux]
|
| 215 |
+
|
| 216 |
+
'@esbuild/[email protected]':
|
| 217 |
+
resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==}
|
| 218 |
+
engines: {node: '>=18'}
|
| 219 |
+
cpu: [s390x]
|
| 220 |
+
os: [linux]
|
| 221 |
+
|
| 222 |
+
'@esbuild/[email protected]':
|
| 223 |
+
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
|
| 224 |
+
engines: {node: '>=12'}
|
| 225 |
+
cpu: [x64]
|
| 226 |
+
os: [linux]
|
| 227 |
+
|
| 228 |
+
'@esbuild/[email protected]':
|
| 229 |
+
resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==}
|
| 230 |
+
engines: {node: '>=18'}
|
| 231 |
+
cpu: [x64]
|
| 232 |
+
os: [linux]
|
| 233 |
+
|
| 234 |
+
'@esbuild/[email protected]':
|
| 235 |
+
resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==}
|
| 236 |
+
engines: {node: '>=18'}
|
| 237 |
+
cpu: [arm64]
|
| 238 |
+
os: [netbsd]
|
| 239 |
+
|
| 240 |
+
'@esbuild/[email protected]':
|
| 241 |
+
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
|
| 242 |
+
engines: {node: '>=12'}
|
| 243 |
+
cpu: [x64]
|
| 244 |
+
os: [netbsd]
|
| 245 |
+
|
| 246 |
+
'@esbuild/[email protected]':
|
| 247 |
+
resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==}
|
| 248 |
+
engines: {node: '>=18'}
|
| 249 |
+
cpu: [x64]
|
| 250 |
+
os: [netbsd]
|
| 251 |
+
|
| 252 |
+
'@esbuild/[email protected]':
|
| 253 |
+
resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==}
|
| 254 |
+
engines: {node: '>=18'}
|
| 255 |
+
cpu: [arm64]
|
| 256 |
+
os: [openbsd]
|
| 257 |
+
|
| 258 |
+
'@esbuild/[email protected]':
|
| 259 |
+
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
|
| 260 |
+
engines: {node: '>=12'}
|
| 261 |
+
cpu: [x64]
|
| 262 |
+
os: [openbsd]
|
| 263 |
+
|
| 264 |
+
'@esbuild/[email protected]':
|
| 265 |
+
resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==}
|
| 266 |
+
engines: {node: '>=18'}
|
| 267 |
+
cpu: [x64]
|
| 268 |
+
os: [openbsd]
|
| 269 |
+
|
| 270 |
+
'@esbuild/[email protected]':
|
| 271 |
+
resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==}
|
| 272 |
+
engines: {node: '>=18'}
|
| 273 |
+
cpu: [arm64]
|
| 274 |
+
os: [openharmony]
|
| 275 |
+
|
| 276 |
+
'@esbuild/[email protected]':
|
| 277 |
+
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
|
| 278 |
+
engines: {node: '>=12'}
|
| 279 |
+
cpu: [x64]
|
| 280 |
+
os: [sunos]
|
| 281 |
+
|
| 282 |
+
'@esbuild/[email protected]':
|
| 283 |
+
resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==}
|
| 284 |
+
engines: {node: '>=18'}
|
| 285 |
+
cpu: [x64]
|
| 286 |
+
os: [sunos]
|
| 287 |
+
|
| 288 |
+
'@esbuild/[email protected]':
|
| 289 |
+
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
|
| 290 |
+
engines: {node: '>=12'}
|
| 291 |
+
cpu: [arm64]
|
| 292 |
+
os: [win32]
|
| 293 |
+
|
| 294 |
+
'@esbuild/[email protected]':
|
| 295 |
+
resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==}
|
| 296 |
+
engines: {node: '>=18'}
|
| 297 |
+
cpu: [arm64]
|
| 298 |
+
os: [win32]
|
| 299 |
+
|
| 300 |
+
'@esbuild/[email protected]':
|
| 301 |
+
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
|
| 302 |
+
engines: {node: '>=12'}
|
| 303 |
+
cpu: [ia32]
|
| 304 |
+
os: [win32]
|
| 305 |
+
|
| 306 |
+
'@esbuild/[email protected]':
|
| 307 |
+
resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==}
|
| 308 |
+
engines: {node: '>=18'}
|
| 309 |
+
cpu: [ia32]
|
| 310 |
+
os: [win32]
|
| 311 |
+
|
| 312 |
+
'@esbuild/[email protected]':
|
| 313 |
+
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
|
| 314 |
+
engines: {node: '>=12'}
|
| 315 |
+
cpu: [x64]
|
| 316 |
+
os: [win32]
|
| 317 |
+
|
| 318 |
+
'@esbuild/[email protected]':
|
| 319 |
+
resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==}
|
| 320 |
+
engines: {node: '>=18'}
|
| 321 |
+
cpu: [x64]
|
| 322 |
+
os: [win32]
|
| 323 |
+
|
| 324 |
+
'@lerobot/node@file:../../packages/node':
|
| 325 |
+
resolution: {directory: ../../packages/node, type: directory}
|
| 326 |
+
engines: {node: '>=18.0.0'}
|
| 327 |
+
peerDependencies:
|
| 328 |
+
typescript: '>=4.5.0'
|
| 329 |
+
|
| 330 |
+
'@rollup/[email protected]':
|
| 331 |
+
resolution: {integrity: sha512-9f3nSTFI2ivfxc7/tHBHcJ8pRnp8ROrELvsVprlQPVvcZ+j5zztYd+PTJGpyIOAdTvNwNrpCXswKSeoQcyGjMQ==}
|
| 332 |
+
cpu: [arm]
|
| 333 |
+
os: [android]
|
| 334 |
+
|
| 335 |
+
'@rollup/[email protected]':
|
| 336 |
+
resolution: {integrity: sha512-tFZSEhqJ8Yrpe50TzOdeoYi72gi/jsnT7y8Qrozf3cNu28WX+s6I3XzEPUAqoaT9SAS8Xz9AzGTFlxxCH/w20w==}
|
| 337 |
+
cpu: [arm64]
|
| 338 |
+
os: [android]
|
| 339 |
+
|
| 340 |
+
'@rollup/[email protected]':
|
| 341 |
+
resolution: {integrity: sha512-+DikIIs+p6yU2hF51UaWG8BnHbq90X0QIOt5zqSKSZxY+G3qqdLih214e9InJal21af2PuuxkDectetGfbVPJw==}
|
| 342 |
+
cpu: [arm64]
|
| 343 |
+
os: [darwin]
|
| 344 |
+
|
| 345 |
+
'@rollup/[email protected]':
|
| 346 |
+
resolution: {integrity: sha512-5a+NofhdEB/WimSlFMskbFQn1vqz1FWryYpA99trmZGO6qEmiS0IsX6w4B3d91U878Q2ZQdiaFF1gxX4P147og==}
|
| 347 |
+
cpu: [x64]
|
| 348 |
+
os: [darwin]
|
| 349 |
+
|
| 350 |
+
'@rollup/[email protected]':
|
| 351 |
+
resolution: {integrity: sha512-igr/RlKPS3OCy4jD3XBmAmo3UAcNZkJSubRsw1JeM8bAbwf15k/3eMZXD91bnjheijJiOJcga3kfCLKjV8IXNg==}
|
| 352 |
+
cpu: [arm64]
|
| 353 |
+
os: [freebsd]
|
| 354 |
+
|
| 355 |
+
'@rollup/[email protected]':
|
| 356 |
+
resolution: {integrity: sha512-MdigWzPSHlQzB1xZ+MdFDWTAH+kcn7UxjEBoOKuaso7z1DRlnAnrknB1mTtNOQ+GdPI8xgExAGwHeqQjntR0Cg==}
|
| 357 |
+
cpu: [x64]
|
| 358 |
+
os: [freebsd]
|
| 359 |
+
|
| 360 |
+
'@rollup/[email protected]':
|
| 361 |
+
resolution: {integrity: sha512-dmZseE0ZwA/4yy1+BwFrDqFTjjNg24GO9xSrb1weVbt6AFkhp5pz1gVS7IMtfIvoWy8yp6q/zN0bKnefRUImvQ==}
|
| 362 |
+
cpu: [arm]
|
| 363 |
+
os: [linux]
|
| 364 |
+
|
| 365 |
+
'@rollup/[email protected]':
|
| 366 |
+
resolution: {integrity: sha512-fzhfn6p9Cfm3W8UrWKIa4l7Wfjs/KGdgaswMBBE3KY3Ta43jg2XsPrAtfezHpsRk0Nx+TFuS3hZk/To2N5kFPQ==}
|
| 367 |
+
cpu: [arm]
|
| 368 |
+
os: [linux]
|
| 369 |
+
|
| 370 |
+
'@rollup/[email protected]':
|
| 371 |
+
resolution: {integrity: sha512-vVDD+iPDPmJQ5nAQ5Tifq3ywdv60FartglFI8VOCK+hcU9aoG0qlQTsDJP97O5yiTaTqlneZWoARMcVC5nyUoQ==}
|
| 372 |
+
cpu: [arm64]
|
| 373 |
+
os: [linux]
|
| 374 |
+
|
| 375 |
+
'@rollup/[email protected]':
|
| 376 |
+
resolution: {integrity: sha512-0d0jx08fzDHCzXqrtCMEEyxKU0SvJrWmUjUDE2/KDQ2UDJql0tfiwYvEx1oHELClKO8CNdE+AGJj+RqXscZpdQ==}
|
| 377 |
+
cpu: [arm64]
|
| 378 |
+
os: [linux]
|
| 379 |
+
|
| 380 |
+
'@rollup/[email protected]':
|
| 381 |
+
resolution: {integrity: sha512-XBYu9oW9eKJadWn8M7hkTZsD4yG+RrsTrVEgyKwb4L72cpJjRbRboTG9Lg9fec8MxJp/cfTHAocg4mnismQR8A==}
|
| 382 |
+
cpu: [loong64]
|
| 383 |
+
os: [linux]
|
| 384 |
+
|
| 385 |
+
'@rollup/[email protected]':
|
| 386 |
+
resolution: {integrity: sha512-wJaRvcT17PoOK6Ggcfo3nouFlybHvARBS4jzT0PC/lg17fIJHcDS2fZz3sD+iA4nRlho2zE6OGbU0HvwATdokQ==}
|
| 387 |
+
cpu: [ppc64]
|
| 388 |
+
os: [linux]
|
| 389 |
+
|
| 390 |
+
'@rollup/[email protected]':
|
| 391 |
+
resolution: {integrity: sha512-GZ5bkMFteAGkcmh8x0Ok4LSa+L62Ez0tMsHPX6JtR0wl4Xc3bQcrFHDiR5DGLEDFtGrXih4Nd/UDaFqs968/wA==}
|
| 392 |
+
cpu: [riscv64]
|
| 393 |
+
os: [linux]
|
| 394 |
+
|
| 395 |
+
'@rollup/[email protected]':
|
| 396 |
+
resolution: {integrity: sha512-7CjPw6FflFsVOUfWOrVrREiV3IYXG4RzZ1ZQUaT3BtSK8YXN6x286o+sruPZJESIaPebYuFowmg54ZdrkVBYog==}
|
| 397 |
+
cpu: [riscv64]
|
| 398 |
+
os: [linux]
|
| 399 |
+
|
| 400 |
+
'@rollup/[email protected]':
|
| 401 |
+
resolution: {integrity: sha512-nmvnl0ZiuysltcB/cKjUh40Rx4FbSyueERDsl2FLvLYr6pCgSsvGr3SocUT84svSpmloS7f1DRWqtRha74Gi1w==}
|
| 402 |
+
cpu: [s390x]
|
| 403 |
+
os: [linux]
|
| 404 |
+
|
| 405 |
+
'@rollup/[email protected]':
|
| 406 |
+
resolution: {integrity: sha512-Cv+moII5C8RM6gZbR3cb21o6rquVDZrN2o81maROg1LFzBz2dZUwIQSxFA8GtGZ/F2KtsqQ2z3eFPBb6akvQNg==}
|
| 407 |
+
cpu: [x64]
|
| 408 |
+
os: [linux]
|
| 409 |
+
|
| 410 |
+
'@rollup/[email protected]':
|
| 411 |
+
resolution: {integrity: sha512-PHcMG8DZTM9RCIjp8QIfN0VYtX0TtBPnWOTRurFhoCDoi9zptUZL2k7pCs+5rgut7JAiUsYy+huyhVKPcmxoog==}
|
| 412 |
+
cpu: [x64]
|
| 413 |
+
os: [linux]
|
| 414 |
+
|
| 415 |
+
'@rollup/[email protected]':
|
| 416 |
+
resolution: {integrity: sha512-1SI/Rd47e8aQJeFWMDg16ET+fjvCcD/CzeaRmIEPmb05hx+3cCcwIF4ebUag4yTt/D1peE+Mgp0+Po3M358cAA==}
|
| 417 |
+
cpu: [arm64]
|
| 418 |
+
os: [win32]
|
| 419 |
+
|
| 420 |
+
'@rollup/[email protected]':
|
| 421 |
+
resolution: {integrity: sha512-JwOCYxmumFDfDhx4kNyz6kTVK3gWzBIvVdMNzQMRDubcoGRDniOOmo6DDNP42qwZx3Bp9/6vWJ+kNzNqXoHmeA==}
|
| 422 |
+
cpu: [ia32]
|
| 423 |
+
os: [win32]
|
| 424 |
+
|
| 425 |
+
'@rollup/[email protected]':
|
| 426 |
+
resolution: {integrity: sha512-IPMIfrfkG1GaEXi+JSsQEx8x9b4b+hRZXO7KYc2pKio3zO2/VDXDs6B9Ts/nnO+25Fk1tdAVtUn60HKKPPzDig==}
|
| 427 |
+
cpu: [x64]
|
| 428 |
+
os: [win32]
|
| 429 |
+
|
| 430 |
+
'@serialport/[email protected]':
|
| 431 |
+
resolution: {integrity: sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==}
|
| 432 |
+
engines: {node: '>=12.0.0'}
|
| 433 |
+
|
| 434 |
+
'@serialport/[email protected]':
|
| 435 |
+
resolution: {integrity: sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==}
|
| 436 |
+
engines: {node: '>=16.0.0'}
|
| 437 |
+
|
| 438 |
+
'@serialport/[email protected]':
|
| 439 |
+
resolution: {integrity: sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==}
|
| 440 |
+
engines: {node: ^12.22 || ^14.13 || >=16}
|
| 441 |
+
|
| 442 |
+
'@serialport/[email protected]':
|
| 443 |
+
resolution: {integrity: sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==}
|
| 444 |
+
engines: {node: '>=12.0.0'}
|
| 445 |
+
|
| 446 |
+
'@serialport/[email protected]':
|
| 447 |
+
resolution: {integrity: sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==}
|
| 448 |
+
engines: {node: '>=12.0.0'}
|
| 449 |
+
|
| 450 |
+
'@serialport/[email protected]':
|
| 451 |
+
resolution: {integrity: sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==}
|
| 452 |
+
engines: {node: '>=12.0.0'}
|
| 453 |
+
|
| 454 |
+
'@serialport/[email protected]':
|
| 455 |
+
resolution: {integrity: sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==}
|
| 456 |
+
engines: {node: '>=12.0.0'}
|
| 457 |
+
|
| 458 |
+
'@serialport/[email protected]':
|
| 459 |
+
resolution: {integrity: sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==}
|
| 460 |
+
engines: {node: '>=12.0.0'}
|
| 461 |
+
|
| 462 |
+
'@serialport/[email protected]':
|
| 463 |
+
resolution: {integrity: sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==}
|
| 464 |
+
engines: {node: '>=8.6.0'}
|
| 465 |
+
|
| 466 |
+
'@serialport/[email protected]':
|
| 467 |
+
resolution: {integrity: sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==}
|
| 468 |
+
engines: {node: '>=12.0.0'}
|
| 469 |
+
|
| 470 |
+
'@serialport/[email protected]':
|
| 471 |
+
resolution: {integrity: sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==}
|
| 472 |
+
engines: {node: '>=12.0.0'}
|
| 473 |
+
|
| 474 |
+
'@serialport/[email protected]':
|
| 475 |
+
resolution: {integrity: sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==}
|
| 476 |
+
engines: {node: '>=12.0.0'}
|
| 477 |
+
|
| 478 |
+
'@serialport/[email protected]':
|
| 479 |
+
resolution: {integrity: sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==}
|
| 480 |
+
engines: {node: '>=12.0.0'}
|
| 481 |
+
|
| 482 |
+
'@serialport/[email protected]':
|
| 483 |
+
resolution: {integrity: sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==}
|
| 484 |
+
engines: {node: '>=12.0.0'}
|
| 485 |
+
|
| 486 |
+
'@serialport/[email protected]':
|
| 487 |
+
resolution: {integrity: sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==}
|
| 488 |
+
engines: {node: '>=12.0.0'}
|
| 489 |
+
|
| 490 |
+
'@serialport/[email protected]':
|
| 491 |
+
resolution: {integrity: sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==}
|
| 492 |
+
engines: {node: '>=12.0.0'}
|
| 493 |
+
|
| 494 |
+
'@types/[email protected]':
|
| 495 |
+
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
| 496 |
+
|
| 497 |
+
'@types/[email protected]':
|
| 498 |
+
resolution: {integrity: sha512-WtCGHFXnVI8WHLxDAt5TbnCM4eSE+nI0QN2NJtwzcgMhht2eNz6V9evJrk+lwC8bCY8OWV5Ym8Jz7ZEyGnKnMA==}
|
| 499 |
+
|
| 500 | |
| 501 |
+
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
| 502 |
+
engines: {node: '>=8'}
|
| 503 |
+
|
| 504 | |
| 505 |
+
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
| 506 |
+
engines: {node: '>=6.0'}
|
| 507 |
+
peerDependencies:
|
| 508 |
+
supports-color: '*'
|
| 509 |
+
peerDependenciesMeta:
|
| 510 |
+
supports-color:
|
| 511 |
+
optional: true
|
| 512 |
+
|
| 513 | |
| 514 |
+
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
| 515 |
+
engines: {node: '>=6.0'}
|
| 516 |
+
peerDependencies:
|
| 517 |
+
supports-color: '*'
|
| 518 |
+
peerDependenciesMeta:
|
| 519 |
+
supports-color:
|
| 520 |
+
optional: true
|
| 521 |
+
|
| 522 | |
| 523 |
+
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
| 524 |
+
|
| 525 | |
| 526 |
+
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
|
| 527 |
+
engines: {node: '>=12'}
|
| 528 |
+
hasBin: true
|
| 529 |
+
|
| 530 | |
| 531 |
+
resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==}
|
| 532 |
+
engines: {node: '>=18'}
|
| 533 |
+
hasBin: true
|
| 534 |
+
|
| 535 | |
| 536 |
+
resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
|
| 537 |
+
peerDependencies:
|
| 538 |
+
picomatch: ^3 || ^4
|
| 539 |
+
peerDependenciesMeta:
|
| 540 |
+
picomatch:
|
| 541 |
+
optional: true
|
| 542 |
+
|
| 543 | |
| 544 |
+
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
| 545 |
+
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
| 546 |
+
os: [darwin]
|
| 547 |
+
|
| 548 | |
| 549 |
+
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
|
| 550 |
+
|
| 551 | |
| 552 |
+
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
| 553 |
+
|
| 554 | |
| 555 |
+
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
| 556 |
+
|
| 557 | |
| 558 |
+
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
| 559 |
+
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
| 560 |
+
hasBin: true
|
| 561 |
+
|
| 562 | |
| 563 |
+
resolution: {integrity: sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==}
|
| 564 |
+
|
| 565 | |
| 566 |
+
resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
|
| 567 |
+
hasBin: true
|
| 568 |
+
|
| 569 | |
| 570 |
+
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
|
| 571 |
+
|
| 572 | |
| 573 |
+
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
| 574 |
+
|
| 575 | |
| 576 |
+
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
| 577 |
+
engines: {node: '>=12'}
|
| 578 |
+
|
| 579 | |
| 580 |
+
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
| 581 |
+
engines: {node: ^10 || ^12 || >=14}
|
| 582 |
+
|
| 583 | |
| 584 |
+
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
| 585 |
+
|
| 586 | |
| 587 |
+
resolution: {integrity: sha512-ONmkT3Ud3IfW15nl7l4qAZko5/2iZ5ALVBDh02ZSZ5IGVLJSYkRcRa3iB58VyEIyoofs9m2xdVrm+lTi97+3pw==}
|
| 588 |
+
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
| 589 |
+
hasBin: true
|
| 590 |
+
|
| 591 | |
| 592 |
+
resolution: {integrity: sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==}
|
| 593 |
+
engines: {node: '>=16.0.0'}
|
| 594 |
+
|
| 595 | |
| 596 |
+
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
| 597 |
+
engines: {node: '>=0.10.0'}
|
| 598 |
+
|
| 599 | |
| 600 |
+
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
|
| 601 |
+
engines: {node: '>=12.0.0'}
|
| 602 |
+
|
| 603 | |
| 604 |
+
resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==}
|
| 605 |
+
engines: {node: '>=18.0.0'}
|
| 606 |
+
hasBin: true
|
| 607 |
+
|
| 608 | |
| 609 |
+
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
| 610 |
+
engines: {node: '>=14.17'}
|
| 611 |
+
hasBin: true
|
| 612 |
+
|
| 613 | |
| 614 |
+
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
| 615 |
+
|
| 616 | |
| 617 |
+
resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==}
|
| 618 |
+
engines: {node: ^18.0.0 || >=20.0.0}
|
| 619 |
+
hasBin: true
|
| 620 |
+
|
| 621 | |
| 622 |
+
resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==}
|
| 623 |
+
engines: {node: ^18.0.0 || >=20.0.0}
|
| 624 |
+
hasBin: true
|
| 625 |
+
peerDependencies:
|
| 626 |
+
'@types/node': ^18.0.0 || >=20.0.0
|
| 627 |
+
less: '*'
|
| 628 |
+
lightningcss: ^1.21.0
|
| 629 |
+
sass: '*'
|
| 630 |
+
sass-embedded: '*'
|
| 631 |
+
stylus: '*'
|
| 632 |
+
sugarss: '*'
|
| 633 |
+
terser: ^5.4.0
|
| 634 |
+
peerDependenciesMeta:
|
| 635 |
+
'@types/node':
|
| 636 |
+
optional: true
|
| 637 |
+
less:
|
| 638 |
+
optional: true
|
| 639 |
+
lightningcss:
|
| 640 |
+
optional: true
|
| 641 |
+
sass:
|
| 642 |
+
optional: true
|
| 643 |
+
sass-embedded:
|
| 644 |
+
optional: true
|
| 645 |
+
stylus:
|
| 646 |
+
optional: true
|
| 647 |
+
sugarss:
|
| 648 |
+
optional: true
|
| 649 |
+
terser:
|
| 650 |
+
optional: true
|
| 651 |
+
|
| 652 | |
| 653 |
+
resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==}
|
| 654 |
+
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
| 655 |
+
hasBin: true
|
| 656 |
+
peerDependencies:
|
| 657 |
+
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
|
| 658 |
+
jiti: '>=1.21.0'
|
| 659 |
+
less: '*'
|
| 660 |
+
lightningcss: ^1.21.0
|
| 661 |
+
sass: '*'
|
| 662 |
+
sass-embedded: '*'
|
| 663 |
+
stylus: '*'
|
| 664 |
+
sugarss: '*'
|
| 665 |
+
terser: ^5.16.0
|
| 666 |
+
tsx: ^4.8.1
|
| 667 |
+
yaml: ^2.4.2
|
| 668 |
+
peerDependenciesMeta:
|
| 669 |
+
'@types/node':
|
| 670 |
+
optional: true
|
| 671 |
+
jiti:
|
| 672 |
+
optional: true
|
| 673 |
+
less:
|
| 674 |
+
optional: true
|
| 675 |
+
lightningcss:
|
| 676 |
+
optional: true
|
| 677 |
+
sass:
|
| 678 |
+
optional: true
|
| 679 |
+
sass-embedded:
|
| 680 |
+
optional: true
|
| 681 |
+
stylus:
|
| 682 |
+
optional: true
|
| 683 |
+
sugarss:
|
| 684 |
+
optional: true
|
| 685 |
+
terser:
|
| 686 |
+
optional: true
|
| 687 |
+
tsx:
|
| 688 |
+
optional: true
|
| 689 |
+
yaml:
|
| 690 |
+
optional: true
|
| 691 |
+
|
| 692 |
+
snapshots:
|
| 693 |
+
|
| 694 |
+
'@esbuild/[email protected]':
|
| 695 |
+
optional: true
|
| 696 |
+
|
| 697 |
+
'@esbuild/[email protected]':
|
| 698 |
+
optional: true
|
| 699 |
+
|
| 700 |
+
'@esbuild/[email protected]':
|
| 701 |
+
optional: true
|
| 702 |
+
|
| 703 |
+
'@esbuild/[email protected]':
|
| 704 |
+
optional: true
|
| 705 |
+
|
| 706 |
+
'@esbuild/[email protected]':
|
| 707 |
+
optional: true
|
| 708 |
+
|
| 709 |
+
'@esbuild/[email protected]':
|
| 710 |
+
optional: true
|
| 711 |
+
|
| 712 |
+
'@esbuild/[email protected]':
|
| 713 |
+
optional: true
|
| 714 |
+
|
| 715 |
+
'@esbuild/[email protected]':
|
| 716 |
+
optional: true
|
| 717 |
+
|
| 718 |
+
'@esbuild/[email protected]':
|
| 719 |
+
optional: true
|
| 720 |
+
|
| 721 |
+
'@esbuild/[email protected]':
|
| 722 |
+
optional: true
|
| 723 |
+
|
| 724 |
+
'@esbuild/[email protected]':
|
| 725 |
+
optional: true
|
| 726 |
+
|
| 727 |
+
'@esbuild/[email protected]':
|
| 728 |
+
optional: true
|
| 729 |
+
|
| 730 |
+
'@esbuild/[email protected]':
|
| 731 |
+
optional: true
|
| 732 |
+
|
| 733 |
+
'@esbuild/[email protected]':
|
| 734 |
+
optional: true
|
| 735 |
+
|
| 736 |
+
'@esbuild/[email protected]':
|
| 737 |
+
optional: true
|
| 738 |
+
|
| 739 |
+
'@esbuild/[email protected]':
|
| 740 |
+
optional: true
|
| 741 |
+
|
| 742 |
+
'@esbuild/[email protected]':
|
| 743 |
+
optional: true
|
| 744 |
+
|
| 745 |
+
'@esbuild/[email protected]':
|
| 746 |
+
optional: true
|
| 747 |
+
|
| 748 |
+
'@esbuild/[email protected]':
|
| 749 |
+
optional: true
|
| 750 |
+
|
| 751 |
+
'@esbuild/[email protected]':
|
| 752 |
+
optional: true
|
| 753 |
+
|
| 754 |
+
'@esbuild/[email protected]':
|
| 755 |
+
optional: true
|
| 756 |
+
|
| 757 |
+
'@esbuild/[email protected]':
|
| 758 |
+
optional: true
|
| 759 |
+
|
| 760 |
+
'@esbuild/[email protected]':
|
| 761 |
+
optional: true
|
| 762 |
+
|
| 763 |
+
'@esbuild/[email protected]':
|
| 764 |
+
optional: true
|
| 765 |
+
|
| 766 |
+
'@esbuild/[email protected]':
|
| 767 |
+
optional: true
|
| 768 |
+
|
| 769 |
+
'@esbuild/[email protected]':
|
| 770 |
+
optional: true
|
| 771 |
+
|
| 772 |
+
'@esbuild/[email protected]':
|
| 773 |
+
optional: true
|
| 774 |
+
|
| 775 |
+
'@esbuild/[email protected]':
|
| 776 |
+
optional: true
|
| 777 |
+
|
| 778 |
+
'@esbuild/[email protected]':
|
| 779 |
+
optional: true
|
| 780 |
+
|
| 781 |
+
'@esbuild/[email protected]':
|
| 782 |
+
optional: true
|
| 783 |
+
|
| 784 |
+
'@esbuild/[email protected]':
|
| 785 |
+
optional: true
|
| 786 |
+
|
| 787 |
+
'@esbuild/[email protected]':
|
| 788 |
+
optional: true
|
| 789 |
+
|
| 790 |
+
'@esbuild/[email protected]':
|
| 791 |
+
optional: true
|
| 792 |
+
|
| 793 |
+
'@esbuild/[email protected]':
|
| 794 |
+
optional: true
|
| 795 |
+
|
| 796 |
+
'@esbuild/[email protected]':
|
| 797 |
+
optional: true
|
| 798 |
+
|
| 799 |
+
'@esbuild/[email protected]':
|
| 800 |
+
optional: true
|
| 801 |
+
|
| 802 |
+
'@esbuild/[email protected]':
|
| 803 |
+
optional: true
|
| 804 |
+
|
| 805 |
+
'@esbuild/[email protected]':
|
| 806 |
+
optional: true
|
| 807 |
+
|
| 808 |
+
'@esbuild/[email protected]':
|
| 809 |
+
optional: true
|
| 810 |
+
|
| 811 |
+
'@esbuild/[email protected]':
|
| 812 |
+
optional: true
|
| 813 |
+
|
| 814 |
+
'@esbuild/[email protected]':
|
| 815 |
+
optional: true
|
| 816 |
+
|
| 817 |
+
'@esbuild/[email protected]':
|
| 818 |
+
optional: true
|
| 819 |
+
|
| 820 |
+
'@esbuild/[email protected]':
|
| 821 |
+
optional: true
|
| 822 |
+
|
| 823 |
+
'@esbuild/[email protected]':
|
| 824 |
+
optional: true
|
| 825 |
+
|
| 826 |
+
'@esbuild/[email protected]':
|
| 827 |
+
optional: true
|
| 828 |
+
|
| 829 |
+
'@esbuild/[email protected]':
|
| 830 |
+
optional: true
|
| 831 |
+
|
| 832 |
+
'@esbuild/[email protected]':
|
| 833 |
+
optional: true
|
| 834 |
+
|
| 835 |
+
'@esbuild/[email protected]':
|
| 836 |
+
optional: true
|
| 837 |
+
|
| 838 |
+
'@esbuild/[email protected]':
|
| 839 |
+
optional: true
|
| 840 |
+
|
| 841 |
+
'@lerobot/node@file:../../packages/node([email protected])':
|
| 842 |
+
dependencies:
|
| 843 |
+
serialport: 12.0.0
|
| 844 |
+
typescript: 5.8.3
|
| 845 |
+
transitivePeerDependencies:
|
| 846 |
+
- supports-color
|
| 847 |
+
|
| 848 |
+
'@rollup/[email protected]':
|
| 849 |
+
optional: true
|
| 850 |
+
|
| 851 |
+
'@rollup/[email protected]':
|
| 852 |
+
optional: true
|
| 853 |
+
|
| 854 |
+
'@rollup/[email protected]':
|
| 855 |
+
optional: true
|
| 856 |
+
|
| 857 |
+
'@rollup/[email protected]':
|
| 858 |
+
optional: true
|
| 859 |
+
|
| 860 |
+
'@rollup/[email protected]':
|
| 861 |
+
optional: true
|
| 862 |
+
|
| 863 |
+
'@rollup/[email protected]':
|
| 864 |
+
optional: true
|
| 865 |
+
|
| 866 |
+
'@rollup/[email protected]':
|
| 867 |
+
optional: true
|
| 868 |
+
|
| 869 |
+
'@rollup/[email protected]':
|
| 870 |
+
optional: true
|
| 871 |
+
|
| 872 |
+
'@rollup/[email protected]':
|
| 873 |
+
optional: true
|
| 874 |
+
|
| 875 |
+
'@rollup/[email protected]':
|
| 876 |
+
optional: true
|
| 877 |
+
|
| 878 |
+
'@rollup/[email protected]':
|
| 879 |
+
optional: true
|
| 880 |
+
|
| 881 |
+
'@rollup/[email protected]':
|
| 882 |
+
optional: true
|
| 883 |
+
|
| 884 |
+
'@rollup/[email protected]':
|
| 885 |
+
optional: true
|
| 886 |
+
|
| 887 |
+
'@rollup/[email protected]':
|
| 888 |
+
optional: true
|
| 889 |
+
|
| 890 |
+
'@rollup/[email protected]':
|
| 891 |
+
optional: true
|
| 892 |
+
|
| 893 |
+
'@rollup/[email protected]':
|
| 894 |
+
optional: true
|
| 895 |
+
|
| 896 |
+
'@rollup/[email protected]':
|
| 897 |
+
optional: true
|
| 898 |
+
|
| 899 |
+
'@rollup/[email protected]':
|
| 900 |
+
optional: true
|
| 901 |
+
|
| 902 |
+
'@rollup/[email protected]':
|
| 903 |
+
optional: true
|
| 904 |
+
|
| 905 |
+
'@rollup/[email protected]':
|
| 906 |
+
optional: true
|
| 907 |
+
|
| 908 |
+
'@serialport/[email protected]':
|
| 909 |
+
dependencies:
|
| 910 |
+
'@serialport/bindings-interface': 1.2.2
|
| 911 |
+
debug: 4.3.4
|
| 912 |
+
transitivePeerDependencies:
|
| 913 |
+
- supports-color
|
| 914 |
+
|
| 915 |
+
'@serialport/[email protected]':
|
| 916 |
+
dependencies:
|
| 917 |
+
'@serialport/bindings-interface': 1.2.2
|
| 918 |
+
'@serialport/parser-readline': 11.0.0
|
| 919 |
+
debug: 4.3.4
|
| 920 |
+
node-addon-api: 7.0.0
|
| 921 |
+
node-gyp-build: 4.6.0
|
| 922 |
+
transitivePeerDependencies:
|
| 923 |
+
- supports-color
|
| 924 |
+
|
| 925 |
+
'@serialport/[email protected]': {}
|
| 926 |
+
|
| 927 |
+
'@serialport/[email protected]': {}
|
| 928 |
+
|
| 929 |
+
'@serialport/[email protected]': {}
|
| 930 |
+
|
| 931 |
+
'@serialport/[email protected]': {}
|
| 932 |
+
|
| 933 |
+
'@serialport/[email protected]': {}
|
| 934 |
+
|
| 935 |
+
'@serialport/[email protected]': {}
|
| 936 |
+
|
| 937 |
+
'@serialport/[email protected]': {}
|
| 938 |
+
|
| 939 |
+
'@serialport/[email protected]':
|
| 940 |
+
dependencies:
|
| 941 |
+
'@serialport/parser-delimiter': 11.0.0
|
| 942 |
+
|
| 943 |
+
'@serialport/[email protected]':
|
| 944 |
+
dependencies:
|
| 945 |
+
'@serialport/parser-delimiter': 12.0.0
|
| 946 |
+
|
| 947 |
+
'@serialport/[email protected]': {}
|
| 948 |
+
|
| 949 |
+
'@serialport/[email protected]': {}
|
| 950 |
+
|
| 951 |
+
'@serialport/[email protected]': {}
|
| 952 |
+
|
| 953 |
+
'@serialport/[email protected]': {}
|
| 954 |
+
|
| 955 |
+
'@serialport/[email protected]':
|
| 956 |
+
dependencies:
|
| 957 |
+
'@serialport/bindings-interface': 1.2.2
|
| 958 |
+
debug: 4.3.4
|
| 959 |
+
transitivePeerDependencies:
|
| 960 |
+
- supports-color
|
| 961 |
+
|
| 962 |
+
'@types/[email protected]': {}
|
| 963 |
+
|
| 964 |
+
'@types/[email protected]':
|
| 965 |
+
dependencies:
|
| 966 |
+
undici-types: 5.26.5
|
| 967 |
+
|
| 968 |
+
[email protected]: {}
|
| 969 |
+
|
| 970 | |
| 971 |
+
dependencies:
|
| 972 |
+
ms: 2.1.2
|
| 973 |
+
|
| 974 | |
| 975 |
+
dependencies:
|
| 976 |
+
ms: 2.1.3
|
| 977 |
+
|
| 978 |
+
[email protected]: {}
|
| 979 |
+
|
| 980 | |
| 981 |
+
optionalDependencies:
|
| 982 |
+
'@esbuild/aix-ppc64': 0.21.5
|
| 983 |
+
'@esbuild/android-arm': 0.21.5
|
| 984 |
+
'@esbuild/android-arm64': 0.21.5
|
| 985 |
+
'@esbuild/android-x64': 0.21.5
|
| 986 |
+
'@esbuild/darwin-arm64': 0.21.5
|
| 987 |
+
'@esbuild/darwin-x64': 0.21.5
|
| 988 |
+
'@esbuild/freebsd-arm64': 0.21.5
|
| 989 |
+
'@esbuild/freebsd-x64': 0.21.5
|
| 990 |
+
'@esbuild/linux-arm': 0.21.5
|
| 991 |
+
'@esbuild/linux-arm64': 0.21.5
|
| 992 |
+
'@esbuild/linux-ia32': 0.21.5
|
| 993 |
+
'@esbuild/linux-loong64': 0.21.5
|
| 994 |
+
'@esbuild/linux-mips64el': 0.21.5
|
| 995 |
+
'@esbuild/linux-ppc64': 0.21.5
|
| 996 |
+
'@esbuild/linux-riscv64': 0.21.5
|
| 997 |
+
'@esbuild/linux-s390x': 0.21.5
|
| 998 |
+
'@esbuild/linux-x64': 0.21.5
|
| 999 |
+
'@esbuild/netbsd-x64': 0.21.5
|
| 1000 |
+
'@esbuild/openbsd-x64': 0.21.5
|
| 1001 |
+
'@esbuild/sunos-x64': 0.21.5
|
| 1002 |
+
'@esbuild/win32-arm64': 0.21.5
|
| 1003 |
+
'@esbuild/win32-ia32': 0.21.5
|
| 1004 |
+
'@esbuild/win32-x64': 0.21.5
|
| 1005 |
+
|
| 1006 | |
| 1007 |
+
optionalDependencies:
|
| 1008 |
+
'@esbuild/aix-ppc64': 0.25.8
|
| 1009 |
+
'@esbuild/android-arm': 0.25.8
|
| 1010 |
+
'@esbuild/android-arm64': 0.25.8
|
| 1011 |
+
'@esbuild/android-x64': 0.25.8
|
| 1012 |
+
'@esbuild/darwin-arm64': 0.25.8
|
| 1013 |
+
'@esbuild/darwin-x64': 0.25.8
|
| 1014 |
+
'@esbuild/freebsd-arm64': 0.25.8
|
| 1015 |
+
'@esbuild/freebsd-x64': 0.25.8
|
| 1016 |
+
'@esbuild/linux-arm': 0.25.8
|
| 1017 |
+
'@esbuild/linux-arm64': 0.25.8
|
| 1018 |
+
'@esbuild/linux-ia32': 0.25.8
|
| 1019 |
+
'@esbuild/linux-loong64': 0.25.8
|
| 1020 |
+
'@esbuild/linux-mips64el': 0.25.8
|
| 1021 |
+
'@esbuild/linux-ppc64': 0.25.8
|
| 1022 |
+
'@esbuild/linux-riscv64': 0.25.8
|
| 1023 |
+
'@esbuild/linux-s390x': 0.25.8
|
| 1024 |
+
'@esbuild/linux-x64': 0.25.8
|
| 1025 |
+
'@esbuild/netbsd-arm64': 0.25.8
|
| 1026 |
+
'@esbuild/netbsd-x64': 0.25.8
|
| 1027 |
+
'@esbuild/openbsd-arm64': 0.25.8
|
| 1028 |
+
'@esbuild/openbsd-x64': 0.25.8
|
| 1029 |
+
'@esbuild/openharmony-arm64': 0.25.8
|
| 1030 |
+
'@esbuild/sunos-x64': 0.25.8
|
| 1031 |
+
'@esbuild/win32-arm64': 0.25.8
|
| 1032 |
+
'@esbuild/win32-ia32': 0.25.8
|
| 1033 |
+
'@esbuild/win32-x64': 0.25.8
|
| 1034 |
+
|
| 1035 | |
| 1036 |
+
optionalDependencies:
|
| 1037 |
+
picomatch: 4.0.3
|
| 1038 |
+
|
| 1039 | |
| 1040 |
+
optional: true
|
| 1041 |
+
|
| 1042 | |
| 1043 |
+
dependencies:
|
| 1044 |
+
resolve-pkg-maps: 1.0.0
|
| 1045 |
+
optional: true
|
| 1046 |
+
|
| 1047 |
+
[email protected]: {}
|
| 1048 |
+
|
| 1049 |
+
[email protected]: {}
|
| 1050 |
+
|
| 1051 |
+
[email protected]: {}
|
| 1052 |
+
|
| 1053 |
+
[email protected]: {}
|
| 1054 |
+
|
| 1055 |
+
[email protected]: {}
|
| 1056 |
+
|
| 1057 |
+
[email protected]: {}
|
| 1058 |
+
|
| 1059 |
+
[email protected]: {}
|
| 1060 |
+
|
| 1061 |
+
[email protected]: {}
|
| 1062 |
+
|
| 1063 | |
| 1064 |
+
dependencies:
|
| 1065 |
+
nanoid: 3.3.11
|
| 1066 |
+
picocolors: 1.1.1
|
| 1067 |
+
source-map-js: 1.2.1
|
| 1068 |
+
|
| 1069 | |
| 1070 |
+
optional: true
|
| 1071 |
+
|
| 1072 | |
| 1073 |
+
dependencies:
|
| 1074 |
+
'@types/estree': 1.0.8
|
| 1075 |
+
optionalDependencies:
|
| 1076 |
+
'@rollup/rollup-android-arm-eabi': 4.46.0
|
| 1077 |
+
'@rollup/rollup-android-arm64': 4.46.0
|
| 1078 |
+
'@rollup/rollup-darwin-arm64': 4.46.0
|
| 1079 |
+
'@rollup/rollup-darwin-x64': 4.46.0
|
| 1080 |
+
'@rollup/rollup-freebsd-arm64': 4.46.0
|
| 1081 |
+
'@rollup/rollup-freebsd-x64': 4.46.0
|
| 1082 |
+
'@rollup/rollup-linux-arm-gnueabihf': 4.46.0
|
| 1083 |
+
'@rollup/rollup-linux-arm-musleabihf': 4.46.0
|
| 1084 |
+
'@rollup/rollup-linux-arm64-gnu': 4.46.0
|
| 1085 |
+
'@rollup/rollup-linux-arm64-musl': 4.46.0
|
| 1086 |
+
'@rollup/rollup-linux-loongarch64-gnu': 4.46.0
|
| 1087 |
+
'@rollup/rollup-linux-ppc64-gnu': 4.46.0
|
| 1088 |
+
'@rollup/rollup-linux-riscv64-gnu': 4.46.0
|
| 1089 |
+
'@rollup/rollup-linux-riscv64-musl': 4.46.0
|
| 1090 |
+
'@rollup/rollup-linux-s390x-gnu': 4.46.0
|
| 1091 |
+
'@rollup/rollup-linux-x64-gnu': 4.46.0
|
| 1092 |
+
'@rollup/rollup-linux-x64-musl': 4.46.0
|
| 1093 |
+
'@rollup/rollup-win32-arm64-msvc': 4.46.0
|
| 1094 |
+
'@rollup/rollup-win32-ia32-msvc': 4.46.0
|
| 1095 |
+
'@rollup/rollup-win32-x64-msvc': 4.46.0
|
| 1096 |
+
fsevents: 2.3.3
|
| 1097 |
+
|
| 1098 | |
| 1099 |
+
dependencies:
|
| 1100 |
+
'@serialport/binding-mock': 10.2.2
|
| 1101 |
+
'@serialport/bindings-cpp': 12.0.1
|
| 1102 |
+
'@serialport/parser-byte-length': 12.0.0
|
| 1103 |
+
'@serialport/parser-cctalk': 12.0.0
|
| 1104 |
+
'@serialport/parser-delimiter': 12.0.0
|
| 1105 |
+
'@serialport/parser-inter-byte-timeout': 12.0.0
|
| 1106 |
+
'@serialport/parser-packet-length': 12.0.0
|
| 1107 |
+
'@serialport/parser-readline': 12.0.0
|
| 1108 |
+
'@serialport/parser-ready': 12.0.0
|
| 1109 |
+
'@serialport/parser-regex': 12.0.0
|
| 1110 |
+
'@serialport/parser-slip-encoder': 12.0.0
|
| 1111 |
+
'@serialport/parser-spacepacket': 12.0.0
|
| 1112 |
+
'@serialport/stream': 12.0.0
|
| 1113 |
+
debug: 4.3.4
|
| 1114 |
+
transitivePeerDependencies:
|
| 1115 |
+
- supports-color
|
| 1116 |
+
|
| 1117 |
+
[email protected]: {}
|
| 1118 |
+
|
| 1119 | |
| 1120 |
+
dependencies:
|
| 1121 |
+
fdir: 6.4.6([email protected])
|
| 1122 |
+
picomatch: 4.0.3
|
| 1123 |
+
|
| 1124 | |
| 1125 |
+
dependencies:
|
| 1126 |
+
esbuild: 0.25.8
|
| 1127 |
+
get-tsconfig: 4.10.1
|
| 1128 |
+
optionalDependencies:
|
| 1129 |
+
fsevents: 2.3.3
|
| 1130 |
+
optional: true
|
| 1131 |
+
|
| 1132 |
+
[email protected]: {}
|
| 1133 |
+
|
| 1134 |
+
[email protected]: {}
|
| 1135 |
+
|
| 1136 |
+
[email protected](@types/[email protected]):
|
| 1137 |
+
dependencies:
|
| 1138 |
+
cac: 6.7.14
|
| 1139 |
+
debug: 4.4.1
|
| 1140 |
+
es-module-lexer: 1.7.0
|
| 1141 |
+
pathe: 1.1.2
|
| 1142 |
+
vite: 5.4.19(@types/[email protected])
|
| 1143 |
+
transitivePeerDependencies:
|
| 1144 |
+
- '@types/node'
|
| 1145 |
+
- less
|
| 1146 |
+
- lightningcss
|
| 1147 |
+
- sass
|
| 1148 |
+
- sass-embedded
|
| 1149 |
+
- stylus
|
| 1150 |
+
- sugarss
|
| 1151 |
+
- supports-color
|
| 1152 |
+
- terser
|
| 1153 |
+
|
| 1154 |
+
[email protected](@types/[email protected]):
|
| 1155 |
+
dependencies:
|
| 1156 |
+
esbuild: 0.21.5
|
| 1157 |
+
postcss: 8.5.6
|
| 1158 |
+
rollup: 4.46.0
|
| 1159 |
+
optionalDependencies:
|
| 1160 |
+
'@types/node': 18.19.120
|
| 1161 |
+
fsevents: 2.3.3
|
| 1162 |
+
|
| 1163 |
+
[email protected](@types/[email protected])([email protected]):
|
| 1164 |
+
dependencies:
|
| 1165 |
+
esbuild: 0.25.8
|
| 1166 |
+
fdir: 6.4.6([email protected])
|
| 1167 |
+
picomatch: 4.0.3
|
| 1168 |
+
postcss: 8.5.6
|
| 1169 |
+
rollup: 4.46.0
|
| 1170 |
+
tinyglobby: 0.2.14
|
| 1171 |
+
optionalDependencies:
|
| 1172 |
+
'@types/node': 18.19.120
|
| 1173 |
+
fsevents: 2.3.3
|
| 1174 |
+
tsx: 4.20.3
|
examples/node-quick-start/src/demo-calibrate.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Calibration Demo
|
| 3 |
+
*
|
| 4 |
+
* Demonstrates robot motor calibration with live feedback
|
| 5 |
+
*/
|
| 6 |
+
|
| 7 |
+
import { findPort, connectPort, releaseMotors, calibrate } from "@lerobot/node";
|
| 8 |
+
import type { RobotConnection, DiscoveredPort } from "@lerobot/node";
|
| 9 |
+
|
| 10 |
+
async function demoCalibrate() {
|
| 11 |
+
console.log("🎯 Calibration Demo");
|
| 12 |
+
console.log("===================\n");
|
| 13 |
+
|
| 14 |
+
try {
|
| 15 |
+
// Step 1: Find available robot ports
|
| 16 |
+
console.log("📡 Looking for connected robots...");
|
| 17 |
+
const findProcess = await findPort();
|
| 18 |
+
const discoveredPorts = await findProcess.result;
|
| 19 |
+
|
| 20 |
+
if (discoveredPorts.length === 0) {
|
| 21 |
+
throw new Error("No robots found. Please connect your robot first.");
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
console.log(`✅ Found robot on ${discoveredPorts[0].path}`);
|
| 25 |
+
|
| 26 |
+
// Step 2: Connect to robot
|
| 27 |
+
console.log("🔌 Connecting to robot...");
|
| 28 |
+
const robot = await connectPort(
|
| 29 |
+
discoveredPorts[0].path,
|
| 30 |
+
"so100_follower",
|
| 31 |
+
"calibration_demo"
|
| 32 |
+
);
|
| 33 |
+
console.log(`✅ Connected: ${robot.robotType} (ID: ${robot.robotId})\n`);
|
| 34 |
+
|
| 35 |
+
// Step 3: Release motors
|
| 36 |
+
console.log("🔓 Releasing motors for calibration setup...");
|
| 37 |
+
await releaseMotors(robot);
|
| 38 |
+
console.log("✅ Motors released - robot can now be moved by hand");
|
| 39 |
+
|
| 40 |
+
console.log("\n📍 Move robot to your preferred starting position...");
|
| 41 |
+
console.log("Press any key to continue...");
|
| 42 |
+
|
| 43 |
+
// Simple key press handler without readline conflicts
|
| 44 |
+
process.stdin.setRawMode(true);
|
| 45 |
+
process.stdin.resume();
|
| 46 |
+
|
| 47 |
+
await new Promise<void>((resolve) => {
|
| 48 |
+
const onData = () => {
|
| 49 |
+
process.stdin.setRawMode(false);
|
| 50 |
+
process.stdin.pause();
|
| 51 |
+
process.stdin.removeListener("data", onData);
|
| 52 |
+
resolve();
|
| 53 |
+
};
|
| 54 |
+
process.stdin.once("data", onData);
|
| 55 |
+
});
|
| 56 |
+
|
| 57 |
+
// Step 4: Calibration process
|
| 58 |
+
console.log("\n🎯 Starting calibration process...");
|
| 59 |
+
console.log("This will:");
|
| 60 |
+
console.log("1. Set homing offsets (center positions)");
|
| 61 |
+
console.log("2. Record range of motion for each motor");
|
| 62 |
+
console.log("3. Write position limits to robot hardware");
|
| 63 |
+
console.log("4. Save calibration data for future use\n");
|
| 64 |
+
|
| 65 |
+
const calibrationProcess = await calibrate({
|
| 66 |
+
robot,
|
| 67 |
+
onProgress: (message) => {
|
| 68 |
+
console.log(`📊 ${message}`);
|
| 69 |
+
},
|
| 70 |
+
onLiveUpdate: (data) => {
|
| 71 |
+
// Display real-time motor positions and ranges
|
| 72 |
+
const updates = Object.entries(data).map(([name, info]) => {
|
| 73 |
+
const range = info.max - info.min;
|
| 74 |
+
return `${name}: ${info.current} [${info.min}→${info.max}] (range: ${range})`;
|
| 75 |
+
});
|
| 76 |
+
|
| 77 |
+
console.clear();
|
| 78 |
+
console.log("🔄 Live Calibration Data:");
|
| 79 |
+
console.log("========================");
|
| 80 |
+
updates.forEach((update) => console.log(` ${update}`));
|
| 81 |
+
console.log("\n💡 Move each motor through its full range of motion");
|
| 82 |
+
console.log(" Press Enter to complete calibration...");
|
| 83 |
+
},
|
| 84 |
+
});
|
| 85 |
+
|
| 86 |
+
// Wait for calibration to complete (it handles user input internally)
|
| 87 |
+
const calibrationData = await calibrationProcess.result;
|
| 88 |
+
|
| 89 |
+
console.log("\n✅ Calibration completed successfully!");
|
| 90 |
+
|
| 91 |
+
// Display detailed results
|
| 92 |
+
console.log("\n📋 Detailed Calibration Results:");
|
| 93 |
+
console.log("=================================");
|
| 94 |
+
Object.entries(calibrationData).forEach(([motorName, config]) => {
|
| 95 |
+
const range = config.range_max - config.range_min;
|
| 96 |
+
console.log(`${motorName}:`);
|
| 97 |
+
console.log(` Motor ID: ${config.id}`);
|
| 98 |
+
console.log(` Drive Mode: ${config.drive_mode}`);
|
| 99 |
+
console.log(` Homing Offset: ${config.homing_offset}`);
|
| 100 |
+
console.log(
|
| 101 |
+
` Range: ${config.range_min} → ${config.range_max} (${range} steps)`
|
| 102 |
+
);
|
| 103 |
+
console.log(` Degrees: ~${((range / 4096) * 360).toFixed(1)}°\n`);
|
| 104 |
+
});
|
| 105 |
+
|
| 106 |
+
console.log("💾 Calibration saved to HuggingFace cache directory");
|
| 107 |
+
console.log("🔄 This file is compatible with Python lerobot");
|
| 108 |
+
|
| 109 |
+
console.log("\n🎉 Calibration demo completed!");
|
| 110 |
+
console.log("💡 You can now use this calibration data for teleoperation");
|
| 111 |
+
|
| 112 |
+
// Ensure process can exit cleanly
|
| 113 |
+
process.exit(0);
|
| 114 |
+
} catch (error) {
|
| 115 |
+
console.error("\n❌ Calibration failed:", error.message);
|
| 116 |
+
console.log("\n🔧 Troubleshooting:");
|
| 117 |
+
console.log("- Ensure robot is connected and responsive");
|
| 118 |
+
console.log("- Check that motors can move freely during calibration");
|
| 119 |
+
console.log("- Avoid forcing motors past their mechanical limits");
|
| 120 |
+
console.log("- Try restarting the robot if motors become unresponsive");
|
| 121 |
+
process.exit(1);
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
demoCalibrate();
|
examples/node-quick-start/src/demo-find-port.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Port Discovery Demo
|
| 3 |
+
*
|
| 4 |
+
* Demonstrates how to find and connect to robot hardware programmatically
|
| 5 |
+
*/
|
| 6 |
+
|
| 7 |
+
import { findPort, connectPort } from "@lerobot/node";
|
| 8 |
+
import type { RobotConnection, DiscoveredPort } from "@lerobot/node";
|
| 9 |
+
|
| 10 |
+
async function demoFindPort() {
|
| 11 |
+
console.log("🔍 Port Discovery Demo");
|
| 12 |
+
console.log("======================\n");
|
| 13 |
+
|
| 14 |
+
try {
|
| 15 |
+
// Demo 1: Basic port discovery
|
| 16 |
+
console.log("📋 Demo 1: Basic robot discovery");
|
| 17 |
+
console.log("Looking for connected robots...\n");
|
| 18 |
+
|
| 19 |
+
const findProcess = await findPort({
|
| 20 |
+
onMessage: (message) => console.log(` 📡 ${message}`),
|
| 21 |
+
});
|
| 22 |
+
const discoveredPorts = await findProcess.result;
|
| 23 |
+
|
| 24 |
+
if (discoveredPorts.length === 0) {
|
| 25 |
+
console.log("❌ No robots found.");
|
| 26 |
+
console.log("\n🔧 Make sure your robot is:");
|
| 27 |
+
console.log(" - Connected via USB");
|
| 28 |
+
console.log(" - Powered on");
|
| 29 |
+
console.log(" - Using a working USB cable");
|
| 30 |
+
return;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
console.log(`\n✅ Found ${discoveredPorts.length} robot port(s):`);
|
| 34 |
+
discoveredPorts.forEach((port, index) => {
|
| 35 |
+
console.log(` ${index + 1}. ${port.robotType} on ${port.path}`);
|
| 36 |
+
console.log(` Port: ${port.path}`);
|
| 37 |
+
console.log(` Type: ${port.robotType}`);
|
| 38 |
+
});
|
| 39 |
+
|
| 40 |
+
// Demo 2: Connect to discovered robot
|
| 41 |
+
console.log("\n🔌 Demo 2: Connecting to discovered robot");
|
| 42 |
+
|
| 43 |
+
const robot = await connectPort(
|
| 44 |
+
discoveredPorts[0].path,
|
| 45 |
+
"so100_follower",
|
| 46 |
+
"demo_robot"
|
| 47 |
+
);
|
| 48 |
+
|
| 49 |
+
console.log(
|
| 50 |
+
`✅ Connected to robot: ${robot.robotType} (ID: ${robot.robotId})`
|
| 51 |
+
);
|
| 52 |
+
console.log(` Port: ${robot.port.path}`);
|
| 53 |
+
console.log(` Connected: ${robot.isConnected ? "✅" : "❌"}`);
|
| 54 |
+
console.log(` Baudrate: ${robot.port.baudRate}`);
|
| 55 |
+
|
| 56 |
+
// Demo 3: Connection details
|
| 57 |
+
console.log("\n🔌 Demo 3: Connection details");
|
| 58 |
+
console.log("Robot connection properties:");
|
| 59 |
+
console.log(` Name: ${robot.name}`);
|
| 60 |
+
console.log(` Type: ${robot.robotType}`);
|
| 61 |
+
console.log(` ID: ${robot.robotId}`);
|
| 62 |
+
console.log(` Port: ${robot.port.path}`);
|
| 63 |
+
console.log(` Serial: ${robot.serialNumber}`);
|
| 64 |
+
console.log(` Connected: ${robot.isConnected ? "✅" : "❌"}`);
|
| 65 |
+
|
| 66 |
+
// Demo 4: Silent discovery (no progress messages)
|
| 67 |
+
console.log("\n🤫 Demo 4: Silent discovery");
|
| 68 |
+
console.log("Finding robots without progress messages...");
|
| 69 |
+
|
| 70 |
+
const silentProcess = await findPort(); // No onMessage callback
|
| 71 |
+
const silentRobots = await silentProcess.result;
|
| 72 |
+
|
| 73 |
+
console.log(`Found ${silentRobots.length} robot(s) silently`);
|
| 74 |
+
|
| 75 |
+
console.log("\n🎉 Port discovery demo completed!");
|
| 76 |
+
console.log("\nℹ️ Note: For interactive port discovery, use the CLI:");
|
| 77 |
+
console.log(" npx lerobot find-port");
|
| 78 |
+
} catch (error) {
|
| 79 |
+
console.error("\n❌ Port discovery failed:", error.message);
|
| 80 |
+
console.log("\n🔧 Troubleshooting:");
|
| 81 |
+
console.log("- Check USB connections");
|
| 82 |
+
console.log("- Verify robot is powered on");
|
| 83 |
+
console.log("- Try different USB ports/cables");
|
| 84 |
+
console.log("- On Linux: Check serial port permissions");
|
| 85 |
+
console.log("- For interactive port discovery, use: npx lerobot find-port");
|
| 86 |
+
process.exit(1);
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
demoFindPort();
|
examples/node-quick-start/src/demo-teleoperate.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Teleoperation Demo
|
| 3 |
+
*
|
| 4 |
+
* Demonstrates different ways to control robot motors
|
| 5 |
+
*/
|
| 6 |
+
|
| 7 |
+
import { findPort, connectPort, teleoperate } from "@lerobot/node";
|
| 8 |
+
import type { RobotConnection, DiscoveredPort } from "@lerobot/node";
|
| 9 |
+
import { createInterface } from "readline";
|
| 10 |
+
|
| 11 |
+
function askUser(question: string): Promise<string> {
|
| 12 |
+
const rl = createInterface({
|
| 13 |
+
input: process.stdin,
|
| 14 |
+
output: process.stdout,
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
return new Promise((resolve) => {
|
| 18 |
+
rl.question(question, (answer) => {
|
| 19 |
+
rl.close();
|
| 20 |
+
resolve(answer.trim());
|
| 21 |
+
});
|
| 22 |
+
});
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
async function demoTeleoperate() {
|
| 26 |
+
console.log("🎮 Teleoperation Demo");
|
| 27 |
+
console.log("=====================\n");
|
| 28 |
+
|
| 29 |
+
try {
|
| 30 |
+
// Step 1: Find available robot ports
|
| 31 |
+
console.log("📡 Looking for connected robots...");
|
| 32 |
+
const findProcess = await findPort();
|
| 33 |
+
const discoveredPorts = await findProcess.result;
|
| 34 |
+
|
| 35 |
+
if (discoveredPorts.length === 0) {
|
| 36 |
+
throw new Error("No robots found. Please connect your robot first.");
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
console.log(`✅ Found robot on ${discoveredPorts[0].path}`);
|
| 40 |
+
|
| 41 |
+
// Step 2: Connect to robot
|
| 42 |
+
console.log("🔌 Connecting to robot...");
|
| 43 |
+
const robot = await connectPort(
|
| 44 |
+
discoveredPorts[0].path,
|
| 45 |
+
"so100_follower",
|
| 46 |
+
"teleop_demo"
|
| 47 |
+
);
|
| 48 |
+
console.log(`✅ Connected: ${robot.robotType} (ID: ${robot.robotId})\n`);
|
| 49 |
+
|
| 50 |
+
// Step 3: Choose teleoperation mode
|
| 51 |
+
console.log("🎯 Choose teleoperation mode:");
|
| 52 |
+
console.log("1. Keyboard Control (interactive)");
|
| 53 |
+
console.log("2. Direct Control (programmatic)");
|
| 54 |
+
|
| 55 |
+
const mode = await askUser("Enter choice (1 or 2): ");
|
| 56 |
+
|
| 57 |
+
if (mode === "1") {
|
| 58 |
+
// Keyboard teleoperation demo
|
| 59 |
+
await demoKeyboardControl(robot);
|
| 60 |
+
} else if (mode === "2") {
|
| 61 |
+
// Direct control demo
|
| 62 |
+
await demoDirectControl(robot);
|
| 63 |
+
} else {
|
| 64 |
+
console.log("Invalid choice. Defaulting to keyboard control...");
|
| 65 |
+
await demoKeyboardControl(robot);
|
| 66 |
+
}
|
| 67 |
+
} catch (error) {
|
| 68 |
+
console.error("\n❌ Teleoperation failed:", error.message);
|
| 69 |
+
console.log("\n🔧 Troubleshooting:");
|
| 70 |
+
console.log("- Ensure robot is calibrated first");
|
| 71 |
+
console.log("- Check that robot is connected and responsive");
|
| 72 |
+
console.log("- Verify calibration data exists");
|
| 73 |
+
console.log("- Try smaller step sizes if movements are too large");
|
| 74 |
+
process.exit(1);
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
async function demoKeyboardControl(robot: RobotConnection) {
|
| 79 |
+
console.log("\n⌨️ Keyboard Control Demo");
|
| 80 |
+
console.log("=========================");
|
| 81 |
+
console.log("\n🎮 Robot Controls:");
|
| 82 |
+
console.log(" Arrow Keys: Shoulder pan/lift");
|
| 83 |
+
console.log(" W/S: Elbow flex/extend");
|
| 84 |
+
console.log(" A/D: Wrist down/up");
|
| 85 |
+
console.log(" Q/E: Wrist roll left/right");
|
| 86 |
+
console.log(" O/C: Gripper open/close");
|
| 87 |
+
console.log(" ESC: Emergency stop");
|
| 88 |
+
console.log(" Ctrl+C: Exit demo\n");
|
| 89 |
+
|
| 90 |
+
const teleop = await teleoperate({
|
| 91 |
+
robot,
|
| 92 |
+
teleop: {
|
| 93 |
+
type: "keyboard",
|
| 94 |
+
// Using optimized defaults for smooth control
|
| 95 |
+
},
|
| 96 |
+
onStateUpdate: (state) => {
|
| 97 |
+
if (state.isActive) {
|
| 98 |
+
// Show live motor positions
|
| 99 |
+
const motorInfo = state.motorConfigs
|
| 100 |
+
.map((motor) => {
|
| 101 |
+
const pos = Math.round(motor.currentPosition);
|
| 102 |
+
const percent = (
|
| 103 |
+
((pos - motor.minPosition) /
|
| 104 |
+
(motor.maxPosition - motor.minPosition)) *
|
| 105 |
+
100
|
| 106 |
+
).toFixed(0);
|
| 107 |
+
return `${motor.name}:${pos}(${percent}%)`;
|
| 108 |
+
})
|
| 109 |
+
.join(" ");
|
| 110 |
+
process.stdout.write(`\r🤖 ${motorInfo}`);
|
| 111 |
+
}
|
| 112 |
+
},
|
| 113 |
+
});
|
| 114 |
+
|
| 115 |
+
// Start keyboard control
|
| 116 |
+
teleop.start();
|
| 117 |
+
console.log("✅ Keyboard control active!");
|
| 118 |
+
console.log("💡 Move robot with keyboard, press Ctrl+C to exit");
|
| 119 |
+
|
| 120 |
+
// Handle graceful shutdown
|
| 121 |
+
process.on("SIGINT", async () => {
|
| 122 |
+
console.log("\n🛑 Stopping keyboard control...");
|
| 123 |
+
teleop.stop();
|
| 124 |
+
await teleop.disconnect();
|
| 125 |
+
console.log("✅ Keyboard control demo completed!");
|
| 126 |
+
process.exit(0);
|
| 127 |
+
});
|
| 128 |
+
|
| 129 |
+
// Keep demo running
|
| 130 |
+
await new Promise(() => {}); // Keep alive
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
async function demoDirectControl(robot: RobotConnection) {
|
| 134 |
+
console.log("\n🎯 Direct Control Demo");
|
| 135 |
+
console.log("======================");
|
| 136 |
+
console.log("This demonstrates programmatic robot control\n");
|
| 137 |
+
|
| 138 |
+
const teleop = await teleoperate({
|
| 139 |
+
robot,
|
| 140 |
+
teleop: { type: "direct" },
|
| 141 |
+
onStateUpdate: (state) => {
|
| 142 |
+
const motorInfo = state.motorConfigs
|
| 143 |
+
.map((motor) => `${motor.name}:${Math.round(motor.currentPosition)}`)
|
| 144 |
+
.join(" ");
|
| 145 |
+
console.log(`🤖 ${motorInfo}`);
|
| 146 |
+
},
|
| 147 |
+
});
|
| 148 |
+
|
| 149 |
+
teleop.start();
|
| 150 |
+
|
| 151 |
+
// Get direct control interface
|
| 152 |
+
const directController = teleop.teleoperator as any;
|
| 153 |
+
|
| 154 |
+
console.log("🎬 Running automated movement sequence...\n");
|
| 155 |
+
|
| 156 |
+
try {
|
| 157 |
+
// Demo sequence: move different motors
|
| 158 |
+
const movements = [
|
| 159 |
+
{
|
| 160 |
+
motor: "shoulder_pan",
|
| 161 |
+
position: 2048,
|
| 162 |
+
description: "Center shoulder pan",
|
| 163 |
+
},
|
| 164 |
+
{ motor: "shoulder_lift", position: 1500, description: "Lift shoulder" },
|
| 165 |
+
{ motor: "elbow_flex", position: 2500, description: "Flex elbow" },
|
| 166 |
+
{ motor: "wrist_flex", position: 2000, description: "Adjust wrist" },
|
| 167 |
+
{ motor: "wrist_roll", position: 2048, description: "Center wrist roll" },
|
| 168 |
+
{ motor: "gripper", position: 1800, description: "Adjust gripper" },
|
| 169 |
+
];
|
| 170 |
+
|
| 171 |
+
for (const movement of movements) {
|
| 172 |
+
console.log(`🎯 ${movement.description}...`);
|
| 173 |
+
await directController.moveMotor(movement.motor, movement.position);
|
| 174 |
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
console.log("\n🎉 Movement sequence completed!");
|
| 178 |
+
|
| 179 |
+
// Demo multi-motor movement
|
| 180 |
+
console.log("\n🎭 Demonstrating simultaneous multi-motor movement...");
|
| 181 |
+
const results = await directController.moveMotors({
|
| 182 |
+
shoulder_pan: 2048,
|
| 183 |
+
shoulder_lift: 2048,
|
| 184 |
+
elbow_flex: 2048,
|
| 185 |
+
wrist_flex: 2048,
|
| 186 |
+
});
|
| 187 |
+
|
| 188 |
+
console.log("📊 Movement results:");
|
| 189 |
+
Object.entries(results).forEach(([motor, success]) => {
|
| 190 |
+
console.log(` ${motor}: ${success ? "✅" : "❌"}`);
|
| 191 |
+
});
|
| 192 |
+
|
| 193 |
+
// Show current positions
|
| 194 |
+
const positions = directController.getCurrentPositions();
|
| 195 |
+
console.log("\n📍 Final positions:");
|
| 196 |
+
Object.entries(positions).forEach(([motor, position]) => {
|
| 197 |
+
console.log(` ${motor}: ${Math.round(position as number)}`);
|
| 198 |
+
});
|
| 199 |
+
} finally {
|
| 200 |
+
console.log("\n🛑 Stopping direct control...");
|
| 201 |
+
teleop.stop();
|
| 202 |
+
await teleop.disconnect();
|
| 203 |
+
console.log("✅ Direct control demo completed!");
|
| 204 |
+
}
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
demoTeleoperate();
|
examples/node-quick-start/src/main.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Node.js Quick Start Example - Complete Workflow
|
| 3 |
+
*
|
| 4 |
+
* This example demonstrates the full robot control workflow:
|
| 5 |
+
* 1. Find and connect to robot hardware
|
| 6 |
+
* 2. Release motors for manual positioning
|
| 7 |
+
* 3. Calibrate motor ranges and homing positions
|
| 8 |
+
* 4. Control robot with keyboard teleoperation
|
| 9 |
+
*/
|
| 10 |
+
|
| 11 |
+
import {
|
| 12 |
+
findPort,
|
| 13 |
+
connectPort,
|
| 14 |
+
releaseMotors,
|
| 15 |
+
calibrate,
|
| 16 |
+
teleoperate,
|
| 17 |
+
} from "@lerobot/node";
|
| 18 |
+
import type { RobotConnection, DiscoveredPort } from "@lerobot/node";
|
| 19 |
+
|
| 20 |
+
// Utility for user confirmation
|
| 21 |
+
import { createInterface } from "readline";
|
| 22 |
+
|
| 23 |
+
function askUser(question: string): Promise<string> {
|
| 24 |
+
const rl = createInterface({
|
| 25 |
+
input: process.stdin,
|
| 26 |
+
output: process.stdout,
|
| 27 |
+
});
|
| 28 |
+
|
| 29 |
+
return new Promise((resolve) => {
|
| 30 |
+
rl.question(question, (answer) => {
|
| 31 |
+
rl.close();
|
| 32 |
+
resolve(answer.trim());
|
| 33 |
+
});
|
| 34 |
+
});
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
async function quickStartDemo() {
|
| 38 |
+
console.log("🤖 LeRobot.js Node.js Quick Start Demo");
|
| 39 |
+
console.log("=====================================\n");
|
| 40 |
+
|
| 41 |
+
try {
|
| 42 |
+
// Step 1: Find available robot ports
|
| 43 |
+
console.log("📡 Step 1: Looking for connected robots...");
|
| 44 |
+
const findProcess = await findPort();
|
| 45 |
+
const discoveredPorts = await findProcess.result;
|
| 46 |
+
|
| 47 |
+
if (discoveredPorts.length === 0) {
|
| 48 |
+
throw new Error("No robots found. Please check your connections.");
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
console.log(`✅ Found robot on ${discoveredPorts[0].path}`);
|
| 52 |
+
|
| 53 |
+
// Step 2: Connect to the first robot found
|
| 54 |
+
console.log("🔌 Step 2: Connecting to robot...");
|
| 55 |
+
const robot = await connectPort(
|
| 56 |
+
discoveredPorts[0].path,
|
| 57 |
+
"so100_follower",
|
| 58 |
+
"demo_robot_arm"
|
| 59 |
+
);
|
| 60 |
+
console.log(`✅ Connected: ${robot.robotType} (ID: ${robot.robotId})\n`);
|
| 61 |
+
|
| 62 |
+
// Step 3: Release motors for calibration setup
|
| 63 |
+
const shouldRelease = await askUser(
|
| 64 |
+
"🔓 Release motors for manual positioning? (y/n): "
|
| 65 |
+
);
|
| 66 |
+
if (shouldRelease.toLowerCase() === "y") {
|
| 67 |
+
console.log("🔓 Step 2: Releasing motors...");
|
| 68 |
+
await releaseMotors(robot);
|
| 69 |
+
console.log("✅ Motors released - you can now move the robot by hand\n");
|
| 70 |
+
|
| 71 |
+
await askUser(
|
| 72 |
+
"Move robot to desired starting position, then press Enter to continue..."
|
| 73 |
+
);
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
// Step 4: Calibrate the robot
|
| 77 |
+
const shouldCalibrate = await askUser("🎯 Run calibration? (y/n): ");
|
| 78 |
+
if (shouldCalibrate.toLowerCase() === "y") {
|
| 79 |
+
console.log("\n🎯 Step 3: Starting calibration...");
|
| 80 |
+
console.log(
|
| 81 |
+
"This will record the motor ranges and set homing positions.\n"
|
| 82 |
+
);
|
| 83 |
+
|
| 84 |
+
const calibrationProcess = await calibrate({
|
| 85 |
+
robot: robot as RobotConnection,
|
| 86 |
+
onProgress: (message) => {
|
| 87 |
+
console.log(` 📊 ${message}`);
|
| 88 |
+
},
|
| 89 |
+
onLiveUpdate: (data) => {
|
| 90 |
+
// Show live motor positions during range recording
|
| 91 |
+
const positions = Object.entries(data)
|
| 92 |
+
.map(
|
| 93 |
+
([name, info]) =>
|
| 94 |
+
`${name}:${info.current}(${info.min}-${info.max})`
|
| 95 |
+
)
|
| 96 |
+
.join(" ");
|
| 97 |
+
process.stdout.write(`\r 🔄 Live: ${positions}`);
|
| 98 |
+
},
|
| 99 |
+
});
|
| 100 |
+
|
| 101 |
+
const calibrationData = await calibrationProcess.result;
|
| 102 |
+
console.log("\n✅ Calibration completed!");
|
| 103 |
+
|
| 104 |
+
// Show calibration summary
|
| 105 |
+
console.log("\n📋 Calibration Results:");
|
| 106 |
+
Object.entries(calibrationData).forEach(([motorName, config]) => {
|
| 107 |
+
console.log(
|
| 108 |
+
` ${motorName}: range ${config.range_min}-${config.range_max}, offset ${config.homing_offset}`
|
| 109 |
+
);
|
| 110 |
+
});
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
// Step 5: Teleoperation
|
| 114 |
+
const shouldTeleoperate = await askUser(
|
| 115 |
+
"\n🎮 Start keyboard teleoperation? (y/n): "
|
| 116 |
+
);
|
| 117 |
+
if (shouldTeleoperate.toLowerCase() === "y") {
|
| 118 |
+
console.log("\n🎮 Step 4: Starting teleoperation...");
|
| 119 |
+
console.log("Use keyboard to control the robot:\n");
|
| 120 |
+
|
| 121 |
+
const teleop = await teleoperate({
|
| 122 |
+
robot: robot as RobotConnection,
|
| 123 |
+
teleop: { type: "keyboard" },
|
| 124 |
+
onStateUpdate: (state) => {
|
| 125 |
+
if (state.isActive) {
|
| 126 |
+
const motorInfo = state.motorConfigs
|
| 127 |
+
.map(
|
| 128 |
+
(motor) => `${motor.name}:${Math.round(motor.currentPosition)}`
|
| 129 |
+
)
|
| 130 |
+
.join(" ");
|
| 131 |
+
process.stdout.write(`\r🤖 Motors: ${motorInfo}`);
|
| 132 |
+
}
|
| 133 |
+
},
|
| 134 |
+
});
|
| 135 |
+
|
| 136 |
+
// Start keyboard control
|
| 137 |
+
teleop.start();
|
| 138 |
+
|
| 139 |
+
console.log("✅ Teleoperation active!");
|
| 140 |
+
console.log("🎯 Use arrow keys, WASD, Q/E, O/C to control");
|
| 141 |
+
console.log("⚠️ Press ESC for emergency stop, Ctrl+C to exit\n");
|
| 142 |
+
|
| 143 |
+
// Handle graceful shutdown
|
| 144 |
+
process.on("SIGINT", async () => {
|
| 145 |
+
console.log("\n🛑 Shutting down teleoperation...");
|
| 146 |
+
teleop.stop();
|
| 147 |
+
await teleop.disconnect();
|
| 148 |
+
console.log("✅ Demo completed successfully!");
|
| 149 |
+
process.exit(0);
|
| 150 |
+
});
|
| 151 |
+
|
| 152 |
+
// Keep the demo running
|
| 153 |
+
console.log("Demo is running... Press Ctrl+C to stop");
|
| 154 |
+
await new Promise(() => {}); // Keep alive
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
console.log("\n🎉 Quick Start Demo completed!");
|
| 158 |
+
console.log(
|
| 159 |
+
"You can now integrate @lerobot/node into your own applications."
|
| 160 |
+
);
|
| 161 |
+
} catch (error) {
|
| 162 |
+
console.error("\n❌ Demo failed:", error.message);
|
| 163 |
+
console.log("\n🔧 Troubleshooting tips:");
|
| 164 |
+
console.log("- Check robot is connected and powered on");
|
| 165 |
+
console.log("- Verify correct serial port permissions");
|
| 166 |
+
console.log("- Try running 'npx lerobot find-port' to test connection");
|
| 167 |
+
process.exit(1);
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
// Handle uncaught errors gracefully
|
| 172 |
+
process.on("uncaughtException", (error) => {
|
| 173 |
+
console.error("\n💥 Unexpected error:", error.message);
|
| 174 |
+
process.exit(1);
|
| 175 |
+
});
|
| 176 |
+
|
| 177 |
+
process.on("unhandledRejection", (error) => {
|
| 178 |
+
console.error("\n💥 Unhandled promise rejection:", error);
|
| 179 |
+
process.exit(1);
|
| 180 |
+
});
|
| 181 |
+
|
| 182 |
+
// Run the demo
|
| 183 |
+
quickStartDemo();
|
examples/node-quick-start/test-homing-offsets.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"shoulder_pan": {
|
| 3 |
+
"id": 1,
|
| 4 |
+
"drive_mode": 0,
|
| 5 |
+
"homing_offset": 0,
|
| 6 |
+
"range_min": 0,
|
| 7 |
+
"range_max": 4095
|
| 8 |
+
},
|
| 9 |
+
"shoulder_lift": {
|
| 10 |
+
"id": 2,
|
| 11 |
+
"drive_mode": 0,
|
| 12 |
+
"homing_offset": 0,
|
| 13 |
+
"range_min": 0,
|
| 14 |
+
"range_max": 4095
|
| 15 |
+
},
|
| 16 |
+
"elbow_flex": {
|
| 17 |
+
"id": 3,
|
| 18 |
+
"drive_mode": 0,
|
| 19 |
+
"homing_offset": 0,
|
| 20 |
+
"range_min": 0,
|
| 21 |
+
"range_max": 4095
|
| 22 |
+
},
|
| 23 |
+
"wrist_flex": {
|
| 24 |
+
"id": 4,
|
| 25 |
+
"drive_mode": 0,
|
| 26 |
+
"homing_offset": 0,
|
| 27 |
+
"range_min": 0,
|
| 28 |
+
"range_max": 4095
|
| 29 |
+
},
|
| 30 |
+
"wrist_roll": {
|
| 31 |
+
"id": 5,
|
| 32 |
+
"drive_mode": 0,
|
| 33 |
+
"homing_offset": 0,
|
| 34 |
+
"range_min": 0,
|
| 35 |
+
"range_max": 4095
|
| 36 |
+
},
|
| 37 |
+
"gripper": {
|
| 38 |
+
"id": 6,
|
| 39 |
+
"drive_mode": 0,
|
| 40 |
+
"homing_offset": 0,
|
| 41 |
+
"range_min": 0,
|
| 42 |
+
"range_max": 4095
|
| 43 |
+
}
|
| 44 |
+
}
|
tsconfig.cli.json → examples/node-quick-start/tsconfig.json
RENAMED
|
@@ -1,15 +1,22 @@
|
|
| 1 |
{
|
| 2 |
"compilerOptions": {
|
| 3 |
"target": "ES2020",
|
| 4 |
-
"module": "
|
| 5 |
-
"moduleResolution": "
|
| 6 |
"outDir": "./dist",
|
| 7 |
"rootDir": "./src",
|
| 8 |
-
"declaration":
|
| 9 |
-
"
|
|
|
|
| 10 |
"esModuleInterop": true,
|
|
|
|
|
|
|
| 11 |
"skipLibCheck": true,
|
| 12 |
-
"forceConsistentCasingInFileNames": true
|
|
|
|
|
|
|
|
|
|
| 13 |
},
|
| 14 |
-
"include": ["src
|
|
|
|
| 15 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"compilerOptions": {
|
| 3 |
"target": "ES2020",
|
| 4 |
+
"module": "ESNext",
|
| 5 |
+
"moduleResolution": "Bundler",
|
| 6 |
"outDir": "./dist",
|
| 7 |
"rootDir": "./src",
|
| 8 |
+
"declaration": false,
|
| 9 |
+
"sourceMap": true,
|
| 10 |
+
"allowSyntheticDefaultImports": true,
|
| 11 |
"esModuleInterop": true,
|
| 12 |
+
"allowJs": true,
|
| 13 |
+
"strict": true,
|
| 14 |
"skipLibCheck": true,
|
| 15 |
+
"forceConsistentCasingInFileNames": true,
|
| 16 |
+
"resolveJsonModule": true,
|
| 17 |
+
"isolatedModules": true,
|
| 18 |
+
"types": ["node"]
|
| 19 |
},
|
| 20 |
+
"include": ["src/**/*"],
|
| 21 |
+
"exclude": ["dist", "node_modules"]
|
| 22 |
}
|
examples/node-quick-start/vite.config.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig } from "vite";
|
| 2 |
+
import { resolve } from "path";
|
| 3 |
+
|
| 4 |
+
export default defineConfig({
|
| 5 |
+
build: {
|
| 6 |
+
target: "node18",
|
| 7 |
+
lib: {
|
| 8 |
+
entry: {
|
| 9 |
+
main: resolve(__dirname, "src/main.ts"),
|
| 10 |
+
"demo-find-port": resolve(__dirname, "src/demo-find-port.ts"),
|
| 11 |
+
"demo-calibrate": resolve(__dirname, "src/demo-calibrate.ts"),
|
| 12 |
+
"demo-teleoperate": resolve(__dirname, "src/demo-teleoperate.ts"),
|
| 13 |
+
},
|
| 14 |
+
formats: ["es"],
|
| 15 |
+
fileName: (format, entryName) => `${entryName}.js`,
|
| 16 |
+
},
|
| 17 |
+
rollupOptions: {
|
| 18 |
+
external: [
|
| 19 |
+
// Node.js built-ins
|
| 20 |
+
"fs",
|
| 21 |
+
"fs/promises",
|
| 22 |
+
"path",
|
| 23 |
+
"os",
|
| 24 |
+
"readline",
|
| 25 |
+
"process",
|
| 26 |
+
// Dependencies that should remain external
|
| 27 |
+
"serialport",
|
| 28 |
+
"@lerobot/node",
|
| 29 |
+
],
|
| 30 |
+
},
|
| 31 |
+
outDir: "dist",
|
| 32 |
+
emptyOutDir: true,
|
| 33 |
+
},
|
| 34 |
+
resolve: {
|
| 35 |
+
alias: {
|
| 36 |
+
"@": resolve(__dirname, "src"),
|
| 37 |
+
},
|
| 38 |
+
},
|
| 39 |
+
});
|
package.json
CHANGED
|
@@ -6,10 +6,7 @@
|
|
| 6 |
"workspaces": [
|
| 7 |
"packages/*"
|
| 8 |
],
|
| 9 |
-
"
|
| 10 |
-
"dist/**/*",
|
| 11 |
-
"README.md"
|
| 12 |
-
],
|
| 13 |
"keywords": [
|
| 14 |
"robotics",
|
| 15 |
"ai",
|
|
@@ -18,14 +15,13 @@
|
|
| 18 |
"lerobot"
|
| 19 |
],
|
| 20 |
"scripts": {
|
| 21 |
-
"cli:find-port": "tsx src/cli/index.ts find-port",
|
| 22 |
-
"cli:calibrate": "tsx src/cli/index.ts calibrate",
|
| 23 |
-
"cli:teleoperate": "tsx src/cli/index.ts teleoperate",
|
| 24 |
"example:cyberpunk": "cd examples/cyberpunk-standalone && pnpm dev",
|
| 25 |
"example:iframe-test": "cd examples/iframe-dialog-test && pnpm dev",
|
| 26 |
"example:sequential-test": "cd examples/test-sequential-operations && pnpm dev",
|
| 27 |
-
"
|
| 28 |
-
"
|
|
|
|
|
|
|
| 29 |
"build:cyberpunk": "cd examples/cyberpunk-standalone && pnpm install && pnpm build",
|
| 30 |
"changeset": "changeset",
|
| 31 |
"changeset:version": "changeset version",
|
|
|
|
| 6 |
"workspaces": [
|
| 7 |
"packages/*"
|
| 8 |
],
|
| 9 |
+
"private": true,
|
|
|
|
|
|
|
|
|
|
| 10 |
"keywords": [
|
| 11 |
"robotics",
|
| 12 |
"ai",
|
|
|
|
| 15 |
"lerobot"
|
| 16 |
],
|
| 17 |
"scripts": {
|
|
|
|
|
|
|
|
|
|
| 18 |
"example:cyberpunk": "cd examples/cyberpunk-standalone && pnpm dev",
|
| 19 |
"example:iframe-test": "cd examples/iframe-dialog-test && pnpm dev",
|
| 20 |
"example:sequential-test": "cd examples/test-sequential-operations && pnpm dev",
|
| 21 |
+
"dev": "pnpm run dev:node",
|
| 22 |
+
"dev:node": "pnpm -C packages/node dev",
|
| 23 |
+
"dev:cli": "pnpm -C packages/cli dev",
|
| 24 |
+
"build:all": "pnpm -C packages/node build && pnpm -C packages/cli build",
|
| 25 |
"build:cyberpunk": "cd examples/cyberpunk-standalone && pnpm install && pnpm build",
|
| 26 |
"changeset": "changeset",
|
| 27 |
"changeset:version": "changeset version",
|
packages/cli/.gitignore
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dependencies
|
| 2 |
+
node_modules/
|
| 3 |
+
npm-debug.log*
|
| 4 |
+
yarn-debug.log*
|
| 5 |
+
yarn-error.log*
|
| 6 |
+
pnpm-debug.log*
|
| 7 |
+
|
| 8 |
+
# Build output
|
| 9 |
+
dist/
|
| 10 |
+
build/
|
| 11 |
+
*.tsbuildinfo
|
| 12 |
+
|
| 13 |
+
# Environment files
|
| 14 |
+
.env
|
| 15 |
+
.env.local
|
| 16 |
+
.env.development.local
|
| 17 |
+
.env.test.local
|
| 18 |
+
.env.production.local
|
| 19 |
+
|
| 20 |
+
# IDE files
|
| 21 |
+
.vscode/
|
| 22 |
+
.idea/
|
| 23 |
+
*.swp
|
| 24 |
+
*.swo
|
| 25 |
+
|
| 26 |
+
# OS files
|
| 27 |
+
.DS_Store
|
| 28 |
+
Thumbs.db
|
| 29 |
+
|
| 30 |
+
# Coverage reports
|
| 31 |
+
coverage/
|
| 32 |
+
*.lcov
|
| 33 |
+
|
| 34 |
+
# Temporary files
|
| 35 |
+
*.tmp
|
| 36 |
+
*.temp
|
packages/cli/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# lerobot
|
| 2 |
+
|
| 3 |
+
Python lerobot compatible CLI for Node.js. Provides the same command-line interface as Python lerobot with identical behavior and syntax.
|
| 4 |
+
|
| 5 |
+
## Installation
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
# Use directly with npx (recommended)
|
| 9 |
+
npx lerobot find-port
|
| 10 |
+
|
| 11 |
+
# Or install globally
|
| 12 |
+
npm install -g lerobot
|
| 13 |
+
lerobot find-port
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
## Commands
|
| 17 |
+
|
| 18 |
+
### Find Port
|
| 19 |
+
|
| 20 |
+
Discover robot port with interactive cable detection. Matches Python lerobot's `find_port.py` exactly.
|
| 21 |
+
|
| 22 |
+
```bash
|
| 23 |
+
# Interactive cable detection (always enabled, like Python lerobot)
|
| 24 |
+
lerobot find-port
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
This command follows Python lerobot's behavior exactly:
|
| 28 |
+
|
| 29 |
+
1. Lists initial ports
|
| 30 |
+
2. Prompts to unplug USB cable
|
| 31 |
+
3. Detects which port disappeared
|
| 32 |
+
4. Prompts to reconnect cable
|
| 33 |
+
5. Verifies port is restored
|
| 34 |
+
|
| 35 |
+
### Calibrate
|
| 36 |
+
|
| 37 |
+
Calibrate robot motors and save calibration data to Hugging Face cache.
|
| 38 |
+
|
| 39 |
+
```bash
|
| 40 |
+
lerobot calibrate \
|
| 41 |
+
--robot.type=so100_follower \
|
| 42 |
+
--robot.port=/dev/ttyUSB0 \
|
| 43 |
+
--robot.id=my_follower_arm
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
**Options:**
|
| 47 |
+
|
| 48 |
+
- `--robot.type` - Robot type (e.g., `so100_follower`)
|
| 49 |
+
- `--robot.port` - Serial port (e.g., `/dev/ttyUSB0`, `COM4`)
|
| 50 |
+
- `--robot.id` - Robot identifier (default: `default`)
|
| 51 |
+
- `--output` - Custom output path for calibration file
|
| 52 |
+
|
| 53 |
+
**Compatible with:** `python -m lerobot calibrate`
|
| 54 |
+
|
| 55 |
+
### Teleoperate
|
| 56 |
+
|
| 57 |
+
Control robot through keyboard teleoperation.
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
lerobot teleoperate \
|
| 61 |
+
--robot.type=so100_follower \
|
| 62 |
+
--robot.port=/dev/ttyUSB0 \
|
| 63 |
+
--robot.id=my_follower_arm
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
**Options:**
|
| 67 |
+
|
| 68 |
+
- `--robot.type` - Robot type (e.g., `so100_follower`)
|
| 69 |
+
- `--robot.port` - Serial port (e.g., `/dev/ttyUSB0`, `COM4`)
|
| 70 |
+
- `--robot.id` - Robot identifier (default: `default`)
|
| 71 |
+
- `--teleop.type` - Teleoperator type (default: `keyboard`)
|
| 72 |
+
- `--teleop.stepSize` - Step size for keyboard control (default: `25`)
|
| 73 |
+
- `--duration` - Duration in seconds, 0 = unlimited (default: `0`)
|
| 74 |
+
|
| 75 |
+
**Controls:**
|
| 76 |
+
|
| 77 |
+
- `w/s` - Motor 1 up/down
|
| 78 |
+
- `a/d` - Motor 2 left/right
|
| 79 |
+
- `q/e` - Motor 3 up/down
|
| 80 |
+
- `r/f` - Motor 4 forward/back
|
| 81 |
+
- `t/g` - Motor 5 up/down
|
| 82 |
+
- `y/h` - Motor 6 open/close
|
| 83 |
+
- `Ctrl+C` - Stop and exit
|
| 84 |
+
|
| 85 |
+
**Compatible with:** `python -m lerobot teleoperate`
|
| 86 |
+
|
| 87 |
+
### Release Motors
|
| 88 |
+
|
| 89 |
+
Release robot motors for manual movement.
|
| 90 |
+
|
| 91 |
+
```bash
|
| 92 |
+
lerobot release-motors \
|
| 93 |
+
--robot.type=so100_follower \
|
| 94 |
+
--robot.port=/dev/ttyUSB0
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
**Options:**
|
| 98 |
+
|
| 99 |
+
- `--robot.type` - Robot type (e.g., `so100_follower`)
|
| 100 |
+
- `--robot.port` - Serial port (e.g., `/dev/ttyUSB0`, `COM4`)
|
| 101 |
+
- `--robot.id` - Robot identifier (default: `default`)
|
| 102 |
+
- `--motors` - Specific motor IDs to release (comma-separated)
|
| 103 |
+
|
| 104 |
+
**Compatible with:** `python -m lerobot release-motors`
|
| 105 |
+
|
| 106 |
+
## Python lerobot Compatibility
|
| 107 |
+
|
| 108 |
+
This CLI provides 100% compatible commands with Python lerobot:
|
| 109 |
+
|
| 110 |
+
| Python lerobot | Node.js lerobot | Status |
|
| 111 |
+
| ---------------------------------- | ---------------------------- | ------------- |
|
| 112 |
+
| `python -m lerobot find_port` | `npx lerobot find-port` | ✅ Compatible |
|
| 113 |
+
| `python -m lerobot calibrate` | `npx lerobot calibrate` | ✅ Compatible |
|
| 114 |
+
| `python -m lerobot teleoperate` | `npx lerobot teleoperate` | ✅ Compatible |
|
| 115 |
+
| `python -m lerobot release-motors` | `npx lerobot release-motors` | ✅ Compatible |
|
| 116 |
+
|
| 117 |
+
### Calibration Data Compatibility
|
| 118 |
+
|
| 119 |
+
Calibration files are saved to the same location as Python lerobot:
|
| 120 |
+
|
| 121 |
+
```
|
| 122 |
+
~/.cache/huggingface/lerobot/calibration/robots/{robot_type}/{robot_id}.json
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
This ensures calibration data is shared between Python and Node.js implementations.
|
| 126 |
+
|
| 127 |
+
## Examples
|
| 128 |
+
|
| 129 |
+
### Complete Workflow
|
| 130 |
+
|
| 131 |
+
```bash
|
| 132 |
+
# 1. Find your robot (interactive mode)
|
| 133 |
+
npx lerobot find-port --interactive
|
| 134 |
+
# Output: Detected port: /dev/ttyUSB0
|
| 135 |
+
|
| 136 |
+
# 2. Calibrate the robot
|
| 137 |
+
npx lerobot calibrate \
|
| 138 |
+
--robot.type=so100_follower \
|
| 139 |
+
--robot.port=/dev/ttyUSB0 \
|
| 140 |
+
--robot.id=my_arm
|
| 141 |
+
|
| 142 |
+
# 3. Control the robot
|
| 143 |
+
npx lerobot teleoperate \
|
| 144 |
+
--robot.type=so100_follower \
|
| 145 |
+
--robot.port=/dev/ttyUSB0 \
|
| 146 |
+
--robot.id=my_arm
|
| 147 |
+
|
| 148 |
+
# 4. Release motors when done
|
| 149 |
+
npx lerobot release-motors \
|
| 150 |
+
--robot.type=so100_follower \
|
| 151 |
+
--robot.port=/dev/ttyUSB0
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
### Automation Scripts
|
| 155 |
+
|
| 156 |
+
```bash
|
| 157 |
+
#!/bin/bash
|
| 158 |
+
# Automated calibration script
|
| 159 |
+
|
| 160 |
+
ROBOT_TYPE="so100_follower"
|
| 161 |
+
ROBOT_PORT="/dev/ttyUSB0"
|
| 162 |
+
ROBOT_ID="production_arm_1"
|
| 163 |
+
|
| 164 |
+
echo "Starting automated calibration..."
|
| 165 |
+
npx lerobot calibrate \
|
| 166 |
+
--robot.type=$ROBOT_TYPE \
|
| 167 |
+
--robot.port=$ROBOT_PORT \
|
| 168 |
+
--robot.id=$ROBOT_ID
|
| 169 |
+
|
| 170 |
+
echo "Calibration complete. Starting teleoperation..."
|
| 171 |
+
npx lerobot teleoperate \
|
| 172 |
+
--robot.type=$ROBOT_TYPE \
|
| 173 |
+
--robot.port=$ROBOT_PORT \
|
| 174 |
+
--robot.id=$ROBOT_ID \
|
| 175 |
+
--duration=60 # Run for 60 seconds
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
## Requirements
|
| 179 |
+
|
| 180 |
+
- Node.js 18+
|
| 181 |
+
- Compatible with Windows, macOS, and Linux
|
| 182 |
+
- Same hardware requirements as Python lerobot
|
| 183 |
+
|
| 184 |
+
## Related Packages
|
| 185 |
+
|
| 186 |
+
- **[@lerobot/node](../node/)** - Node.js library for programmatic control
|
| 187 |
+
- **[@lerobot/web](../web/)** - Browser library for web applications
|
| 188 |
+
|
| 189 |
+
## License
|
| 190 |
+
|
| 191 |
+
Apache-2.0
|
packages/cli/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "lerobot",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"description": "CLI for lerobot.js",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"main": "./dist/index.js",
|
| 7 |
+
"types": "./dist/index.d.ts",
|
| 8 |
+
"bin": {
|
| 9 |
+
"lerobot": "./dist/cli.js"
|
| 10 |
+
},
|
| 11 |
+
"files": [
|
| 12 |
+
"dist/**/*",
|
| 13 |
+
"README.md"
|
| 14 |
+
],
|
| 15 |
+
"keywords": [
|
| 16 |
+
"robotics",
|
| 17 |
+
"cli",
|
| 18 |
+
"lerobot",
|
| 19 |
+
"python-compatible",
|
| 20 |
+
"hardware-control"
|
| 21 |
+
],
|
| 22 |
+
"scripts": {
|
| 23 |
+
"build": "vite build",
|
| 24 |
+
"dev": "vite-node src/cli.ts",
|
| 25 |
+
"prepublishOnly": "npm run build",
|
| 26 |
+
"test": "echo 'No tests defined'"
|
| 27 |
+
},
|
| 28 |
+
"dependencies": {
|
| 29 |
+
"@lerobot/node": "file:../node",
|
| 30 |
+
"chalk": "^5.3.0",
|
| 31 |
+
"commander": "^11.0.0",
|
| 32 |
+
"serialport": "^12.0.0"
|
| 33 |
+
},
|
| 34 |
+
"devDependencies": {
|
| 35 |
+
"vite": "^6.3.5",
|
| 36 |
+
"vite-node": "^2.0.0",
|
| 37 |
+
"typescript": "^5.3.0",
|
| 38 |
+
"@types/node": "^18.0.0"
|
| 39 |
+
},
|
| 40 |
+
"engines": {
|
| 41 |
+
"node": ">=18.0.0"
|
| 42 |
+
},
|
| 43 |
+
"repository": {
|
| 44 |
+
"type": "git",
|
| 45 |
+
"url": "https://github.com/timpietrusky/lerobot.js/tree/main/packages/cli"
|
| 46 |
+
},
|
| 47 |
+
"license": "Apache-2.0",
|
| 48 |
+
"author": "Tim Pietrusky",
|
| 49 |
+
"publishConfig": {
|
| 50 |
+
"access": "public"
|
| 51 |
+
}
|
| 52 |
+
}
|
packages/cli/pnpm-lock.yaml
ADDED
|
@@ -0,0 +1,1167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
lockfileVersion: '9.0'
|
| 2 |
+
|
| 3 |
+
settings:
|
| 4 |
+
autoInstallPeers: true
|
| 5 |
+
excludeLinksFromLockfile: false
|
| 6 |
+
|
| 7 |
+
importers:
|
| 8 |
+
|
| 9 |
+
.:
|
| 10 |
+
dependencies:
|
| 11 |
+
'@lerobot/node':
|
| 12 |
+
specifier: file:../node
|
| 13 |
+
version: file:../node([email protected])
|
| 14 |
+
chalk:
|
| 15 |
+
specifier: ^5.3.0
|
| 16 |
+
version: 5.4.1
|
| 17 |
+
commander:
|
| 18 |
+
specifier: ^11.0.0
|
| 19 |
+
version: 11.1.0
|
| 20 |
+
serialport:
|
| 21 |
+
specifier: ^12.0.0
|
| 22 |
+
version: 12.0.0
|
| 23 |
+
devDependencies:
|
| 24 |
+
'@types/node':
|
| 25 |
+
specifier: ^18.0.0
|
| 26 |
+
version: 18.19.121
|
| 27 |
+
typescript:
|
| 28 |
+
specifier: ^5.3.0
|
| 29 |
+
version: 5.8.3
|
| 30 |
+
vite:
|
| 31 |
+
specifier: ^6.3.5
|
| 32 |
+
version: 6.3.5(@types/[email protected])
|
| 33 |
+
vite-node:
|
| 34 |
+
specifier: ^2.0.0
|
| 35 |
+
version: 2.1.9(@types/[email protected])
|
| 36 |
+
|
| 37 |
+
packages:
|
| 38 |
+
|
| 39 |
+
'@esbuild/[email protected]':
|
| 40 |
+
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
| 41 |
+
engines: {node: '>=12'}
|
| 42 |
+
cpu: [ppc64]
|
| 43 |
+
os: [aix]
|
| 44 |
+
|
| 45 |
+
'@esbuild/[email protected]':
|
| 46 |
+
resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==}
|
| 47 |
+
engines: {node: '>=18'}
|
| 48 |
+
cpu: [ppc64]
|
| 49 |
+
os: [aix]
|
| 50 |
+
|
| 51 |
+
'@esbuild/[email protected]':
|
| 52 |
+
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
|
| 53 |
+
engines: {node: '>=12'}
|
| 54 |
+
cpu: [arm64]
|
| 55 |
+
os: [android]
|
| 56 |
+
|
| 57 |
+
'@esbuild/[email protected]':
|
| 58 |
+
resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==}
|
| 59 |
+
engines: {node: '>=18'}
|
| 60 |
+
cpu: [arm64]
|
| 61 |
+
os: [android]
|
| 62 |
+
|
| 63 |
+
'@esbuild/[email protected]':
|
| 64 |
+
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
|
| 65 |
+
engines: {node: '>=12'}
|
| 66 |
+
cpu: [arm]
|
| 67 |
+
os: [android]
|
| 68 |
+
|
| 69 |
+
'@esbuild/[email protected]':
|
| 70 |
+
resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==}
|
| 71 |
+
engines: {node: '>=18'}
|
| 72 |
+
cpu: [arm]
|
| 73 |
+
os: [android]
|
| 74 |
+
|
| 75 |
+
'@esbuild/[email protected]':
|
| 76 |
+
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
|
| 77 |
+
engines: {node: '>=12'}
|
| 78 |
+
cpu: [x64]
|
| 79 |
+
os: [android]
|
| 80 |
+
|
| 81 |
+
'@esbuild/[email protected]':
|
| 82 |
+
resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==}
|
| 83 |
+
engines: {node: '>=18'}
|
| 84 |
+
cpu: [x64]
|
| 85 |
+
os: [android]
|
| 86 |
+
|
| 87 |
+
'@esbuild/[email protected]':
|
| 88 |
+
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
|
| 89 |
+
engines: {node: '>=12'}
|
| 90 |
+
cpu: [arm64]
|
| 91 |
+
os: [darwin]
|
| 92 |
+
|
| 93 |
+
'@esbuild/[email protected]':
|
| 94 |
+
resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==}
|
| 95 |
+
engines: {node: '>=18'}
|
| 96 |
+
cpu: [arm64]
|
| 97 |
+
os: [darwin]
|
| 98 |
+
|
| 99 |
+
'@esbuild/[email protected]':
|
| 100 |
+
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
|
| 101 |
+
engines: {node: '>=12'}
|
| 102 |
+
cpu: [x64]
|
| 103 |
+
os: [darwin]
|
| 104 |
+
|
| 105 |
+
'@esbuild/[email protected]':
|
| 106 |
+
resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==}
|
| 107 |
+
engines: {node: '>=18'}
|
| 108 |
+
cpu: [x64]
|
| 109 |
+
os: [darwin]
|
| 110 |
+
|
| 111 |
+
'@esbuild/[email protected]':
|
| 112 |
+
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
|
| 113 |
+
engines: {node: '>=12'}
|
| 114 |
+
cpu: [arm64]
|
| 115 |
+
os: [freebsd]
|
| 116 |
+
|
| 117 |
+
'@esbuild/[email protected]':
|
| 118 |
+
resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==}
|
| 119 |
+
engines: {node: '>=18'}
|
| 120 |
+
cpu: [arm64]
|
| 121 |
+
os: [freebsd]
|
| 122 |
+
|
| 123 |
+
'@esbuild/[email protected]':
|
| 124 |
+
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
|
| 125 |
+
engines: {node: '>=12'}
|
| 126 |
+
cpu: [x64]
|
| 127 |
+
os: [freebsd]
|
| 128 |
+
|
| 129 |
+
'@esbuild/[email protected]':
|
| 130 |
+
resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==}
|
| 131 |
+
engines: {node: '>=18'}
|
| 132 |
+
cpu: [x64]
|
| 133 |
+
os: [freebsd]
|
| 134 |
+
|
| 135 |
+
'@esbuild/[email protected]':
|
| 136 |
+
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
|
| 137 |
+
engines: {node: '>=12'}
|
| 138 |
+
cpu: [arm64]
|
| 139 |
+
os: [linux]
|
| 140 |
+
|
| 141 |
+
'@esbuild/[email protected]':
|
| 142 |
+
resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==}
|
| 143 |
+
engines: {node: '>=18'}
|
| 144 |
+
cpu: [arm64]
|
| 145 |
+
os: [linux]
|
| 146 |
+
|
| 147 |
+
'@esbuild/[email protected]':
|
| 148 |
+
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
|
| 149 |
+
engines: {node: '>=12'}
|
| 150 |
+
cpu: [arm]
|
| 151 |
+
os: [linux]
|
| 152 |
+
|
| 153 |
+
'@esbuild/[email protected]':
|
| 154 |
+
resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==}
|
| 155 |
+
engines: {node: '>=18'}
|
| 156 |
+
cpu: [arm]
|
| 157 |
+
os: [linux]
|
| 158 |
+
|
| 159 |
+
'@esbuild/[email protected]':
|
| 160 |
+
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
|
| 161 |
+
engines: {node: '>=12'}
|
| 162 |
+
cpu: [ia32]
|
| 163 |
+
os: [linux]
|
| 164 |
+
|
| 165 |
+
'@esbuild/[email protected]':
|
| 166 |
+
resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==}
|
| 167 |
+
engines: {node: '>=18'}
|
| 168 |
+
cpu: [ia32]
|
| 169 |
+
os: [linux]
|
| 170 |
+
|
| 171 |
+
'@esbuild/[email protected]':
|
| 172 |
+
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
|
| 173 |
+
engines: {node: '>=12'}
|
| 174 |
+
cpu: [loong64]
|
| 175 |
+
os: [linux]
|
| 176 |
+
|
| 177 |
+
'@esbuild/[email protected]':
|
| 178 |
+
resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==}
|
| 179 |
+
engines: {node: '>=18'}
|
| 180 |
+
cpu: [loong64]
|
| 181 |
+
os: [linux]
|
| 182 |
+
|
| 183 |
+
'@esbuild/[email protected]':
|
| 184 |
+
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
|
| 185 |
+
engines: {node: '>=12'}
|
| 186 |
+
cpu: [mips64el]
|
| 187 |
+
os: [linux]
|
| 188 |
+
|
| 189 |
+
'@esbuild/[email protected]':
|
| 190 |
+
resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==}
|
| 191 |
+
engines: {node: '>=18'}
|
| 192 |
+
cpu: [mips64el]
|
| 193 |
+
os: [linux]
|
| 194 |
+
|
| 195 |
+
'@esbuild/[email protected]':
|
| 196 |
+
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
|
| 197 |
+
engines: {node: '>=12'}
|
| 198 |
+
cpu: [ppc64]
|
| 199 |
+
os: [linux]
|
| 200 |
+
|
| 201 |
+
'@esbuild/[email protected]':
|
| 202 |
+
resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==}
|
| 203 |
+
engines: {node: '>=18'}
|
| 204 |
+
cpu: [ppc64]
|
| 205 |
+
os: [linux]
|
| 206 |
+
|
| 207 |
+
'@esbuild/[email protected]':
|
| 208 |
+
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
|
| 209 |
+
engines: {node: '>=12'}
|
| 210 |
+
cpu: [riscv64]
|
| 211 |
+
os: [linux]
|
| 212 |
+
|
| 213 |
+
'@esbuild/[email protected]':
|
| 214 |
+
resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==}
|
| 215 |
+
engines: {node: '>=18'}
|
| 216 |
+
cpu: [riscv64]
|
| 217 |
+
os: [linux]
|
| 218 |
+
|
| 219 |
+
'@esbuild/[email protected]':
|
| 220 |
+
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
|
| 221 |
+
engines: {node: '>=12'}
|
| 222 |
+
cpu: [s390x]
|
| 223 |
+
os: [linux]
|
| 224 |
+
|
| 225 |
+
'@esbuild/[email protected]':
|
| 226 |
+
resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==}
|
| 227 |
+
engines: {node: '>=18'}
|
| 228 |
+
cpu: [s390x]
|
| 229 |
+
os: [linux]
|
| 230 |
+
|
| 231 |
+
'@esbuild/[email protected]':
|
| 232 |
+
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
|
| 233 |
+
engines: {node: '>=12'}
|
| 234 |
+
cpu: [x64]
|
| 235 |
+
os: [linux]
|
| 236 |
+
|
| 237 |
+
'@esbuild/[email protected]':
|
| 238 |
+
resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==}
|
| 239 |
+
engines: {node: '>=18'}
|
| 240 |
+
cpu: [x64]
|
| 241 |
+
os: [linux]
|
| 242 |
+
|
| 243 |
+
'@esbuild/[email protected]':
|
| 244 |
+
resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==}
|
| 245 |
+
engines: {node: '>=18'}
|
| 246 |
+
cpu: [arm64]
|
| 247 |
+
os: [netbsd]
|
| 248 |
+
|
| 249 |
+
'@esbuild/[email protected]':
|
| 250 |
+
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
|
| 251 |
+
engines: {node: '>=12'}
|
| 252 |
+
cpu: [x64]
|
| 253 |
+
os: [netbsd]
|
| 254 |
+
|
| 255 |
+
'@esbuild/[email protected]':
|
| 256 |
+
resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==}
|
| 257 |
+
engines: {node: '>=18'}
|
| 258 |
+
cpu: [x64]
|
| 259 |
+
os: [netbsd]
|
| 260 |
+
|
| 261 |
+
'@esbuild/[email protected]':
|
| 262 |
+
resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==}
|
| 263 |
+
engines: {node: '>=18'}
|
| 264 |
+
cpu: [arm64]
|
| 265 |
+
os: [openbsd]
|
| 266 |
+
|
| 267 |
+
'@esbuild/[email protected]':
|
| 268 |
+
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
|
| 269 |
+
engines: {node: '>=12'}
|
| 270 |
+
cpu: [x64]
|
| 271 |
+
os: [openbsd]
|
| 272 |
+
|
| 273 |
+
'@esbuild/[email protected]':
|
| 274 |
+
resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==}
|
| 275 |
+
engines: {node: '>=18'}
|
| 276 |
+
cpu: [x64]
|
| 277 |
+
os: [openbsd]
|
| 278 |
+
|
| 279 |
+
'@esbuild/[email protected]':
|
| 280 |
+
resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==}
|
| 281 |
+
engines: {node: '>=18'}
|
| 282 |
+
cpu: [arm64]
|
| 283 |
+
os: [openharmony]
|
| 284 |
+
|
| 285 |
+
'@esbuild/[email protected]':
|
| 286 |
+
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
|
| 287 |
+
engines: {node: '>=12'}
|
| 288 |
+
cpu: [x64]
|
| 289 |
+
os: [sunos]
|
| 290 |
+
|
| 291 |
+
'@esbuild/[email protected]':
|
| 292 |
+
resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==}
|
| 293 |
+
engines: {node: '>=18'}
|
| 294 |
+
cpu: [x64]
|
| 295 |
+
os: [sunos]
|
| 296 |
+
|
| 297 |
+
'@esbuild/[email protected]':
|
| 298 |
+
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
|
| 299 |
+
engines: {node: '>=12'}
|
| 300 |
+
cpu: [arm64]
|
| 301 |
+
os: [win32]
|
| 302 |
+
|
| 303 |
+
'@esbuild/[email protected]':
|
| 304 |
+
resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==}
|
| 305 |
+
engines: {node: '>=18'}
|
| 306 |
+
cpu: [arm64]
|
| 307 |
+
os: [win32]
|
| 308 |
+
|
| 309 |
+
'@esbuild/[email protected]':
|
| 310 |
+
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
|
| 311 |
+
engines: {node: '>=12'}
|
| 312 |
+
cpu: [ia32]
|
| 313 |
+
os: [win32]
|
| 314 |
+
|
| 315 |
+
'@esbuild/[email protected]':
|
| 316 |
+
resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==}
|
| 317 |
+
engines: {node: '>=18'}
|
| 318 |
+
cpu: [ia32]
|
| 319 |
+
os: [win32]
|
| 320 |
+
|
| 321 |
+
'@esbuild/[email protected]':
|
| 322 |
+
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
|
| 323 |
+
engines: {node: '>=12'}
|
| 324 |
+
cpu: [x64]
|
| 325 |
+
os: [win32]
|
| 326 |
+
|
| 327 |
+
'@esbuild/[email protected]':
|
| 328 |
+
resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==}
|
| 329 |
+
engines: {node: '>=18'}
|
| 330 |
+
cpu: [x64]
|
| 331 |
+
os: [win32]
|
| 332 |
+
|
| 333 |
+
'@lerobot/node@file:../node':
|
| 334 |
+
resolution: {directory: ../node, type: directory}
|
| 335 |
+
engines: {node: '>=18.0.0'}
|
| 336 |
+
peerDependencies:
|
| 337 |
+
typescript: '>=4.5.0'
|
| 338 |
+
|
| 339 |
+
'@rollup/[email protected]':
|
| 340 |
+
resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==}
|
| 341 |
+
cpu: [arm]
|
| 342 |
+
os: [android]
|
| 343 |
+
|
| 344 |
+
'@rollup/[email protected]':
|
| 345 |
+
resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==}
|
| 346 |
+
cpu: [arm64]
|
| 347 |
+
os: [android]
|
| 348 |
+
|
| 349 |
+
'@rollup/[email protected]':
|
| 350 |
+
resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==}
|
| 351 |
+
cpu: [arm64]
|
| 352 |
+
os: [darwin]
|
| 353 |
+
|
| 354 |
+
'@rollup/[email protected]':
|
| 355 |
+
resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==}
|
| 356 |
+
cpu: [x64]
|
| 357 |
+
os: [darwin]
|
| 358 |
+
|
| 359 |
+
'@rollup/[email protected]':
|
| 360 |
+
resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==}
|
| 361 |
+
cpu: [arm64]
|
| 362 |
+
os: [freebsd]
|
| 363 |
+
|
| 364 |
+
'@rollup/[email protected]':
|
| 365 |
+
resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==}
|
| 366 |
+
cpu: [x64]
|
| 367 |
+
os: [freebsd]
|
| 368 |
+
|
| 369 |
+
'@rollup/[email protected]':
|
| 370 |
+
resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==}
|
| 371 |
+
cpu: [arm]
|
| 372 |
+
os: [linux]
|
| 373 |
+
|
| 374 |
+
'@rollup/[email protected]':
|
| 375 |
+
resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==}
|
| 376 |
+
cpu: [arm]
|
| 377 |
+
os: [linux]
|
| 378 |
+
|
| 379 |
+
'@rollup/[email protected]':
|
| 380 |
+
resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==}
|
| 381 |
+
cpu: [arm64]
|
| 382 |
+
os: [linux]
|
| 383 |
+
|
| 384 |
+
'@rollup/[email protected]':
|
| 385 |
+
resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==}
|
| 386 |
+
cpu: [arm64]
|
| 387 |
+
os: [linux]
|
| 388 |
+
|
| 389 |
+
'@rollup/[email protected]':
|
| 390 |
+
resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==}
|
| 391 |
+
cpu: [loong64]
|
| 392 |
+
os: [linux]
|
| 393 |
+
|
| 394 |
+
'@rollup/[email protected]':
|
| 395 |
+
resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==}
|
| 396 |
+
cpu: [ppc64]
|
| 397 |
+
os: [linux]
|
| 398 |
+
|
| 399 |
+
'@rollup/[email protected]':
|
| 400 |
+
resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==}
|
| 401 |
+
cpu: [riscv64]
|
| 402 |
+
os: [linux]
|
| 403 |
+
|
| 404 |
+
'@rollup/[email protected]':
|
| 405 |
+
resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==}
|
| 406 |
+
cpu: [riscv64]
|
| 407 |
+
os: [linux]
|
| 408 |
+
|
| 409 |
+
'@rollup/[email protected]':
|
| 410 |
+
resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==}
|
| 411 |
+
cpu: [s390x]
|
| 412 |
+
os: [linux]
|
| 413 |
+
|
| 414 |
+
'@rollup/[email protected]':
|
| 415 |
+
resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==}
|
| 416 |
+
cpu: [x64]
|
| 417 |
+
os: [linux]
|
| 418 |
+
|
| 419 |
+
'@rollup/[email protected]':
|
| 420 |
+
resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==}
|
| 421 |
+
cpu: [x64]
|
| 422 |
+
os: [linux]
|
| 423 |
+
|
| 424 |
+
'@rollup/[email protected]':
|
| 425 |
+
resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==}
|
| 426 |
+
cpu: [arm64]
|
| 427 |
+
os: [win32]
|
| 428 |
+
|
| 429 |
+
'@rollup/[email protected]':
|
| 430 |
+
resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==}
|
| 431 |
+
cpu: [ia32]
|
| 432 |
+
os: [win32]
|
| 433 |
+
|
| 434 |
+
'@rollup/[email protected]':
|
| 435 |
+
resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==}
|
| 436 |
+
cpu: [x64]
|
| 437 |
+
os: [win32]
|
| 438 |
+
|
| 439 |
+
'@serialport/[email protected]':
|
| 440 |
+
resolution: {integrity: sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==}
|
| 441 |
+
engines: {node: '>=12.0.0'}
|
| 442 |
+
|
| 443 |
+
'@serialport/[email protected]':
|
| 444 |
+
resolution: {integrity: sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==}
|
| 445 |
+
engines: {node: '>=16.0.0'}
|
| 446 |
+
|
| 447 |
+
'@serialport/[email protected]':
|
| 448 |
+
resolution: {integrity: sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==}
|
| 449 |
+
engines: {node: ^12.22 || ^14.13 || >=16}
|
| 450 |
+
|
| 451 |
+
'@serialport/[email protected]':
|
| 452 |
+
resolution: {integrity: sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==}
|
| 453 |
+
engines: {node: '>=12.0.0'}
|
| 454 |
+
|
| 455 |
+
'@serialport/[email protected]':
|
| 456 |
+
resolution: {integrity: sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==}
|
| 457 |
+
engines: {node: '>=12.0.0'}
|
| 458 |
+
|
| 459 |
+
'@serialport/[email protected]':
|
| 460 |
+
resolution: {integrity: sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==}
|
| 461 |
+
engines: {node: '>=12.0.0'}
|
| 462 |
+
|
| 463 |
+
'@serialport/[email protected]':
|
| 464 |
+
resolution: {integrity: sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==}
|
| 465 |
+
engines: {node: '>=12.0.0'}
|
| 466 |
+
|
| 467 |
+
'@serialport/[email protected]':
|
| 468 |
+
resolution: {integrity: sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==}
|
| 469 |
+
engines: {node: '>=12.0.0'}
|
| 470 |
+
|
| 471 |
+
'@serialport/[email protected]':
|
| 472 |
+
resolution: {integrity: sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==}
|
| 473 |
+
engines: {node: '>=8.6.0'}
|
| 474 |
+
|
| 475 |
+
'@serialport/[email protected]':
|
| 476 |
+
resolution: {integrity: sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==}
|
| 477 |
+
engines: {node: '>=12.0.0'}
|
| 478 |
+
|
| 479 |
+
'@serialport/[email protected]':
|
| 480 |
+
resolution: {integrity: sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==}
|
| 481 |
+
engines: {node: '>=12.0.0'}
|
| 482 |
+
|
| 483 |
+
'@serialport/[email protected]':
|
| 484 |
+
resolution: {integrity: sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==}
|
| 485 |
+
engines: {node: '>=12.0.0'}
|
| 486 |
+
|
| 487 |
+
'@serialport/[email protected]':
|
| 488 |
+
resolution: {integrity: sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==}
|
| 489 |
+
engines: {node: '>=12.0.0'}
|
| 490 |
+
|
| 491 |
+
'@serialport/[email protected]':
|
| 492 |
+
resolution: {integrity: sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==}
|
| 493 |
+
engines: {node: '>=12.0.0'}
|
| 494 |
+
|
| 495 |
+
'@serialport/[email protected]':
|
| 496 |
+
resolution: {integrity: sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==}
|
| 497 |
+
engines: {node: '>=12.0.0'}
|
| 498 |
+
|
| 499 |
+
'@serialport/[email protected]':
|
| 500 |
+
resolution: {integrity: sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==}
|
| 501 |
+
engines: {node: '>=12.0.0'}
|
| 502 |
+
|
| 503 |
+
'@types/[email protected]':
|
| 504 |
+
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
| 505 |
+
|
| 506 |
+
'@types/[email protected]':
|
| 507 |
+
resolution: {integrity: sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ==}
|
| 508 |
+
|
| 509 | |
| 510 |
+
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
| 511 |
+
engines: {node: '>=8'}
|
| 512 |
+
|
| 513 | |
| 514 |
+
resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
|
| 515 |
+
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
| 516 |
+
|
| 517 | |
| 518 |
+
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
|
| 519 |
+
engines: {node: '>=16'}
|
| 520 |
+
|
| 521 | |
| 522 |
+
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
| 523 |
+
engines: {node: '>=6.0'}
|
| 524 |
+
peerDependencies:
|
| 525 |
+
supports-color: '*'
|
| 526 |
+
peerDependenciesMeta:
|
| 527 |
+
supports-color:
|
| 528 |
+
optional: true
|
| 529 |
+
|
| 530 | |
| 531 |
+
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
| 532 |
+
engines: {node: '>=6.0'}
|
| 533 |
+
peerDependencies:
|
| 534 |
+
supports-color: '*'
|
| 535 |
+
peerDependenciesMeta:
|
| 536 |
+
supports-color:
|
| 537 |
+
optional: true
|
| 538 |
+
|
| 539 | |
| 540 |
+
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
| 541 |
+
|
| 542 | |
| 543 |
+
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
|
| 544 |
+
engines: {node: '>=12'}
|
| 545 |
+
hasBin: true
|
| 546 |
+
|
| 547 | |
| 548 |
+
resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==}
|
| 549 |
+
engines: {node: '>=18'}
|
| 550 |
+
hasBin: true
|
| 551 |
+
|
| 552 | |
| 553 |
+
resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
|
| 554 |
+
peerDependencies:
|
| 555 |
+
picomatch: ^3 || ^4
|
| 556 |
+
peerDependenciesMeta:
|
| 557 |
+
picomatch:
|
| 558 |
+
optional: true
|
| 559 |
+
|
| 560 | |
| 561 |
+
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
| 562 |
+
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
| 563 |
+
os: [darwin]
|
| 564 |
+
|
| 565 | |
| 566 |
+
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
| 567 |
+
|
| 568 | |
| 569 |
+
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
| 570 |
+
|
| 571 | |
| 572 |
+
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
| 573 |
+
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
| 574 |
+
hasBin: true
|
| 575 |
+
|
| 576 | |
| 577 |
+
resolution: {integrity: sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==}
|
| 578 |
+
|
| 579 | |
| 580 |
+
resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
|
| 581 |
+
hasBin: true
|
| 582 |
+
|
| 583 | |
| 584 |
+
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
|
| 585 |
+
|
| 586 | |
| 587 |
+
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
| 588 |
+
|
| 589 | |
| 590 |
+
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
| 591 |
+
engines: {node: '>=12'}
|
| 592 |
+
|
| 593 | |
| 594 |
+
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
| 595 |
+
engines: {node: ^10 || ^12 || >=14}
|
| 596 |
+
|
| 597 | |
| 598 |
+
resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==}
|
| 599 |
+
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
| 600 |
+
hasBin: true
|
| 601 |
+
|
| 602 | |
| 603 |
+
resolution: {integrity: sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==}
|
| 604 |
+
engines: {node: '>=16.0.0'}
|
| 605 |
+
|
| 606 | |
| 607 |
+
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
| 608 |
+
engines: {node: '>=0.10.0'}
|
| 609 |
+
|
| 610 | |
| 611 |
+
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
|
| 612 |
+
engines: {node: '>=12.0.0'}
|
| 613 |
+
|
| 614 | |
| 615 |
+
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
| 616 |
+
engines: {node: '>=14.17'}
|
| 617 |
+
hasBin: true
|
| 618 |
+
|
| 619 | |
| 620 |
+
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
| 621 |
+
|
| 622 | |
| 623 |
+
resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==}
|
| 624 |
+
engines: {node: ^18.0.0 || >=20.0.0}
|
| 625 |
+
hasBin: true
|
| 626 |
+
|
| 627 | |
| 628 |
+
resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==}
|
| 629 |
+
engines: {node: ^18.0.0 || >=20.0.0}
|
| 630 |
+
hasBin: true
|
| 631 |
+
peerDependencies:
|
| 632 |
+
'@types/node': ^18.0.0 || >=20.0.0
|
| 633 |
+
less: '*'
|
| 634 |
+
lightningcss: ^1.21.0
|
| 635 |
+
sass: '*'
|
| 636 |
+
sass-embedded: '*'
|
| 637 |
+
stylus: '*'
|
| 638 |
+
sugarss: '*'
|
| 639 |
+
terser: ^5.4.0
|
| 640 |
+
peerDependenciesMeta:
|
| 641 |
+
'@types/node':
|
| 642 |
+
optional: true
|
| 643 |
+
less:
|
| 644 |
+
optional: true
|
| 645 |
+
lightningcss:
|
| 646 |
+
optional: true
|
| 647 |
+
sass:
|
| 648 |
+
optional: true
|
| 649 |
+
sass-embedded:
|
| 650 |
+
optional: true
|
| 651 |
+
stylus:
|
| 652 |
+
optional: true
|
| 653 |
+
sugarss:
|
| 654 |
+
optional: true
|
| 655 |
+
terser:
|
| 656 |
+
optional: true
|
| 657 |
+
|
| 658 | |
| 659 |
+
resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==}
|
| 660 |
+
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
| 661 |
+
hasBin: true
|
| 662 |
+
peerDependencies:
|
| 663 |
+
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
|
| 664 |
+
jiti: '>=1.21.0'
|
| 665 |
+
less: '*'
|
| 666 |
+
lightningcss: ^1.21.0
|
| 667 |
+
sass: '*'
|
| 668 |
+
sass-embedded: '*'
|
| 669 |
+
stylus: '*'
|
| 670 |
+
sugarss: '*'
|
| 671 |
+
terser: ^5.16.0
|
| 672 |
+
tsx: ^4.8.1
|
| 673 |
+
yaml: ^2.4.2
|
| 674 |
+
peerDependenciesMeta:
|
| 675 |
+
'@types/node':
|
| 676 |
+
optional: true
|
| 677 |
+
jiti:
|
| 678 |
+
optional: true
|
| 679 |
+
less:
|
| 680 |
+
optional: true
|
| 681 |
+
lightningcss:
|
| 682 |
+
optional: true
|
| 683 |
+
sass:
|
| 684 |
+
optional: true
|
| 685 |
+
sass-embedded:
|
| 686 |
+
optional: true
|
| 687 |
+
stylus:
|
| 688 |
+
optional: true
|
| 689 |
+
sugarss:
|
| 690 |
+
optional: true
|
| 691 |
+
terser:
|
| 692 |
+
optional: true
|
| 693 |
+
tsx:
|
| 694 |
+
optional: true
|
| 695 |
+
yaml:
|
| 696 |
+
optional: true
|
| 697 |
+
|
| 698 |
+
snapshots:
|
| 699 |
+
|
| 700 |
+
'@esbuild/[email protected]':
|
| 701 |
+
optional: true
|
| 702 |
+
|
| 703 |
+
'@esbuild/[email protected]':
|
| 704 |
+
optional: true
|
| 705 |
+
|
| 706 |
+
'@esbuild/[email protected]':
|
| 707 |
+
optional: true
|
| 708 |
+
|
| 709 |
+
'@esbuild/[email protected]':
|
| 710 |
+
optional: true
|
| 711 |
+
|
| 712 |
+
'@esbuild/[email protected]':
|
| 713 |
+
optional: true
|
| 714 |
+
|
| 715 |
+
'@esbuild/[email protected]':
|
| 716 |
+
optional: true
|
| 717 |
+
|
| 718 |
+
'@esbuild/[email protected]':
|
| 719 |
+
optional: true
|
| 720 |
+
|
| 721 |
+
'@esbuild/[email protected]':
|
| 722 |
+
optional: true
|
| 723 |
+
|
| 724 |
+
'@esbuild/[email protected]':
|
| 725 |
+
optional: true
|
| 726 |
+
|
| 727 |
+
'@esbuild/[email protected]':
|
| 728 |
+
optional: true
|
| 729 |
+
|
| 730 |
+
'@esbuild/[email protected]':
|
| 731 |
+
optional: true
|
| 732 |
+
|
| 733 |
+
'@esbuild/[email protected]':
|
| 734 |
+
optional: true
|
| 735 |
+
|
| 736 |
+
'@esbuild/[email protected]':
|
| 737 |
+
optional: true
|
| 738 |
+
|
| 739 |
+
'@esbuild/[email protected]':
|
| 740 |
+
optional: true
|
| 741 |
+
|
| 742 |
+
'@esbuild/[email protected]':
|
| 743 |
+
optional: true
|
| 744 |
+
|
| 745 |
+
'@esbuild/[email protected]':
|
| 746 |
+
optional: true
|
| 747 |
+
|
| 748 |
+
'@esbuild/[email protected]':
|
| 749 |
+
optional: true
|
| 750 |
+
|
| 751 |
+
'@esbuild/[email protected]':
|
| 752 |
+
optional: true
|
| 753 |
+
|
| 754 |
+
'@esbuild/[email protected]':
|
| 755 |
+
optional: true
|
| 756 |
+
|
| 757 |
+
'@esbuild/[email protected]':
|
| 758 |
+
optional: true
|
| 759 |
+
|
| 760 |
+
'@esbuild/[email protected]':
|
| 761 |
+
optional: true
|
| 762 |
+
|
| 763 |
+
'@esbuild/[email protected]':
|
| 764 |
+
optional: true
|
| 765 |
+
|
| 766 |
+
'@esbuild/[email protected]':
|
| 767 |
+
optional: true
|
| 768 |
+
|
| 769 |
+
'@esbuild/[email protected]':
|
| 770 |
+
optional: true
|
| 771 |
+
|
| 772 |
+
'@esbuild/[email protected]':
|
| 773 |
+
optional: true
|
| 774 |
+
|
| 775 |
+
'@esbuild/[email protected]':
|
| 776 |
+
optional: true
|
| 777 |
+
|
| 778 |
+
'@esbuild/[email protected]':
|
| 779 |
+
optional: true
|
| 780 |
+
|
| 781 |
+
'@esbuild/[email protected]':
|
| 782 |
+
optional: true
|
| 783 |
+
|
| 784 |
+
'@esbuild/[email protected]':
|
| 785 |
+
optional: true
|
| 786 |
+
|
| 787 |
+
'@esbuild/[email protected]':
|
| 788 |
+
optional: true
|
| 789 |
+
|
| 790 |
+
'@esbuild/[email protected]':
|
| 791 |
+
optional: true
|
| 792 |
+
|
| 793 |
+
'@esbuild/[email protected]':
|
| 794 |
+
optional: true
|
| 795 |
+
|
| 796 |
+
'@esbuild/[email protected]':
|
| 797 |
+
optional: true
|
| 798 |
+
|
| 799 |
+
'@esbuild/[email protected]':
|
| 800 |
+
optional: true
|
| 801 |
+
|
| 802 |
+
'@esbuild/[email protected]':
|
| 803 |
+
optional: true
|
| 804 |
+
|
| 805 |
+
'@esbuild/[email protected]':
|
| 806 |
+
optional: true
|
| 807 |
+
|
| 808 |
+
'@esbuild/[email protected]':
|
| 809 |
+
optional: true
|
| 810 |
+
|
| 811 |
+
'@esbuild/[email protected]':
|
| 812 |
+
optional: true
|
| 813 |
+
|
| 814 |
+
'@esbuild/[email protected]':
|
| 815 |
+
optional: true
|
| 816 |
+
|
| 817 |
+
'@esbuild/[email protected]':
|
| 818 |
+
optional: true
|
| 819 |
+
|
| 820 |
+
'@esbuild/[email protected]':
|
| 821 |
+
optional: true
|
| 822 |
+
|
| 823 |
+
'@esbuild/[email protected]':
|
| 824 |
+
optional: true
|
| 825 |
+
|
| 826 |
+
'@esbuild/[email protected]':
|
| 827 |
+
optional: true
|
| 828 |
+
|
| 829 |
+
'@esbuild/[email protected]':
|
| 830 |
+
optional: true
|
| 831 |
+
|
| 832 |
+
'@esbuild/[email protected]':
|
| 833 |
+
optional: true
|
| 834 |
+
|
| 835 |
+
'@esbuild/[email protected]':
|
| 836 |
+
optional: true
|
| 837 |
+
|
| 838 |
+
'@esbuild/[email protected]':
|
| 839 |
+
optional: true
|
| 840 |
+
|
| 841 |
+
'@esbuild/[email protected]':
|
| 842 |
+
optional: true
|
| 843 |
+
|
| 844 |
+
'@esbuild/[email protected]':
|
| 845 |
+
optional: true
|
| 846 |
+
|
| 847 |
+
'@lerobot/node@file:../node([email protected])':
|
| 848 |
+
dependencies:
|
| 849 |
+
serialport: 12.0.0
|
| 850 |
+
typescript: 5.8.3
|
| 851 |
+
transitivePeerDependencies:
|
| 852 |
+
- supports-color
|
| 853 |
+
|
| 854 |
+
'@rollup/[email protected]':
|
| 855 |
+
optional: true
|
| 856 |
+
|
| 857 |
+
'@rollup/[email protected]':
|
| 858 |
+
optional: true
|
| 859 |
+
|
| 860 |
+
'@rollup/[email protected]':
|
| 861 |
+
optional: true
|
| 862 |
+
|
| 863 |
+
'@rollup/[email protected]':
|
| 864 |
+
optional: true
|
| 865 |
+
|
| 866 |
+
'@rollup/[email protected]':
|
| 867 |
+
optional: true
|
| 868 |
+
|
| 869 |
+
'@rollup/[email protected]':
|
| 870 |
+
optional: true
|
| 871 |
+
|
| 872 |
+
'@rollup/[email protected]':
|
| 873 |
+
optional: true
|
| 874 |
+
|
| 875 |
+
'@rollup/[email protected]':
|
| 876 |
+
optional: true
|
| 877 |
+
|
| 878 |
+
'@rollup/[email protected]':
|
| 879 |
+
optional: true
|
| 880 |
+
|
| 881 |
+
'@rollup/[email protected]':
|
| 882 |
+
optional: true
|
| 883 |
+
|
| 884 |
+
'@rollup/[email protected]':
|
| 885 |
+
optional: true
|
| 886 |
+
|
| 887 |
+
'@rollup/[email protected]':
|
| 888 |
+
optional: true
|
| 889 |
+
|
| 890 |
+
'@rollup/[email protected]':
|
| 891 |
+
optional: true
|
| 892 |
+
|
| 893 |
+
'@rollup/[email protected]':
|
| 894 |
+
optional: true
|
| 895 |
+
|
| 896 |
+
'@rollup/[email protected]':
|
| 897 |
+
optional: true
|
| 898 |
+
|
| 899 |
+
'@rollup/[email protected]':
|
| 900 |
+
optional: true
|
| 901 |
+
|
| 902 |
+
'@rollup/[email protected]':
|
| 903 |
+
optional: true
|
| 904 |
+
|
| 905 |
+
'@rollup/[email protected]':
|
| 906 |
+
optional: true
|
| 907 |
+
|
| 908 |
+
'@rollup/[email protected]':
|
| 909 |
+
optional: true
|
| 910 |
+
|
| 911 |
+
'@rollup/[email protected]':
|
| 912 |
+
optional: true
|
| 913 |
+
|
| 914 |
+
'@serialport/[email protected]':
|
| 915 |
+
dependencies:
|
| 916 |
+
'@serialport/bindings-interface': 1.2.2
|
| 917 |
+
debug: 4.4.1
|
| 918 |
+
transitivePeerDependencies:
|
| 919 |
+
- supports-color
|
| 920 |
+
|
| 921 |
+
'@serialport/[email protected]':
|
| 922 |
+
dependencies:
|
| 923 |
+
'@serialport/bindings-interface': 1.2.2
|
| 924 |
+
'@serialport/parser-readline': 11.0.0
|
| 925 |
+
debug: 4.3.4
|
| 926 |
+
node-addon-api: 7.0.0
|
| 927 |
+
node-gyp-build: 4.6.0
|
| 928 |
+
transitivePeerDependencies:
|
| 929 |
+
- supports-color
|
| 930 |
+
|
| 931 |
+
'@serialport/[email protected]': {}
|
| 932 |
+
|
| 933 |
+
'@serialport/[email protected]': {}
|
| 934 |
+
|
| 935 |
+
'@serialport/[email protected]': {}
|
| 936 |
+
|
| 937 |
+
'@serialport/[email protected]': {}
|
| 938 |
+
|
| 939 |
+
'@serialport/[email protected]': {}
|
| 940 |
+
|
| 941 |
+
'@serialport/[email protected]': {}
|
| 942 |
+
|
| 943 |
+
'@serialport/[email protected]': {}
|
| 944 |
+
|
| 945 |
+
'@serialport/[email protected]':
|
| 946 |
+
dependencies:
|
| 947 |
+
'@serialport/parser-delimiter': 11.0.0
|
| 948 |
+
|
| 949 |
+
'@serialport/[email protected]':
|
| 950 |
+
dependencies:
|
| 951 |
+
'@serialport/parser-delimiter': 12.0.0
|
| 952 |
+
|
| 953 |
+
'@serialport/[email protected]': {}
|
| 954 |
+
|
| 955 |
+
'@serialport/[email protected]': {}
|
| 956 |
+
|
| 957 |
+
'@serialport/[email protected]': {}
|
| 958 |
+
|
| 959 |
+
'@serialport/[email protected]': {}
|
| 960 |
+
|
| 961 |
+
'@serialport/[email protected]':
|
| 962 |
+
dependencies:
|
| 963 |
+
'@serialport/bindings-interface': 1.2.2
|
| 964 |
+
debug: 4.3.4
|
| 965 |
+
transitivePeerDependencies:
|
| 966 |
+
- supports-color
|
| 967 |
+
|
| 968 |
+
'@types/[email protected]': {}
|
| 969 |
+
|
| 970 |
+
'@types/[email protected]':
|
| 971 |
+
dependencies:
|
| 972 |
+
undici-types: 5.26.5
|
| 973 |
+
|
| 974 |
+
[email protected]: {}
|
| 975 |
+
|
| 976 |
+
[email protected]: {}
|
| 977 |
+
|
| 978 |
+
[email protected]: {}
|
| 979 |
+
|
| 980 | |
| 981 |
+
dependencies:
|
| 982 |
+
ms: 2.1.2
|
| 983 |
+
|
| 984 | |
| 985 |
+
dependencies:
|
| 986 |
+
ms: 2.1.3
|
| 987 |
+
|
| 988 |
+
[email protected]: {}
|
| 989 |
+
|
| 990 | |
| 991 |
+
optionalDependencies:
|
| 992 |
+
'@esbuild/aix-ppc64': 0.21.5
|
| 993 |
+
'@esbuild/android-arm': 0.21.5
|
| 994 |
+
'@esbuild/android-arm64': 0.21.5
|
| 995 |
+
'@esbuild/android-x64': 0.21.5
|
| 996 |
+
'@esbuild/darwin-arm64': 0.21.5
|
| 997 |
+
'@esbuild/darwin-x64': 0.21.5
|
| 998 |
+
'@esbuild/freebsd-arm64': 0.21.5
|
| 999 |
+
'@esbuild/freebsd-x64': 0.21.5
|
| 1000 |
+
'@esbuild/linux-arm': 0.21.5
|
| 1001 |
+
'@esbuild/linux-arm64': 0.21.5
|
| 1002 |
+
'@esbuild/linux-ia32': 0.21.5
|
| 1003 |
+
'@esbuild/linux-loong64': 0.21.5
|
| 1004 |
+
'@esbuild/linux-mips64el': 0.21.5
|
| 1005 |
+
'@esbuild/linux-ppc64': 0.21.5
|
| 1006 |
+
'@esbuild/linux-riscv64': 0.21.5
|
| 1007 |
+
'@esbuild/linux-s390x': 0.21.5
|
| 1008 |
+
'@esbuild/linux-x64': 0.21.5
|
| 1009 |
+
'@esbuild/netbsd-x64': 0.21.5
|
| 1010 |
+
'@esbuild/openbsd-x64': 0.21.5
|
| 1011 |
+
'@esbuild/sunos-x64': 0.21.5
|
| 1012 |
+
'@esbuild/win32-arm64': 0.21.5
|
| 1013 |
+
'@esbuild/win32-ia32': 0.21.5
|
| 1014 |
+
'@esbuild/win32-x64': 0.21.5
|
| 1015 |
+
|
| 1016 | |
| 1017 |
+
optionalDependencies:
|
| 1018 |
+
'@esbuild/aix-ppc64': 0.25.8
|
| 1019 |
+
'@esbuild/android-arm': 0.25.8
|
| 1020 |
+
'@esbuild/android-arm64': 0.25.8
|
| 1021 |
+
'@esbuild/android-x64': 0.25.8
|
| 1022 |
+
'@esbuild/darwin-arm64': 0.25.8
|
| 1023 |
+
'@esbuild/darwin-x64': 0.25.8
|
| 1024 |
+
'@esbuild/freebsd-arm64': 0.25.8
|
| 1025 |
+
'@esbuild/freebsd-x64': 0.25.8
|
| 1026 |
+
'@esbuild/linux-arm': 0.25.8
|
| 1027 |
+
'@esbuild/linux-arm64': 0.25.8
|
| 1028 |
+
'@esbuild/linux-ia32': 0.25.8
|
| 1029 |
+
'@esbuild/linux-loong64': 0.25.8
|
| 1030 |
+
'@esbuild/linux-mips64el': 0.25.8
|
| 1031 |
+
'@esbuild/linux-ppc64': 0.25.8
|
| 1032 |
+
'@esbuild/linux-riscv64': 0.25.8
|
| 1033 |
+
'@esbuild/linux-s390x': 0.25.8
|
| 1034 |
+
'@esbuild/linux-x64': 0.25.8
|
| 1035 |
+
'@esbuild/netbsd-arm64': 0.25.8
|
| 1036 |
+
'@esbuild/netbsd-x64': 0.25.8
|
| 1037 |
+
'@esbuild/openbsd-arm64': 0.25.8
|
| 1038 |
+
'@esbuild/openbsd-x64': 0.25.8
|
| 1039 |
+
'@esbuild/openharmony-arm64': 0.25.8
|
| 1040 |
+
'@esbuild/sunos-x64': 0.25.8
|
| 1041 |
+
'@esbuild/win32-arm64': 0.25.8
|
| 1042 |
+
'@esbuild/win32-ia32': 0.25.8
|
| 1043 |
+
'@esbuild/win32-x64': 0.25.8
|
| 1044 |
+
|
| 1045 | |
| 1046 |
+
optionalDependencies:
|
| 1047 |
+
picomatch: 4.0.3
|
| 1048 |
+
|
| 1049 | |
| 1050 |
+
optional: true
|
| 1051 |
+
|
| 1052 |
+
[email protected]: {}
|
| 1053 |
+
|
| 1054 |
+
[email protected]: {}
|
| 1055 |
+
|
| 1056 |
+
[email protected]: {}
|
| 1057 |
+
|
| 1058 |
+
[email protected]: {}
|
| 1059 |
+
|
| 1060 |
+
[email protected]: {}
|
| 1061 |
+
|
| 1062 |
+
[email protected]: {}
|
| 1063 |
+
|
| 1064 |
+
[email protected]: {}
|
| 1065 |
+
|
| 1066 |
+
[email protected]: {}
|
| 1067 |
+
|
| 1068 | |
| 1069 |
+
dependencies:
|
| 1070 |
+
nanoid: 3.3.11
|
| 1071 |
+
picocolors: 1.1.1
|
| 1072 |
+
source-map-js: 1.2.1
|
| 1073 |
+
|
| 1074 | |
| 1075 |
+
dependencies:
|
| 1076 |
+
'@types/estree': 1.0.8
|
| 1077 |
+
optionalDependencies:
|
| 1078 |
+
'@rollup/rollup-android-arm-eabi': 4.46.2
|
| 1079 |
+
'@rollup/rollup-android-arm64': 4.46.2
|
| 1080 |
+
'@rollup/rollup-darwin-arm64': 4.46.2
|
| 1081 |
+
'@rollup/rollup-darwin-x64': 4.46.2
|
| 1082 |
+
'@rollup/rollup-freebsd-arm64': 4.46.2
|
| 1083 |
+
'@rollup/rollup-freebsd-x64': 4.46.2
|
| 1084 |
+
'@rollup/rollup-linux-arm-gnueabihf': 4.46.2
|
| 1085 |
+
'@rollup/rollup-linux-arm-musleabihf': 4.46.2
|
| 1086 |
+
'@rollup/rollup-linux-arm64-gnu': 4.46.2
|
| 1087 |
+
'@rollup/rollup-linux-arm64-musl': 4.46.2
|
| 1088 |
+
'@rollup/rollup-linux-loongarch64-gnu': 4.46.2
|
| 1089 |
+
'@rollup/rollup-linux-ppc64-gnu': 4.46.2
|
| 1090 |
+
'@rollup/rollup-linux-riscv64-gnu': 4.46.2
|
| 1091 |
+
'@rollup/rollup-linux-riscv64-musl': 4.46.2
|
| 1092 |
+
'@rollup/rollup-linux-s390x-gnu': 4.46.2
|
| 1093 |
+
'@rollup/rollup-linux-x64-gnu': 4.46.2
|
| 1094 |
+
'@rollup/rollup-linux-x64-musl': 4.46.2
|
| 1095 |
+
'@rollup/rollup-win32-arm64-msvc': 4.46.2
|
| 1096 |
+
'@rollup/rollup-win32-ia32-msvc': 4.46.2
|
| 1097 |
+
'@rollup/rollup-win32-x64-msvc': 4.46.2
|
| 1098 |
+
fsevents: 2.3.3
|
| 1099 |
+
|
| 1100 | |
| 1101 |
+
dependencies:
|
| 1102 |
+
'@serialport/binding-mock': 10.2.2
|
| 1103 |
+
'@serialport/bindings-cpp': 12.0.1
|
| 1104 |
+
'@serialport/parser-byte-length': 12.0.0
|
| 1105 |
+
'@serialport/parser-cctalk': 12.0.0
|
| 1106 |
+
'@serialport/parser-delimiter': 12.0.0
|
| 1107 |
+
'@serialport/parser-inter-byte-timeout': 12.0.0
|
| 1108 |
+
'@serialport/parser-packet-length': 12.0.0
|
| 1109 |
+
'@serialport/parser-readline': 12.0.0
|
| 1110 |
+
'@serialport/parser-ready': 12.0.0
|
| 1111 |
+
'@serialport/parser-regex': 12.0.0
|
| 1112 |
+
'@serialport/parser-slip-encoder': 12.0.0
|
| 1113 |
+
'@serialport/parser-spacepacket': 12.0.0
|
| 1114 |
+
'@serialport/stream': 12.0.0
|
| 1115 |
+
debug: 4.3.4
|
| 1116 |
+
transitivePeerDependencies:
|
| 1117 |
+
- supports-color
|
| 1118 |
+
|
| 1119 |
+
[email protected]: {}
|
| 1120 |
+
|
| 1121 | |
| 1122 |
+
dependencies:
|
| 1123 |
+
fdir: 6.4.6([email protected])
|
| 1124 |
+
picomatch: 4.0.3
|
| 1125 |
+
|
| 1126 |
+
[email protected]: {}
|
| 1127 |
+
|
| 1128 |
+
[email protected]: {}
|
| 1129 |
+
|
| 1130 |
+
[email protected](@types/[email protected]):
|
| 1131 |
+
dependencies:
|
| 1132 |
+
cac: 6.7.14
|
| 1133 |
+
debug: 4.4.1
|
| 1134 |
+
es-module-lexer: 1.7.0
|
| 1135 |
+
pathe: 1.1.2
|
| 1136 |
+
vite: 5.4.19(@types/[email protected])
|
| 1137 |
+
transitivePeerDependencies:
|
| 1138 |
+
- '@types/node'
|
| 1139 |
+
- less
|
| 1140 |
+
- lightningcss
|
| 1141 |
+
- sass
|
| 1142 |
+
- sass-embedded
|
| 1143 |
+
- stylus
|
| 1144 |
+
- sugarss
|
| 1145 |
+
- supports-color
|
| 1146 |
+
- terser
|
| 1147 |
+
|
| 1148 |
+
[email protected](@types/[email protected]):
|
| 1149 |
+
dependencies:
|
| 1150 |
+
esbuild: 0.21.5
|
| 1151 |
+
postcss: 8.5.6
|
| 1152 |
+
rollup: 4.46.2
|
| 1153 |
+
optionalDependencies:
|
| 1154 |
+
'@types/node': 18.19.121
|
| 1155 |
+
fsevents: 2.3.3
|
| 1156 |
+
|
| 1157 |
+
[email protected](@types/[email protected]):
|
| 1158 |
+
dependencies:
|
| 1159 |
+
esbuild: 0.25.8
|
| 1160 |
+
fdir: 6.4.6([email protected])
|
| 1161 |
+
picomatch: 4.0.3
|
| 1162 |
+
postcss: 8.5.6
|
| 1163 |
+
rollup: 4.46.2
|
| 1164 |
+
tinyglobby: 0.2.14
|
| 1165 |
+
optionalDependencies:
|
| 1166 |
+
'@types/node': 18.19.121
|
| 1167 |
+
fsevents: 2.3.3
|
packages/cli/src/cli.ts
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* lerobot CLI - Python lerobot compatible command-line interface
|
| 5 |
+
* Uses @lerobot/node library for core functionality with CLI-specific interactive features
|
| 6 |
+
*/
|
| 7 |
+
|
| 8 |
+
import { program } from "commander";
|
| 9 |
+
import chalk from "chalk";
|
| 10 |
+
import {
|
| 11 |
+
findPort,
|
| 12 |
+
calibrate,
|
| 13 |
+
teleoperate,
|
| 14 |
+
releaseMotors,
|
| 15 |
+
connectPort,
|
| 16 |
+
} from "@lerobot/node";
|
| 17 |
+
import type { RobotConnection } from "@lerobot/node";
|
| 18 |
+
import { SerialPort } from "serialport";
|
| 19 |
+
import { createInterface } from "readline";
|
| 20 |
+
import { platform } from "os";
|
| 21 |
+
import { readdir } from "fs/promises";
|
| 22 |
+
import { join } from "path";
|
| 23 |
+
|
| 24 |
+
/**
|
| 25 |
+
* CLI-specific function to list available serial ports
|
| 26 |
+
* Only used by the CLI, not part of the library API
|
| 27 |
+
*/
|
| 28 |
+
async function findAvailablePorts(): Promise<string[]> {
|
| 29 |
+
if (platform() === "win32") {
|
| 30 |
+
// List COM ports using serialport library (equivalent to pyserial)
|
| 31 |
+
const ports = await SerialPort.list();
|
| 32 |
+
return ports.map((port) => port.path);
|
| 33 |
+
} else {
|
| 34 |
+
// List /dev/tty* ports for Unix-based systems (Linux/macOS)
|
| 35 |
+
try {
|
| 36 |
+
const devFiles = await readdir("/dev");
|
| 37 |
+
const ttyPorts = devFiles
|
| 38 |
+
.filter((file) => file.startsWith("tty"))
|
| 39 |
+
.map((file) => join("/dev", file));
|
| 40 |
+
return ttyPorts;
|
| 41 |
+
} catch (error) {
|
| 42 |
+
// Fallback to serialport library if /dev reading fails
|
| 43 |
+
const ports = await SerialPort.list();
|
| 44 |
+
return ports.map((port) => port.path);
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
/**
|
| 50 |
+
* CLI-specific interactive port detection for Python lerobot compatibility
|
| 51 |
+
* Matches Python lerobot's unplug/replug cable detection exactly
|
| 52 |
+
*/
|
| 53 |
+
async function detectPortInteractive(
|
| 54 |
+
onMessage?: (message: string) => void
|
| 55 |
+
): Promise<string> {
|
| 56 |
+
const rl = createInterface({
|
| 57 |
+
input: process.stdin,
|
| 58 |
+
output: process.stdout,
|
| 59 |
+
});
|
| 60 |
+
|
| 61 |
+
function waitForInput(prompt: string): Promise<string> {
|
| 62 |
+
return new Promise((resolve) => {
|
| 63 |
+
rl.question(prompt, (answer: string) => {
|
| 64 |
+
resolve(answer);
|
| 65 |
+
});
|
| 66 |
+
});
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
try {
|
| 70 |
+
const message = "Finding all available ports for the MotorsBus.";
|
| 71 |
+
if (onMessage) onMessage(message);
|
| 72 |
+
else console.log(message);
|
| 73 |
+
|
| 74 |
+
// Get initial port list
|
| 75 |
+
const portsBefore = await findAvailablePorts();
|
| 76 |
+
|
| 77 |
+
// Show initial ports (Python lerobot style)
|
| 78 |
+
const portsMessage = `Ports before disconnecting: [${portsBefore
|
| 79 |
+
.map((p) => `'${p}'`)
|
| 80 |
+
.join(", ")}]`;
|
| 81 |
+
if (onMessage) onMessage(portsMessage);
|
| 82 |
+
else console.log(portsMessage);
|
| 83 |
+
|
| 84 |
+
const disconnectPrompt =
|
| 85 |
+
"Remove the USB cable from your MotorsBus and press Enter when done.";
|
| 86 |
+
await waitForInput(disconnectPrompt);
|
| 87 |
+
|
| 88 |
+
// Get port list after disconnect
|
| 89 |
+
const portsAfter = await findAvailablePorts();
|
| 90 |
+
|
| 91 |
+
// Find the difference
|
| 92 |
+
const portsDiff = portsBefore.filter((port) => !portsAfter.includes(port));
|
| 93 |
+
|
| 94 |
+
if (portsDiff.length === 1) {
|
| 95 |
+
const detectedPort = portsDiff[0];
|
| 96 |
+
|
| 97 |
+
// Show empty line then the result (Python lerobot style)
|
| 98 |
+
if (onMessage) {
|
| 99 |
+
onMessage("");
|
| 100 |
+
onMessage(`The port of this MotorsBus is '${detectedPort}'`);
|
| 101 |
+
onMessage("Reconnect the USB cable.");
|
| 102 |
+
} else {
|
| 103 |
+
console.log("");
|
| 104 |
+
console.log(`The port of this MotorsBus is '${detectedPort}'`);
|
| 105 |
+
console.log("Reconnect the USB cable.");
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
return detectedPort;
|
| 109 |
+
} else if (portsDiff.length === 0) {
|
| 110 |
+
throw new Error(
|
| 111 |
+
"No port difference detected. Please check cable connection."
|
| 112 |
+
);
|
| 113 |
+
} else {
|
| 114 |
+
throw new Error(
|
| 115 |
+
`Multiple ports detected: ${portsDiff.join(
|
| 116 |
+
", "
|
| 117 |
+
)}. Please disconnect other devices.`
|
| 118 |
+
);
|
| 119 |
+
}
|
| 120 |
+
} finally {
|
| 121 |
+
rl.close();
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
/**
|
| 126 |
+
* Create robot connection directly from specified port (Python lerobot style)
|
| 127 |
+
*/
|
| 128 |
+
async function connectToSpecificPort(
|
| 129 |
+
portPath: string,
|
| 130 |
+
robotType: string,
|
| 131 |
+
robotId: string
|
| 132 |
+
): Promise<RobotConnection> {
|
| 133 |
+
console.log(chalk.gray(`📡 Connecting to ${portPath}...`));
|
| 134 |
+
|
| 135 |
+
const connection = await connectPort(portPath);
|
| 136 |
+
|
| 137 |
+
if (!connection.isConnected) {
|
| 138 |
+
throw new Error(
|
| 139 |
+
`Failed to connect to port ${portPath}: ${connection.error}`
|
| 140 |
+
);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
// Configure the robot with CLI parameters
|
| 144 |
+
connection.robotType = robotType;
|
| 145 |
+
connection.robotId = robotId;
|
| 146 |
+
connection.name = `${robotType} on ${portPath}`;
|
| 147 |
+
|
| 148 |
+
console.log(chalk.green(`✅ Connected to ${robotType} on ${portPath}`));
|
| 149 |
+
return connection;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
/**
|
| 153 |
+
* Find port command - matches Python lerobot CLI exactly
|
| 154 |
+
* Always interactive by default (like Python lerobot)
|
| 155 |
+
*/
|
| 156 |
+
program
|
| 157 |
+
.command("find-port")
|
| 158 |
+
.description(
|
| 159 |
+
"Find robot port with interactive cable detection (Python lerobot compatible)"
|
| 160 |
+
)
|
| 161 |
+
.action(async () => {
|
| 162 |
+
try {
|
| 163 |
+
console.log(chalk.blue("🔍 Finding robot port..."));
|
| 164 |
+
|
| 165 |
+
// Always use interactive cable detection (Python lerobot behavior)
|
| 166 |
+
await detectPortInteractive((message) =>
|
| 167 |
+
console.log(chalk.gray(message))
|
| 168 |
+
);
|
| 169 |
+
// No additional success message - detectPortInteractive already shows the result
|
| 170 |
+
} catch (error) {
|
| 171 |
+
console.error(
|
| 172 |
+
chalk.red(`❌ Error: ${error instanceof Error ? error.message : error}`)
|
| 173 |
+
);
|
| 174 |
+
process.exit(1);
|
| 175 |
+
}
|
| 176 |
+
});
|
| 177 |
+
|
| 178 |
+
/**
|
| 179 |
+
* Calibrate command - matches Python lerobot exactly
|
| 180 |
+
*/
|
| 181 |
+
program
|
| 182 |
+
.command("calibrate")
|
| 183 |
+
.description("Calibrate robot motors")
|
| 184 |
+
.requiredOption("--robot.type <type>", "Robot type (e.g., so100_follower)")
|
| 185 |
+
.requiredOption(
|
| 186 |
+
"--robot.port <port>",
|
| 187 |
+
"Serial port (e.g., /dev/ttyUSB0, COM4)"
|
| 188 |
+
)
|
| 189 |
+
.option("--robot.id <id>", "Robot ID", "default")
|
| 190 |
+
.option("--output <path>", "Output calibration file path")
|
| 191 |
+
.action(async (options) => {
|
| 192 |
+
try {
|
| 193 |
+
const robotType = options["robot.type"];
|
| 194 |
+
const robotPort = options["robot.port"];
|
| 195 |
+
const robotId = options["robot.id"] || "default";
|
| 196 |
+
|
| 197 |
+
console.log(chalk.blue(`🔧 Starting calibration for ${robotType}...`));
|
| 198 |
+
|
| 199 |
+
// Step 1: Connect directly to specified port (Python lerobot style)
|
| 200 |
+
const robot = await connectToSpecificPort(robotPort, robotType, robotId);
|
| 201 |
+
|
| 202 |
+
// Step 2: Release motors
|
| 203 |
+
console.log(chalk.gray("🔓 Releasing motors for calibration setup..."));
|
| 204 |
+
await releaseMotors(robot);
|
| 205 |
+
console.log(
|
| 206 |
+
chalk.green("✅ Motors released - robot can now be moved by hand")
|
| 207 |
+
);
|
| 208 |
+
|
| 209 |
+
// Step 3: Wait for user to position robot
|
| 210 |
+
console.log(
|
| 211 |
+
chalk.yellow(
|
| 212 |
+
"\n📍 Move robot to your preferred starting position, then press Enter..."
|
| 213 |
+
)
|
| 214 |
+
);
|
| 215 |
+
const rl = createInterface({
|
| 216 |
+
input: process.stdin,
|
| 217 |
+
output: process.stdout,
|
| 218 |
+
});
|
| 219 |
+
|
| 220 |
+
await new Promise<void>((resolve) => {
|
| 221 |
+
rl.question("", () => {
|
| 222 |
+
rl.close();
|
| 223 |
+
resolve();
|
| 224 |
+
});
|
| 225 |
+
});
|
| 226 |
+
|
| 227 |
+
console.log(chalk.blue("\n🎯 Starting calibration process..."));
|
| 228 |
+
const calibrationProcess = await calibrate({
|
| 229 |
+
robot,
|
| 230 |
+
outputPath: options.output,
|
| 231 |
+
onProgress: (message) => console.log(chalk.gray(message)),
|
| 232 |
+
onLiveUpdate: (data) => {
|
| 233 |
+
// Clear previous output and display live data as table
|
| 234 |
+
process.stdout.write("\x1B[2J\x1B[0f"); // Clear screen and move cursor to top
|
| 235 |
+
|
| 236 |
+
console.log(chalk.cyan("📊 Live Motor Data:"));
|
| 237 |
+
console.log(
|
| 238 |
+
"┌─────────────────┬─────────┬─────────┬─────────┬─────────┐"
|
| 239 |
+
);
|
| 240 |
+
console.log(
|
| 241 |
+
"│ Motor │ Current │ Min │ Max │ Range │"
|
| 242 |
+
);
|
| 243 |
+
console.log(
|
| 244 |
+
"├─────────────────┼─────────┼─────────┼─────────┼─────────┤"
|
| 245 |
+
);
|
| 246 |
+
|
| 247 |
+
Object.entries(data).forEach(([name, info]) => {
|
| 248 |
+
const motorName = name.padEnd(15);
|
| 249 |
+
const current = info.current.toString().padStart(7);
|
| 250 |
+
const min = info.min.toString().padStart(7);
|
| 251 |
+
const max = info.max.toString().padStart(7);
|
| 252 |
+
const range = info.range.toString().padStart(7);
|
| 253 |
+
console.log(
|
| 254 |
+
`│ ${motorName} │ ${current} │ ${min} │ ${max} │ ${range} │`
|
| 255 |
+
);
|
| 256 |
+
});
|
| 257 |
+
|
| 258 |
+
console.log(
|
| 259 |
+
"└─────────────────┴─────────┴─────────┴─────────┴─────────┘"
|
| 260 |
+
);
|
| 261 |
+
console.log(
|
| 262 |
+
chalk.yellow(
|
| 263 |
+
"Move motors through full range, then press Enter when done..."
|
| 264 |
+
)
|
| 265 |
+
);
|
| 266 |
+
},
|
| 267 |
+
});
|
| 268 |
+
|
| 269 |
+
const results = await calibrationProcess.result;
|
| 270 |
+
console.log(chalk.green("\n✅ Calibration completed successfully!"));
|
| 271 |
+
|
| 272 |
+
// CRITICAL: Close robot connection to allow process to exit
|
| 273 |
+
if (robot.port && robot.port.close) {
|
| 274 |
+
await robot.port.close();
|
| 275 |
+
}
|
| 276 |
+
} catch (error) {
|
| 277 |
+
console.error(
|
| 278 |
+
chalk.red(
|
| 279 |
+
`❌ Calibration failed: ${
|
| 280 |
+
error instanceof Error ? error.message : error
|
| 281 |
+
}`
|
| 282 |
+
)
|
| 283 |
+
);
|
| 284 |
+
|
| 285 |
+
// Close robot connection even on error
|
| 286 |
+
try {
|
| 287 |
+
if (robot && robot.port && robot.port.close) {
|
| 288 |
+
await robot.port.close();
|
| 289 |
+
}
|
| 290 |
+
} catch (closeError) {
|
| 291 |
+
// Ignore close errors
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
process.exit(1);
|
| 295 |
+
}
|
| 296 |
+
});
|
| 297 |
+
|
| 298 |
+
/**
|
| 299 |
+
* Teleoperate command - matches Python lerobot exactly
|
| 300 |
+
*/
|
| 301 |
+
program
|
| 302 |
+
.command("teleoperate")
|
| 303 |
+
.alias("teleop")
|
| 304 |
+
.description("Control robot through teleoperation")
|
| 305 |
+
.requiredOption("--robot.type <type>", "Robot type (e.g., so100_follower)")
|
| 306 |
+
.requiredOption(
|
| 307 |
+
"--robot.port <port>",
|
| 308 |
+
"Serial port (e.g., /dev/ttyUSB0, COM4)"
|
| 309 |
+
)
|
| 310 |
+
.option("--robot.id <id>", "Robot ID", "default")
|
| 311 |
+
.option("--teleop.type <type>", "Teleoperator type", "keyboard")
|
| 312 |
+
.option("--duration <seconds>", "Duration in seconds (0 = unlimited)", "0")
|
| 313 |
+
.action(async (options) => {
|
| 314 |
+
try {
|
| 315 |
+
const robotType = options["robot.type"];
|
| 316 |
+
const robotPort = options["robot.port"];
|
| 317 |
+
const robotId = options["robot.id"] || "default";
|
| 318 |
+
const teleopType = options["teleop.type"] || "keyboard";
|
| 319 |
+
|
| 320 |
+
console.log(chalk.blue(`🎮 Starting teleoperation for ${robotType}...`));
|
| 321 |
+
|
| 322 |
+
// Connect directly to specified port (Python lerobot style)
|
| 323 |
+
const robot = await connectToSpecificPort(robotPort, robotType, robotId);
|
| 324 |
+
|
| 325 |
+
const teleoperationProcess = await teleoperate({
|
| 326 |
+
robot,
|
| 327 |
+
teleop: {
|
| 328 |
+
type: teleopType,
|
| 329 |
+
},
|
| 330 |
+
onStateUpdate: (state) => {
|
| 331 |
+
if (state.isActive) {
|
| 332 |
+
const motorInfo = state.motorConfigs
|
| 333 |
+
.map(
|
| 334 |
+
(motor) => `${motor.name}:${Math.round(motor.currentPosition)}`
|
| 335 |
+
)
|
| 336 |
+
.join(" ");
|
| 337 |
+
process.stdout.write(`\r${chalk.cyan("🤖 Motors:")} ${motorInfo}`);
|
| 338 |
+
}
|
| 339 |
+
},
|
| 340 |
+
});
|
| 341 |
+
|
| 342 |
+
// Start teleoperation
|
| 343 |
+
teleoperationProcess.start();
|
| 344 |
+
|
| 345 |
+
// Handle duration limit
|
| 346 |
+
const duration = parseInt(options.duration || "0");
|
| 347 |
+
if (duration > 0) {
|
| 348 |
+
setTimeout(() => {
|
| 349 |
+
console.log(
|
| 350 |
+
chalk.yellow(
|
| 351 |
+
`\n⏰ Duration limit reached (${duration}s). Stopping...`
|
| 352 |
+
)
|
| 353 |
+
);
|
| 354 |
+
teleoperationProcess.stop();
|
| 355 |
+
process.exit(0);
|
| 356 |
+
}, duration * 1000);
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
// Handle process termination
|
| 360 |
+
process.on("SIGINT", async () => {
|
| 361 |
+
console.log(chalk.yellow("\n🛑 Stopping teleoperation..."));
|
| 362 |
+
teleoperationProcess.stop();
|
| 363 |
+
await teleoperationProcess.disconnect();
|
| 364 |
+
process.exit(0);
|
| 365 |
+
});
|
| 366 |
+
|
| 367 |
+
console.log(chalk.green("✅ Teleoperation started successfully!"));
|
| 368 |
+
console.log(chalk.gray("Press Ctrl+C to stop"));
|
| 369 |
+
} catch (error) {
|
| 370 |
+
console.error(
|
| 371 |
+
chalk.red(
|
| 372 |
+
`❌ Teleoperation failed: ${
|
| 373 |
+
error instanceof Error ? error.message : error
|
| 374 |
+
}`
|
| 375 |
+
)
|
| 376 |
+
);
|
| 377 |
+
process.exit(1);
|
| 378 |
+
}
|
| 379 |
+
});
|
| 380 |
+
|
| 381 |
+
/**
|
| 382 |
+
* Release motors command
|
| 383 |
+
*/
|
| 384 |
+
program
|
| 385 |
+
.command("release-motors")
|
| 386 |
+
.description("Release robot motors for manual movement")
|
| 387 |
+
.requiredOption("--robot.type <type>", "Robot type (e.g., so100_follower)")
|
| 388 |
+
.requiredOption(
|
| 389 |
+
"--robot.port <port>",
|
| 390 |
+
"Serial port (e.g., /dev/ttyUSB0, COM4)"
|
| 391 |
+
)
|
| 392 |
+
.option("--robot.id <id>", "Robot ID", "default")
|
| 393 |
+
.option("--motors <ids>", "Specific motor IDs to release (comma-separated)")
|
| 394 |
+
.action(async (options) => {
|
| 395 |
+
try {
|
| 396 |
+
const robotType = options["robot.type"];
|
| 397 |
+
const robotPort = options["robot.port"];
|
| 398 |
+
const robotId = options["robot.id"] || "default";
|
| 399 |
+
|
| 400 |
+
console.log(chalk.blue(`🔓 Releasing motors for ${robotType}...`));
|
| 401 |
+
|
| 402 |
+
// Connect directly to specified port (Python lerobot style)
|
| 403 |
+
const robot = await connectToSpecificPort(robotPort, robotType, robotId);
|
| 404 |
+
|
| 405 |
+
const motorIds = options.motors
|
| 406 |
+
? options.motors.split(",").map((id: string) => parseInt(id.trim()))
|
| 407 |
+
: undefined;
|
| 408 |
+
|
| 409 |
+
await releaseMotors(robot, motorIds);
|
| 410 |
+
|
| 411 |
+
console.log(chalk.green("✅ Motors released successfully!"));
|
| 412 |
+
console.log(chalk.gray("Motors can now be moved freely by hand."));
|
| 413 |
+
} catch (error) {
|
| 414 |
+
console.error(
|
| 415 |
+
chalk.red(
|
| 416 |
+
`❌ Failed to release motors: ${
|
| 417 |
+
error instanceof Error ? error.message : error
|
| 418 |
+
}`
|
| 419 |
+
)
|
| 420 |
+
);
|
| 421 |
+
process.exit(1);
|
| 422 |
+
}
|
| 423 |
+
});
|
| 424 |
+
|
| 425 |
+
/**
|
| 426 |
+
* Version and help setup
|
| 427 |
+
*/
|
| 428 |
+
program
|
| 429 |
+
.name("lerobot")
|
| 430 |
+
.description("Node.js robotics control CLI - Python lerobot compatible")
|
| 431 |
+
.version("0.1.0");
|
| 432 |
+
|
| 433 |
+
/**
|
| 434 |
+
* Parse CLI arguments and run
|
| 435 |
+
*/
|
| 436 |
+
program.parse();
|
| 437 |
+
|
| 438 |
+
// Show help if no command provided
|
| 439 |
+
if (!process.argv.slice(2).length) {
|
| 440 |
+
program.outputHelp();
|
| 441 |
+
}
|
packages/cli/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @lerobot/cli - Python lerobot compatible CLI commands
|
| 3 |
+
*
|
| 4 |
+
* This package provides CLI commands that match Python lerobot exactly:
|
| 5 |
+
* - lerobot find-port (with interactive cable detection)
|
| 6 |
+
* - lerobot calibrate
|
| 7 |
+
* - lerobot teleoperate
|
| 8 |
+
* - lerobot release-motors
|
| 9 |
+
*
|
| 10 |
+
* Uses @lerobot/node library for core functionality
|
| 11 |
+
*/
|
| 12 |
+
|
| 13 |
+
export * from "./cli.js";
|
packages/cli/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2022",
|
| 4 |
+
"module": "ESNext",
|
| 5 |
+
"moduleResolution": "Bundler",
|
| 6 |
+
"outDir": "./dist",
|
| 7 |
+
"declaration": true,
|
| 8 |
+
"declarationMap": true,
|
| 9 |
+
"sourceMap": true,
|
| 10 |
+
"removeComments": true,
|
| 11 |
+
"noEmit": false,
|
| 12 |
+
"strict": true,
|
| 13 |
+
"esModuleInterop": true,
|
| 14 |
+
"skipLibCheck": true,
|
| 15 |
+
"forceConsistentCasingInFileNames": true,
|
| 16 |
+
"types": ["node"]
|
| 17 |
+
},
|
| 18 |
+
"include": ["src/**/*"],
|
| 19 |
+
"exclude": ["src/**/*.test.ts", "dist", "node_modules"]
|
| 20 |
+
}
|
packages/cli/vite.config.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig } from "vite";
|
| 2 |
+
|
| 3 |
+
export default defineConfig({
|
| 4 |
+
build: {
|
| 5 |
+
target: "node18",
|
| 6 |
+
lib: {
|
| 7 |
+
entry: {
|
| 8 |
+
cli: "src/cli.ts",
|
| 9 |
+
index: "src/index.ts",
|
| 10 |
+
},
|
| 11 |
+
formats: ["es"],
|
| 12 |
+
},
|
| 13 |
+
rollupOptions: {
|
| 14 |
+
external: [
|
| 15 |
+
// Node.js built-ins
|
| 16 |
+
"fs",
|
| 17 |
+
"fs/promises",
|
| 18 |
+
"path",
|
| 19 |
+
"os",
|
| 20 |
+
"readline",
|
| 21 |
+
"util",
|
| 22 |
+
"events",
|
| 23 |
+
"stream",
|
| 24 |
+
// External dependencies
|
| 25 |
+
"@lerobot/node",
|
| 26 |
+
"chalk",
|
| 27 |
+
"commander",
|
| 28 |
+
"serialport",
|
| 29 |
+
],
|
| 30 |
+
},
|
| 31 |
+
minify: false,
|
| 32 |
+
sourcemap: true,
|
| 33 |
+
},
|
| 34 |
+
});
|
packages/node/.eslintrc.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"root": true,
|
| 3 |
+
"parser": "@typescript-eslint/parser",
|
| 4 |
+
"plugins": ["@typescript-eslint"],
|
| 5 |
+
"extends": ["eslint:recommended", "@typescript-eslint/recommended"],
|
| 6 |
+
"parserOptions": {
|
| 7 |
+
"ecmaVersion": 2022,
|
| 8 |
+
"sourceType": "module"
|
| 9 |
+
},
|
| 10 |
+
"env": {
|
| 11 |
+
"node": true,
|
| 12 |
+
"es2022": true
|
| 13 |
+
},
|
| 14 |
+
"rules": {
|
| 15 |
+
"@typescript-eslint/no-unused-vars": [
|
| 16 |
+
"error",
|
| 17 |
+
{ "argsIgnorePattern": "^_" }
|
| 18 |
+
],
|
| 19 |
+
"@typescript-eslint/no-explicit-any": "warn",
|
| 20 |
+
"@typescript-eslint/no-non-null-assertion": "warn"
|
| 21 |
+
},
|
| 22 |
+
"ignorePatterns": ["dist/", "node_modules/"]
|
| 23 |
+
}
|
packages/node/.gitignore
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dependencies
|
| 2 |
+
node_modules/
|
| 3 |
+
|
| 4 |
+
# Build outputs
|
| 5 |
+
dist/
|
| 6 |
+
*.tsbuildinfo
|
| 7 |
+
|
| 8 |
+
# Environment files
|
| 9 |
+
.env
|
| 10 |
+
.env.local
|
| 11 |
+
.env.production
|
| 12 |
+
|
| 13 |
+
# IDE files
|
| 14 |
+
.vscode/
|
| 15 |
+
.idea/
|
| 16 |
+
*.swp
|
| 17 |
+
*.swo
|
| 18 |
+
|
| 19 |
+
# OS files
|
| 20 |
+
.DS_Store
|
| 21 |
+
Thumbs.db
|
| 22 |
+
|
| 23 |
+
# Test coverage
|
| 24 |
+
coverage/
|
| 25 |
+
|
| 26 |
+
# Logs
|
| 27 |
+
*.log
|
| 28 |
+
npm-debug.log*
|
| 29 |
+
yarn-debug.log*
|
| 30 |
+
yarn-error.log*
|
| 31 |
+
|
| 32 |
+
# Runtime data
|
| 33 |
+
pids
|
| 34 |
+
*.pid
|
| 35 |
+
*.seed
|
| 36 |
+
*.pid.lock
|
packages/node/README.md
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @lerobot/node
|
| 2 |
+
|
| 3 |
+
Control robots with Node.js (serialport), inspired by [LeRobot](https://github.com/huggingface/lerobot)
|
| 4 |
+
|
| 5 |
+
🚀 **[Try the live (web) demo →](https://huggingface.co/spaces/NERDDISCO/LeRobot.js)**
|
| 6 |
+
|
| 7 |
+
## Installation
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
# pnpm
|
| 11 |
+
pnpm add @lerobot/node
|
| 12 |
+
|
| 13 |
+
# npm
|
| 14 |
+
npm install @lerobot/node
|
| 15 |
+
|
| 16 |
+
# yarn
|
| 17 |
+
yarn add @lerobot/node
|
| 18 |
+
```
|
| 19 |
+
|
| 20 |
+
## Quick Start
|
| 21 |
+
|
| 22 |
+
```typescript
|
| 23 |
+
import {
|
| 24 |
+
findPort,
|
| 25 |
+
connectPort,
|
| 26 |
+
releaseMotors,
|
| 27 |
+
calibrate,
|
| 28 |
+
teleoperate,
|
| 29 |
+
} from "@lerobot/node";
|
| 30 |
+
|
| 31 |
+
// 1. find available robot ports
|
| 32 |
+
console.log("🔍 finding available robot ports...");
|
| 33 |
+
const findProcess = await findPort();
|
| 34 |
+
const robots = await findProcess.result;
|
| 35 |
+
|
| 36 |
+
if (robots.length === 0) {
|
| 37 |
+
console.log("❌ no robots found. check connections.");
|
| 38 |
+
process.exit(1);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
// 2. connect to the first robot found
|
| 42 |
+
console.log(`✅ found ${robots.length} robot(s). connecting to first one...`);
|
| 43 |
+
const robot = await connectPort(robots[0].path, robots[0].robotType);
|
| 44 |
+
|
| 45 |
+
// 3. release motors for manual positioning
|
| 46 |
+
console.log("🔓 releasing motors for manual positioning...");
|
| 47 |
+
await releaseMotors(robot);
|
| 48 |
+
|
| 49 |
+
// 4. calibrate motors by moving through full range
|
| 50 |
+
console.log("⚙️ starting calibration...");
|
| 51 |
+
const calibrationProcess = await calibrate({
|
| 52 |
+
robot,
|
| 53 |
+
onProgress: (message) => console.log(message),
|
| 54 |
+
onLiveUpdate: (data) => console.log("live positions:", data),
|
| 55 |
+
});
|
| 56 |
+
|
| 57 |
+
// move robot through its range, then stop calibration
|
| 58 |
+
console.log("👋 move robot through full range, press enter when done...");
|
| 59 |
+
process.stdin.once("data", () => {
|
| 60 |
+
calibrationProcess.stop();
|
| 61 |
+
});
|
| 62 |
+
|
| 63 |
+
const calibrationData = await calibrationProcess.result;
|
| 64 |
+
console.log("✅ calibration complete!");
|
| 65 |
+
|
| 66 |
+
// 5. control robot with keyboard
|
| 67 |
+
console.log("🎮 starting keyboard control...");
|
| 68 |
+
const teleop = await teleoperate({
|
| 69 |
+
robot,
|
| 70 |
+
calibrationData,
|
| 71 |
+
teleop: { type: "keyboard" },
|
| 72 |
+
});
|
| 73 |
+
teleop.start();
|
| 74 |
+
|
| 75 |
+
// stop control after 30 seconds
|
| 76 |
+
setTimeout(() => {
|
| 77 |
+
teleop.stop();
|
| 78 |
+
console.log("🛑 control stopped");
|
| 79 |
+
}, 30000);
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
## How It Works
|
| 83 |
+
|
| 84 |
+
### **Beginner Flow: `findPort()` → `connectPort()` → Use Robot**
|
| 85 |
+
|
| 86 |
+
Most users should start with `findPort()` for discovery, then `connectPort()` for connection:
|
| 87 |
+
|
| 88 |
+
```typescript
|
| 89 |
+
// ✅ recommended: discover then connect
|
| 90 |
+
const findProcess = await findPort();
|
| 91 |
+
const robots = await findProcess.result;
|
| 92 |
+
const robot = await connectPort(robots[0].path, robots[0].robotType);
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
### **Advanced: Direct Connection with `connectPort()`**
|
| 96 |
+
|
| 97 |
+
Only use `connectPort()` when you already know the exact port:
|
| 98 |
+
|
| 99 |
+
```typescript
|
| 100 |
+
// ⚡ advanced: direct connection to known port
|
| 101 |
+
const robot = await connectPort("/dev/ttyUSB0", "so100_follower");
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
## Core API
|
| 105 |
+
|
| 106 |
+
### `findPort(config?): Promise<FindPortProcess>`
|
| 107 |
+
|
| 108 |
+
Discovers available robotics hardware on serial ports. Unlike the web version, this only discovers ports - connection happens separately with `connectPort()`.
|
| 109 |
+
|
| 110 |
+
```typescript
|
| 111 |
+
// Discover all available robots
|
| 112 |
+
const findProcess = await findPort();
|
| 113 |
+
const robots = await findProcess.result;
|
| 114 |
+
|
| 115 |
+
console.log(`Found ${robots.length} robot(s):`);
|
| 116 |
+
robots.forEach((robot) => {
|
| 117 |
+
console.log(`- ${robot.robotType} on ${robot.path}`);
|
| 118 |
+
});
|
| 119 |
+
|
| 120 |
+
// Connect to specific robot
|
| 121 |
+
const robot = await connectPort(robots[0].path, robots[0].robotType);
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
#### Options
|
| 125 |
+
|
| 126 |
+
- `config?: FindPortConfig` - Optional configuration
|
| 127 |
+
- `onMessage?: (message: string) => void` - Progress messages callback
|
| 128 |
+
|
| 129 |
+
#### Returns: `FindPortProcess`
|
| 130 |
+
|
| 131 |
+
- `result: Promise<DiscoveredRobot[]>` - Array of discovered robots with `path`, `robotType`, and other metadata
|
| 132 |
+
- `stop(): void` - Cancel discovery process
|
| 133 |
+
|
| 134 |
+
#### DiscoveredRobot Structure
|
| 135 |
+
|
| 136 |
+
```typescript
|
| 137 |
+
interface DiscoveredRobot {
|
| 138 |
+
path: string; // Serial port path (e.g., "/dev/ttyUSB0")
|
| 139 |
+
robotType: "so100_follower" | "so100_leader";
|
| 140 |
+
// Additional metadata...
|
| 141 |
+
}
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
### `connectPort(port): Promise<RobotConnection>`
|
| 147 |
+
|
| 148 |
+
Creates a connection to a robot on the specified serial port.
|
| 149 |
+
|
| 150 |
+
```typescript
|
| 151 |
+
// Connect to SO-100 follower arm
|
| 152 |
+
const robot = await connectPort(
|
| 153 |
+
"/dev/ttyUSB0", // Serial port path
|
| 154 |
+
"so100_follower", // Robot type
|
| 155 |
+
"my_robot_arm" // Custom robot ID
|
| 156 |
+
);
|
| 157 |
+
|
| 158 |
+
// Windows
|
| 159 |
+
const robot = await connectPort("COM4", "so100_follower", "my_robot");
|
| 160 |
+
|
| 161 |
+
// Connection is ready to use
|
| 162 |
+
console.log(`Connected to ${robot.robotType} on ${robot.port.path}`);
|
| 163 |
+
```
|
| 164 |
+
|
| 165 |
+
#### Parameters
|
| 166 |
+
|
| 167 |
+
- `port: string` - Serial port path (e.g., `/dev/ttyUSB0`, `COM4`)
|
| 168 |
+
- `robotType: "so100_follower" | "so100_leader"` - Type of robot
|
| 169 |
+
- `robotId: string` - Custom identifier for your robot
|
| 170 |
+
|
| 171 |
+
#### Returns: `Promise<RobotConnection>`
|
| 172 |
+
|
| 173 |
+
- Initialized robot connection ready for calibration/teleoperation
|
| 174 |
+
- Includes configured motor IDs, keyboard controls, and hardware settings
|
| 175 |
+
|
| 176 |
+
---
|
| 177 |
+
|
| 178 |
+
### `calibrate(config): Promise<CalibrationProcess>`
|
| 179 |
+
|
| 180 |
+
Calibrates motor homing offsets and records range of motion. **Identical to Python lerobot behavior.**
|
| 181 |
+
|
| 182 |
+
```typescript
|
| 183 |
+
const calibrationProcess = await calibrate({
|
| 184 |
+
robot,
|
| 185 |
+
onProgress: (message) => {
|
| 186 |
+
console.log(message); // "⚙️ Setting motor homing offsets"
|
| 187 |
+
},
|
| 188 |
+
onLiveUpdate: (data) => {
|
| 189 |
+
// Real-time motor positions during range recording
|
| 190 |
+
Object.entries(data).forEach(([motor, info]) => {
|
| 191 |
+
console.log(`${motor}: ${info.current} (range: ${info.range})`);
|
| 192 |
+
});
|
| 193 |
+
},
|
| 194 |
+
});
|
| 195 |
+
|
| 196 |
+
// Move robot through full range of motion...
|
| 197 |
+
// When finished, stop calibration
|
| 198 |
+
calibrationProcess.stop();
|
| 199 |
+
|
| 200 |
+
const calibrationData = await calibrationProcess.result;
|
| 201 |
+
|
| 202 |
+
// Save to file (Python-compatible format)
|
| 203 |
+
import { writeFileSync } from "fs";
|
| 204 |
+
writeFileSync(
|
| 205 |
+
"./my_robot_calibration.json",
|
| 206 |
+
JSON.stringify(calibrationData, null, 2)
|
| 207 |
+
);
|
| 208 |
+
```
|
| 209 |
+
|
| 210 |
+
#### Options
|
| 211 |
+
|
| 212 |
+
- `config: CalibrateConfig`
|
| 213 |
+
- `robot: RobotConnection` - Connected robot from `connectPort()`
|
| 214 |
+
- `onProgress?: (message: string) => void` - Progress messages
|
| 215 |
+
- `onLiveUpdate?: (data: LiveCalibrationData) => void` - Real-time position updates
|
| 216 |
+
|
| 217 |
+
#### Returns: `CalibrationProcess`
|
| 218 |
+
|
| 219 |
+
- `result: Promise<CalibrationResults>` - **Python-compatible** calibration data
|
| 220 |
+
- `stop(): void` - Stop calibration process
|
| 221 |
+
|
| 222 |
+
#### Calibration Data Format
|
| 223 |
+
|
| 224 |
+
**Python Compatible**: This format is identical to Python lerobot calibration files - you can use the same calibration data across both implementations.
|
| 225 |
+
|
| 226 |
+
```json
|
| 227 |
+
{
|
| 228 |
+
"shoulder_pan": {
|
| 229 |
+
"id": 1,
|
| 230 |
+
"drive_mode": 0,
|
| 231 |
+
"homing_offset": 14,
|
| 232 |
+
"range_min": 1015,
|
| 233 |
+
"range_max": 3128
|
| 234 |
+
},
|
| 235 |
+
"shoulder_lift": {
|
| 236 |
+
"id": 2,
|
| 237 |
+
"drive_mode": 0,
|
| 238 |
+
"homing_offset": 989,
|
| 239 |
+
"range_min": 965,
|
| 240 |
+
"range_max": 3265
|
| 241 |
+
},
|
| 242 |
+
"elbow_flex": {
|
| 243 |
+
"id": 3,
|
| 244 |
+
"drive_mode": 0,
|
| 245 |
+
"homing_offset": -879,
|
| 246 |
+
"range_min": 820,
|
| 247 |
+
"range_max": 3051
|
| 248 |
+
},
|
| 249 |
+
"wrist_flex": {
|
| 250 |
+
"id": 4,
|
| 251 |
+
"drive_mode": 0,
|
| 252 |
+
"homing_offset": 31,
|
| 253 |
+
"range_min": 758,
|
| 254 |
+
"range_max": 3277
|
| 255 |
+
},
|
| 256 |
+
"wrist_roll": {
|
| 257 |
+
"id": 5,
|
| 258 |
+
"drive_mode": 0,
|
| 259 |
+
"homing_offset": -37,
|
| 260 |
+
"range_min": 2046,
|
| 261 |
+
"range_max": 3171
|
| 262 |
+
},
|
| 263 |
+
"gripper": {
|
| 264 |
+
"id": 6,
|
| 265 |
+
"drive_mode": 0,
|
| 266 |
+
"homing_offset": -1173,
|
| 267 |
+
"range_min": 2038,
|
| 268 |
+
"range_max": 3528
|
| 269 |
+
}
|
| 270 |
+
}
|
| 271 |
+
```
|
| 272 |
+
|
| 273 |
+
---
|
| 274 |
+
|
| 275 |
+
### `teleoperate(config): Promise<TeleoperationProcess>`
|
| 276 |
+
|
| 277 |
+
Real-time robot control with keyboard input. **Smooth, responsive movement** optimized for Node.js.
|
| 278 |
+
|
| 279 |
+
#### Keyboard Teleoperation
|
| 280 |
+
|
| 281 |
+
```typescript
|
| 282 |
+
const teleop = await teleoperate({
|
| 283 |
+
robot,
|
| 284 |
+
teleop: { type: "keyboard" },
|
| 285 |
+
onStateUpdate: (state) => {
|
| 286 |
+
console.log(`Active: ${state.isActive}`);
|
| 287 |
+
state.motorConfigs.forEach((motor) => {
|
| 288 |
+
console.log(`${motor.name}: ${motor.currentPosition}`);
|
| 289 |
+
});
|
| 290 |
+
},
|
| 291 |
+
});
|
| 292 |
+
|
| 293 |
+
// Start keyboard control
|
| 294 |
+
teleop.start();
|
| 295 |
+
|
| 296 |
+
// Control will be active until stopped
|
| 297 |
+
setTimeout(() => teleop.stop(), 60000);
|
| 298 |
+
```
|
| 299 |
+
|
| 300 |
+
#### Options
|
| 301 |
+
|
| 302 |
+
- `config: TeleoperateConfig`
|
| 303 |
+
- `robot: RobotConnection` - Connected robot
|
| 304 |
+
- `teleop: TeleoperatorConfig` - Teleoperator configuration:
|
| 305 |
+
- `{ type: "keyboard" }` - Keyboard control with optimized defaults
|
| 306 |
+
- `onStateUpdate?: (state: TeleoperationState) => void` - State change callback
|
| 307 |
+
|
| 308 |
+
#### Returns: `TeleoperationProcess`
|
| 309 |
+
|
| 310 |
+
- `start(): void` - Begin teleoperation (shows keyboard controls)
|
| 311 |
+
- `stop(): void` - Stop teleoperation
|
| 312 |
+
- `getState(): TeleoperationState` - Current state and motor positions
|
| 313 |
+
|
| 314 |
+
#### Keyboard Controls (SO-100)
|
| 315 |
+
|
| 316 |
+
```
|
| 317 |
+
Arrow Keys: Shoulder pan/lift
|
| 318 |
+
WASD: Elbow flex, wrist flex
|
| 319 |
+
Q/E: Wrist roll
|
| 320 |
+
O/C: Gripper open/close
|
| 321 |
+
ESC: Emergency stop
|
| 322 |
+
Ctrl+C: Exit
|
| 323 |
+
```
|
| 324 |
+
|
| 325 |
+
#### Performance Characteristics
|
| 326 |
+
|
| 327 |
+
- **120 Hz update rate** for smooth movement
|
| 328 |
+
- **Immediate response** on keypress (no delay)
|
| 329 |
+
- **8-unit step size** matching browser demo
|
| 330 |
+
- **150ms key timeout** for optimal single-tap vs hold behavior
|
| 331 |
+
|
| 332 |
+
---
|
| 333 |
+
|
| 334 |
+
### `releaseMotors(robot): Promise<void>`
|
| 335 |
+
|
| 336 |
+
Releases motor torque so robot can be moved freely by hand.
|
| 337 |
+
|
| 338 |
+
```typescript
|
| 339 |
+
// Release all motors for calibration
|
| 340 |
+
await releaseMotors(robot);
|
| 341 |
+
console.log("Motors released - you can now move the robot freely");
|
| 342 |
+
```
|
| 343 |
+
|
| 344 |
+
#### Parameters
|
| 345 |
+
|
| 346 |
+
- `robot: RobotConnection` - Connected robot
|
| 347 |
+
|
| 348 |
+
---
|
| 349 |
+
|
| 350 |
+
## CLI Usage
|
| 351 |
+
|
| 352 |
+
For command-line usage, install the CLI package:
|
| 353 |
+
|
| 354 |
+
```bash
|
| 355 |
+
# Install CLI globally
|
| 356 |
+
pnpm add -g lerobot
|
| 357 |
+
|
| 358 |
+
# Find and connect to robot
|
| 359 |
+
npx lerobot find-port
|
| 360 |
+
|
| 361 |
+
# Calibrate robot
|
| 362 |
+
npx lerobot calibrate --robot.type so100_follower --robot.port /dev/ttyUSB0 --robot.id my_robot
|
| 363 |
+
|
| 364 |
+
# Control robot with keyboard
|
| 365 |
+
npx lerobot teleoperate --robot.type so100_follower --robot.port /dev/ttyUSB0 --robot.id my_robot
|
| 366 |
+
|
| 367 |
+
# Release motors
|
| 368 |
+
npx lerobot release-motors --robot.type so100_follower --robot.port /dev/ttyUSB0 --robot.id my_robot
|
| 369 |
+
```
|
| 370 |
+
|
| 371 |
+
**CLI commands are identical to Python lerobot** - same syntax, same behavior, seamless migration.
|
| 372 |
+
|
| 373 |
+
## Node.js Requirements
|
| 374 |
+
|
| 375 |
+
- **Node.js 18+**
|
| 376 |
+
- **Serial port access** (may require permissions on Linux/macOS)
|
| 377 |
+
- **Supported platforms**: Windows, macOS, Linux
|
| 378 |
+
|
| 379 |
+
### Serial Port Permissions
|
| 380 |
+
|
| 381 |
+
**Linux/macOS:**
|
| 382 |
+
|
| 383 |
+
```bash
|
| 384 |
+
# Add user to dialout group (Linux)
|
| 385 |
+
sudo usermod -a -G dialout $USER
|
| 386 |
+
|
| 387 |
+
# Set permissions (macOS)
|
| 388 |
+
sudo chmod 666 /dev/tty.usbserial-*
|
| 389 |
+
```
|
| 390 |
+
|
| 391 |
+
**Windows:** No additional setup required.
|
| 392 |
+
|
| 393 |
+
## Hardware Support
|
| 394 |
+
|
| 395 |
+
Currently supports SO-100 follower and leader arms with STS3215 motors. More devices coming soon.
|
| 396 |
+
|
| 397 |
+
## Migration from lerobot.py
|
| 398 |
+
|
| 399 |
+
```python
|
| 400 |
+
# Python lerobot
|
| 401 |
+
python -m lerobot.calibrate --robot.type so100_follower --robot.port /dev/ttyUSB0
|
| 402 |
+
|
| 403 |
+
# Node.js equivalent
|
| 404 |
+
npx lerobot calibrate --robot.type so100_follower --robot.port /dev/ttyUSB0
|
| 405 |
+
```
|
| 406 |
+
|
| 407 |
+
- **Same commands** - just replace `python -m lerobot.` with `npx lerobot`
|
| 408 |
+
- **Same calibration files** - Python and Node.js calibrations are interchangeable
|
packages/node/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "@lerobot/node",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"description": "Node.js-based robotics control using SerialPort",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"main": "./dist/index.js",
|
| 7 |
+
"types": "./dist/index.d.ts",
|
| 8 |
+
"exports": {
|
| 9 |
+
".": {
|
| 10 |
+
"import": "./dist/index.js",
|
| 11 |
+
"types": "./dist/index.d.ts"
|
| 12 |
+
},
|
| 13 |
+
"./calibrate": {
|
| 14 |
+
"import": "./dist/calibrate.js",
|
| 15 |
+
"types": "./dist/calibrate.d.ts"
|
| 16 |
+
},
|
| 17 |
+
"./teleoperate": {
|
| 18 |
+
"import": "./dist/teleoperate.js",
|
| 19 |
+
"types": "./dist/teleoperate.d.ts"
|
| 20 |
+
},
|
| 21 |
+
"./find-port": {
|
| 22 |
+
"import": "./dist/find_port.js",
|
| 23 |
+
"types": "./dist/find_port.d.ts"
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
"files": [
|
| 27 |
+
"dist/**/*",
|
| 28 |
+
"README.md"
|
| 29 |
+
],
|
| 30 |
+
"keywords": [
|
| 31 |
+
"robotics",
|
| 32 |
+
"serialport",
|
| 33 |
+
"hardware-control",
|
| 34 |
+
"nodejs",
|
| 35 |
+
"typescript"
|
| 36 |
+
],
|
| 37 |
+
"scripts": {
|
| 38 |
+
"build": "vite build",
|
| 39 |
+
"dev": "vite build --watch",
|
| 40 |
+
"lint": "eslint src --ext .ts,.tsx --report-unused-disable-directives --max-warnings 0",
|
| 41 |
+
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
| 42 |
+
"prepublishOnly": "npm run build",
|
| 43 |
+
"test": "vitest run",
|
| 44 |
+
"test:ui": "vitest --ui",
|
| 45 |
+
"test:coverage": "vitest run --coverage"
|
| 46 |
+
},
|
| 47 |
+
"dependencies": {
|
| 48 |
+
"serialport": "^12.0.0"
|
| 49 |
+
},
|
| 50 |
+
"devDependencies": {
|
| 51 |
+
"@types/node": "^18.0.0",
|
| 52 |
+
"@typescript-eslint/eslint-plugin": "^8.41.0",
|
| 53 |
+
"@typescript-eslint/parser": "^8.41.0",
|
| 54 |
+
"@vitest/ui": "^2.0.0",
|
| 55 |
+
"eslint": "^9.34.0",
|
| 56 |
+
"typescript": "^5.3.0",
|
| 57 |
+
"vite": "^6.3.5",
|
| 58 |
+
"vite-node": "^2.0.0",
|
| 59 |
+
"vitest": "^2.0.0"
|
| 60 |
+
},
|
| 61 |
+
"peerDependencies": {
|
| 62 |
+
"typescript": ">=4.5.0"
|
| 63 |
+
},
|
| 64 |
+
"engines": {
|
| 65 |
+
"node": ">=18.0.0"
|
| 66 |
+
},
|
| 67 |
+
"repository": {
|
| 68 |
+
"type": "git",
|
| 69 |
+
"url": "https://github.com/timpietrusky/lerobot.js/tree/main/packages/node"
|
| 70 |
+
},
|
| 71 |
+
"license": "Apache-2.0",
|
| 72 |
+
"author": "Tim Pietrusky",
|
| 73 |
+
"publishConfig": {
|
| 74 |
+
"access": "public"
|
| 75 |
+
}
|
| 76 |
+
}
|
packages/node/pnpm-lock.yaml
ADDED
|
@@ -0,0 +1,2396 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
lockfileVersion: '9.0'
|
| 2 |
+
|
| 3 |
+
settings:
|
| 4 |
+
autoInstallPeers: true
|
| 5 |
+
excludeLinksFromLockfile: false
|
| 6 |
+
|
| 7 |
+
importers:
|
| 8 |
+
|
| 9 |
+
.:
|
| 10 |
+
dependencies:
|
| 11 |
+
serialport:
|
| 12 |
+
specifier: ^12.0.0
|
| 13 |
+
version: 12.0.0
|
| 14 |
+
devDependencies:
|
| 15 |
+
'@types/node':
|
| 16 |
+
specifier: ^18.0.0
|
| 17 |
+
version: 18.19.123
|
| 18 |
+
'@typescript-eslint/eslint-plugin':
|
| 19 |
+
specifier: ^8.41.0
|
| 20 |
+
version: 8.41.0(@typescript-eslint/[email protected]([email protected])([email protected]))([email protected])([email protected])
|
| 21 |
+
'@typescript-eslint/parser':
|
| 22 |
+
specifier: ^8.41.0
|
| 23 |
+
version: 8.41.0([email protected])([email protected])
|
| 24 |
+
'@vitest/ui':
|
| 25 |
+
specifier: ^2.0.0
|
| 26 |
+
version: 2.1.9([email protected])
|
| 27 |
+
eslint:
|
| 28 |
+
specifier: ^9.34.0
|
| 29 |
+
version: 9.34.0
|
| 30 |
+
typescript:
|
| 31 |
+
specifier: ^5.3.0
|
| 32 |
+
version: 5.8.3
|
| 33 |
+
vite:
|
| 34 |
+
specifier: ^6.3.5
|
| 35 |
+
version: 6.3.5(@types/[email protected])
|
| 36 |
+
vite-node:
|
| 37 |
+
specifier: ^2.0.0
|
| 38 |
+
version: 2.1.9(@types/[email protected])
|
| 39 |
+
vitest:
|
| 40 |
+
specifier: ^2.0.0
|
| 41 |
+
version: 2.1.9(@types/[email protected])(@vitest/[email protected])
|
| 42 |
+
|
| 43 |
+
packages:
|
| 44 |
+
|
| 45 |
+
'@esbuild/[email protected]':
|
| 46 |
+
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
| 47 |
+
engines: {node: '>=12'}
|
| 48 |
+
cpu: [ppc64]
|
| 49 |
+
os: [aix]
|
| 50 |
+
|
| 51 |
+
'@esbuild/[email protected]':
|
| 52 |
+
resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==}
|
| 53 |
+
engines: {node: '>=18'}
|
| 54 |
+
cpu: [ppc64]
|
| 55 |
+
os: [aix]
|
| 56 |
+
|
| 57 |
+
'@esbuild/[email protected]':
|
| 58 |
+
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
|
| 59 |
+
engines: {node: '>=12'}
|
| 60 |
+
cpu: [arm64]
|
| 61 |
+
os: [android]
|
| 62 |
+
|
| 63 |
+
'@esbuild/[email protected]':
|
| 64 |
+
resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==}
|
| 65 |
+
engines: {node: '>=18'}
|
| 66 |
+
cpu: [arm64]
|
| 67 |
+
os: [android]
|
| 68 |
+
|
| 69 |
+
'@esbuild/[email protected]':
|
| 70 |
+
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
|
| 71 |
+
engines: {node: '>=12'}
|
| 72 |
+
cpu: [arm]
|
| 73 |
+
os: [android]
|
| 74 |
+
|
| 75 |
+
'@esbuild/[email protected]':
|
| 76 |
+
resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==}
|
| 77 |
+
engines: {node: '>=18'}
|
| 78 |
+
cpu: [arm]
|
| 79 |
+
os: [android]
|
| 80 |
+
|
| 81 |
+
'@esbuild/[email protected]':
|
| 82 |
+
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
|
| 83 |
+
engines: {node: '>=12'}
|
| 84 |
+
cpu: [x64]
|
| 85 |
+
os: [android]
|
| 86 |
+
|
| 87 |
+
'@esbuild/[email protected]':
|
| 88 |
+
resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==}
|
| 89 |
+
engines: {node: '>=18'}
|
| 90 |
+
cpu: [x64]
|
| 91 |
+
os: [android]
|
| 92 |
+
|
| 93 |
+
'@esbuild/[email protected]':
|
| 94 |
+
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
|
| 95 |
+
engines: {node: '>=12'}
|
| 96 |
+
cpu: [arm64]
|
| 97 |
+
os: [darwin]
|
| 98 |
+
|
| 99 |
+
'@esbuild/[email protected]':
|
| 100 |
+
resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==}
|
| 101 |
+
engines: {node: '>=18'}
|
| 102 |
+
cpu: [arm64]
|
| 103 |
+
os: [darwin]
|
| 104 |
+
|
| 105 |
+
'@esbuild/[email protected]':
|
| 106 |
+
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
|
| 107 |
+
engines: {node: '>=12'}
|
| 108 |
+
cpu: [x64]
|
| 109 |
+
os: [darwin]
|
| 110 |
+
|
| 111 |
+
'@esbuild/[email protected]':
|
| 112 |
+
resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==}
|
| 113 |
+
engines: {node: '>=18'}
|
| 114 |
+
cpu: [x64]
|
| 115 |
+
os: [darwin]
|
| 116 |
+
|
| 117 |
+
'@esbuild/[email protected]':
|
| 118 |
+
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
|
| 119 |
+
engines: {node: '>=12'}
|
| 120 |
+
cpu: [arm64]
|
| 121 |
+
os: [freebsd]
|
| 122 |
+
|
| 123 |
+
'@esbuild/[email protected]':
|
| 124 |
+
resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==}
|
| 125 |
+
engines: {node: '>=18'}
|
| 126 |
+
cpu: [arm64]
|
| 127 |
+
os: [freebsd]
|
| 128 |
+
|
| 129 |
+
'@esbuild/[email protected]':
|
| 130 |
+
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
|
| 131 |
+
engines: {node: '>=12'}
|
| 132 |
+
cpu: [x64]
|
| 133 |
+
os: [freebsd]
|
| 134 |
+
|
| 135 |
+
'@esbuild/[email protected]':
|
| 136 |
+
resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==}
|
| 137 |
+
engines: {node: '>=18'}
|
| 138 |
+
cpu: [x64]
|
| 139 |
+
os: [freebsd]
|
| 140 |
+
|
| 141 |
+
'@esbuild/[email protected]':
|
| 142 |
+
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
|
| 143 |
+
engines: {node: '>=12'}
|
| 144 |
+
cpu: [arm64]
|
| 145 |
+
os: [linux]
|
| 146 |
+
|
| 147 |
+
'@esbuild/[email protected]':
|
| 148 |
+
resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==}
|
| 149 |
+
engines: {node: '>=18'}
|
| 150 |
+
cpu: [arm64]
|
| 151 |
+
os: [linux]
|
| 152 |
+
|
| 153 |
+
'@esbuild/[email protected]':
|
| 154 |
+
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
|
| 155 |
+
engines: {node: '>=12'}
|
| 156 |
+
cpu: [arm]
|
| 157 |
+
os: [linux]
|
| 158 |
+
|
| 159 |
+
'@esbuild/[email protected]':
|
| 160 |
+
resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==}
|
| 161 |
+
engines: {node: '>=18'}
|
| 162 |
+
cpu: [arm]
|
| 163 |
+
os: [linux]
|
| 164 |
+
|
| 165 |
+
'@esbuild/[email protected]':
|
| 166 |
+
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
|
| 167 |
+
engines: {node: '>=12'}
|
| 168 |
+
cpu: [ia32]
|
| 169 |
+
os: [linux]
|
| 170 |
+
|
| 171 |
+
'@esbuild/[email protected]':
|
| 172 |
+
resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==}
|
| 173 |
+
engines: {node: '>=18'}
|
| 174 |
+
cpu: [ia32]
|
| 175 |
+
os: [linux]
|
| 176 |
+
|
| 177 |
+
'@esbuild/[email protected]':
|
| 178 |
+
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
|
| 179 |
+
engines: {node: '>=12'}
|
| 180 |
+
cpu: [loong64]
|
| 181 |
+
os: [linux]
|
| 182 |
+
|
| 183 |
+
'@esbuild/[email protected]':
|
| 184 |
+
resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==}
|
| 185 |
+
engines: {node: '>=18'}
|
| 186 |
+
cpu: [loong64]
|
| 187 |
+
os: [linux]
|
| 188 |
+
|
| 189 |
+
'@esbuild/[email protected]':
|
| 190 |
+
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
|
| 191 |
+
engines: {node: '>=12'}
|
| 192 |
+
cpu: [mips64el]
|
| 193 |
+
os: [linux]
|
| 194 |
+
|
| 195 |
+
'@esbuild/[email protected]':
|
| 196 |
+
resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==}
|
| 197 |
+
engines: {node: '>=18'}
|
| 198 |
+
cpu: [mips64el]
|
| 199 |
+
os: [linux]
|
| 200 |
+
|
| 201 |
+
'@esbuild/[email protected]':
|
| 202 |
+
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
|
| 203 |
+
engines: {node: '>=12'}
|
| 204 |
+
cpu: [ppc64]
|
| 205 |
+
os: [linux]
|
| 206 |
+
|
| 207 |
+
'@esbuild/[email protected]':
|
| 208 |
+
resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==}
|
| 209 |
+
engines: {node: '>=18'}
|
| 210 |
+
cpu: [ppc64]
|
| 211 |
+
os: [linux]
|
| 212 |
+
|
| 213 |
+
'@esbuild/[email protected]':
|
| 214 |
+
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
|
| 215 |
+
engines: {node: '>=12'}
|
| 216 |
+
cpu: [riscv64]
|
| 217 |
+
os: [linux]
|
| 218 |
+
|
| 219 |
+
'@esbuild/[email protected]':
|
| 220 |
+
resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==}
|
| 221 |
+
engines: {node: '>=18'}
|
| 222 |
+
cpu: [riscv64]
|
| 223 |
+
os: [linux]
|
| 224 |
+
|
| 225 |
+
'@esbuild/[email protected]':
|
| 226 |
+
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
|
| 227 |
+
engines: {node: '>=12'}
|
| 228 |
+
cpu: [s390x]
|
| 229 |
+
os: [linux]
|
| 230 |
+
|
| 231 |
+
'@esbuild/[email protected]':
|
| 232 |
+
resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==}
|
| 233 |
+
engines: {node: '>=18'}
|
| 234 |
+
cpu: [s390x]
|
| 235 |
+
os: [linux]
|
| 236 |
+
|
| 237 |
+
'@esbuild/[email protected]':
|
| 238 |
+
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
|
| 239 |
+
engines: {node: '>=12'}
|
| 240 |
+
cpu: [x64]
|
| 241 |
+
os: [linux]
|
| 242 |
+
|
| 243 |
+
'@esbuild/[email protected]':
|
| 244 |
+
resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==}
|
| 245 |
+
engines: {node: '>=18'}
|
| 246 |
+
cpu: [x64]
|
| 247 |
+
os: [linux]
|
| 248 |
+
|
| 249 |
+
'@esbuild/[email protected]':
|
| 250 |
+
resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==}
|
| 251 |
+
engines: {node: '>=18'}
|
| 252 |
+
cpu: [arm64]
|
| 253 |
+
os: [netbsd]
|
| 254 |
+
|
| 255 |
+
'@esbuild/[email protected]':
|
| 256 |
+
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
|
| 257 |
+
engines: {node: '>=12'}
|
| 258 |
+
cpu: [x64]
|
| 259 |
+
os: [netbsd]
|
| 260 |
+
|
| 261 |
+
'@esbuild/[email protected]':
|
| 262 |
+
resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==}
|
| 263 |
+
engines: {node: '>=18'}
|
| 264 |
+
cpu: [x64]
|
| 265 |
+
os: [netbsd]
|
| 266 |
+
|
| 267 |
+
'@esbuild/[email protected]':
|
| 268 |
+
resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==}
|
| 269 |
+
engines: {node: '>=18'}
|
| 270 |
+
cpu: [arm64]
|
| 271 |
+
os: [openbsd]
|
| 272 |
+
|
| 273 |
+
'@esbuild/[email protected]':
|
| 274 |
+
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
|
| 275 |
+
engines: {node: '>=12'}
|
| 276 |
+
cpu: [x64]
|
| 277 |
+
os: [openbsd]
|
| 278 |
+
|
| 279 |
+
'@esbuild/[email protected]':
|
| 280 |
+
resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==}
|
| 281 |
+
engines: {node: '>=18'}
|
| 282 |
+
cpu: [x64]
|
| 283 |
+
os: [openbsd]
|
| 284 |
+
|
| 285 |
+
'@esbuild/[email protected]':
|
| 286 |
+
resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==}
|
| 287 |
+
engines: {node: '>=18'}
|
| 288 |
+
cpu: [arm64]
|
| 289 |
+
os: [openharmony]
|
| 290 |
+
|
| 291 |
+
'@esbuild/[email protected]':
|
| 292 |
+
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
|
| 293 |
+
engines: {node: '>=12'}
|
| 294 |
+
cpu: [x64]
|
| 295 |
+
os: [sunos]
|
| 296 |
+
|
| 297 |
+
'@esbuild/[email protected]':
|
| 298 |
+
resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==}
|
| 299 |
+
engines: {node: '>=18'}
|
| 300 |
+
cpu: [x64]
|
| 301 |
+
os: [sunos]
|
| 302 |
+
|
| 303 |
+
'@esbuild/[email protected]':
|
| 304 |
+
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
|
| 305 |
+
engines: {node: '>=12'}
|
| 306 |
+
cpu: [arm64]
|
| 307 |
+
os: [win32]
|
| 308 |
+
|
| 309 |
+
'@esbuild/[email protected]':
|
| 310 |
+
resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==}
|
| 311 |
+
engines: {node: '>=18'}
|
| 312 |
+
cpu: [arm64]
|
| 313 |
+
os: [win32]
|
| 314 |
+
|
| 315 |
+
'@esbuild/[email protected]':
|
| 316 |
+
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
|
| 317 |
+
engines: {node: '>=12'}
|
| 318 |
+
cpu: [ia32]
|
| 319 |
+
os: [win32]
|
| 320 |
+
|
| 321 |
+
'@esbuild/[email protected]':
|
| 322 |
+
resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==}
|
| 323 |
+
engines: {node: '>=18'}
|
| 324 |
+
cpu: [ia32]
|
| 325 |
+
os: [win32]
|
| 326 |
+
|
| 327 |
+
'@esbuild/[email protected]':
|
| 328 |
+
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
|
| 329 |
+
engines: {node: '>=12'}
|
| 330 |
+
cpu: [x64]
|
| 331 |
+
os: [win32]
|
| 332 |
+
|
| 333 |
+
'@esbuild/[email protected]':
|
| 334 |
+
resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==}
|
| 335 |
+
engines: {node: '>=18'}
|
| 336 |
+
cpu: [x64]
|
| 337 |
+
os: [win32]
|
| 338 |
+
|
| 339 |
+
'@eslint-community/[email protected]':
|
| 340 |
+
resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
|
| 341 |
+
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
| 342 |
+
peerDependencies:
|
| 343 |
+
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
| 344 |
+
|
| 345 |
+
'@eslint-community/[email protected]':
|
| 346 |
+
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
|
| 347 |
+
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
| 348 |
+
|
| 349 |
+
'@eslint/[email protected]':
|
| 350 |
+
resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==}
|
| 351 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 352 |
+
|
| 353 |
+
'@eslint/[email protected]':
|
| 354 |
+
resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==}
|
| 355 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 356 |
+
|
| 357 |
+
'@eslint/[email protected]':
|
| 358 |
+
resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==}
|
| 359 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 360 |
+
|
| 361 |
+
'@eslint/[email protected]':
|
| 362 |
+
resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
|
| 363 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 364 |
+
|
| 365 |
+
'@eslint/[email protected]':
|
| 366 |
+
resolution: {integrity: sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==}
|
| 367 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 368 |
+
|
| 369 |
+
'@eslint/[email protected]':
|
| 370 |
+
resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
|
| 371 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 372 |
+
|
| 373 |
+
'@eslint/[email protected]':
|
| 374 |
+
resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==}
|
| 375 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 376 |
+
|
| 377 |
+
'@humanfs/[email protected]':
|
| 378 |
+
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
| 379 |
+
engines: {node: '>=18.18.0'}
|
| 380 |
+
|
| 381 |
+
'@humanfs/[email protected]':
|
| 382 |
+
resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
|
| 383 |
+
engines: {node: '>=18.18.0'}
|
| 384 |
+
|
| 385 |
+
'@humanwhocodes/[email protected]':
|
| 386 |
+
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
|
| 387 |
+
engines: {node: '>=12.22'}
|
| 388 |
+
|
| 389 |
+
'@humanwhocodes/[email protected]':
|
| 390 |
+
resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
|
| 391 |
+
engines: {node: '>=18.18'}
|
| 392 |
+
|
| 393 |
+
'@humanwhocodes/[email protected]':
|
| 394 |
+
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
|
| 395 |
+
engines: {node: '>=18.18'}
|
| 396 |
+
|
| 397 |
+
'@jridgewell/[email protected]':
|
| 398 |
+
resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==}
|
| 399 |
+
|
| 400 |
+
'@nodelib/[email protected]':
|
| 401 |
+
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
| 402 |
+
engines: {node: '>= 8'}
|
| 403 |
+
|
| 404 |
+
'@nodelib/[email protected]':
|
| 405 |
+
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
|
| 406 |
+
engines: {node: '>= 8'}
|
| 407 |
+
|
| 408 |
+
'@nodelib/[email protected]':
|
| 409 |
+
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
| 410 |
+
engines: {node: '>= 8'}
|
| 411 |
+
|
| 412 |
+
'@polka/[email protected]':
|
| 413 |
+
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
| 414 |
+
|
| 415 |
+
'@rollup/[email protected]':
|
| 416 |
+
resolution: {integrity: sha512-9f3nSTFI2ivfxc7/tHBHcJ8pRnp8ROrELvsVprlQPVvcZ+j5zztYd+PTJGpyIOAdTvNwNrpCXswKSeoQcyGjMQ==}
|
| 417 |
+
cpu: [arm]
|
| 418 |
+
os: [android]
|
| 419 |
+
|
| 420 |
+
'@rollup/[email protected]':
|
| 421 |
+
resolution: {integrity: sha512-tFZSEhqJ8Yrpe50TzOdeoYi72gi/jsnT7y8Qrozf3cNu28WX+s6I3XzEPUAqoaT9SAS8Xz9AzGTFlxxCH/w20w==}
|
| 422 |
+
cpu: [arm64]
|
| 423 |
+
os: [android]
|
| 424 |
+
|
| 425 |
+
'@rollup/[email protected]':
|
| 426 |
+
resolution: {integrity: sha512-+DikIIs+p6yU2hF51UaWG8BnHbq90X0QIOt5zqSKSZxY+G3qqdLih214e9InJal21af2PuuxkDectetGfbVPJw==}
|
| 427 |
+
cpu: [arm64]
|
| 428 |
+
os: [darwin]
|
| 429 |
+
|
| 430 |
+
'@rollup/[email protected]':
|
| 431 |
+
resolution: {integrity: sha512-5a+NofhdEB/WimSlFMskbFQn1vqz1FWryYpA99trmZGO6qEmiS0IsX6w4B3d91U878Q2ZQdiaFF1gxX4P147og==}
|
| 432 |
+
cpu: [x64]
|
| 433 |
+
os: [darwin]
|
| 434 |
+
|
| 435 |
+
'@rollup/[email protected]':
|
| 436 |
+
resolution: {integrity: sha512-igr/RlKPS3OCy4jD3XBmAmo3UAcNZkJSubRsw1JeM8bAbwf15k/3eMZXD91bnjheijJiOJcga3kfCLKjV8IXNg==}
|
| 437 |
+
cpu: [arm64]
|
| 438 |
+
os: [freebsd]
|
| 439 |
+
|
| 440 |
+
'@rollup/[email protected]':
|
| 441 |
+
resolution: {integrity: sha512-MdigWzPSHlQzB1xZ+MdFDWTAH+kcn7UxjEBoOKuaso7z1DRlnAnrknB1mTtNOQ+GdPI8xgExAGwHeqQjntR0Cg==}
|
| 442 |
+
cpu: [x64]
|
| 443 |
+
os: [freebsd]
|
| 444 |
+
|
| 445 |
+
'@rollup/[email protected]':
|
| 446 |
+
resolution: {integrity: sha512-dmZseE0ZwA/4yy1+BwFrDqFTjjNg24GO9xSrb1weVbt6AFkhp5pz1gVS7IMtfIvoWy8yp6q/zN0bKnefRUImvQ==}
|
| 447 |
+
cpu: [arm]
|
| 448 |
+
os: [linux]
|
| 449 |
+
|
| 450 |
+
'@rollup/[email protected]':
|
| 451 |
+
resolution: {integrity: sha512-fzhfn6p9Cfm3W8UrWKIa4l7Wfjs/KGdgaswMBBE3KY3Ta43jg2XsPrAtfezHpsRk0Nx+TFuS3hZk/To2N5kFPQ==}
|
| 452 |
+
cpu: [arm]
|
| 453 |
+
os: [linux]
|
| 454 |
+
|
| 455 |
+
'@rollup/[email protected]':
|
| 456 |
+
resolution: {integrity: sha512-vVDD+iPDPmJQ5nAQ5Tifq3ywdv60FartglFI8VOCK+hcU9aoG0qlQTsDJP97O5yiTaTqlneZWoARMcVC5nyUoQ==}
|
| 457 |
+
cpu: [arm64]
|
| 458 |
+
os: [linux]
|
| 459 |
+
|
| 460 |
+
'@rollup/[email protected]':
|
| 461 |
+
resolution: {integrity: sha512-0d0jx08fzDHCzXqrtCMEEyxKU0SvJrWmUjUDE2/KDQ2UDJql0tfiwYvEx1oHELClKO8CNdE+AGJj+RqXscZpdQ==}
|
| 462 |
+
cpu: [arm64]
|
| 463 |
+
os: [linux]
|
| 464 |
+
|
| 465 |
+
'@rollup/[email protected]':
|
| 466 |
+
resolution: {integrity: sha512-XBYu9oW9eKJadWn8M7hkTZsD4yG+RrsTrVEgyKwb4L72cpJjRbRboTG9Lg9fec8MxJp/cfTHAocg4mnismQR8A==}
|
| 467 |
+
cpu: [loong64]
|
| 468 |
+
os: [linux]
|
| 469 |
+
|
| 470 |
+
'@rollup/[email protected]':
|
| 471 |
+
resolution: {integrity: sha512-wJaRvcT17PoOK6Ggcfo3nouFlybHvARBS4jzT0PC/lg17fIJHcDS2fZz3sD+iA4nRlho2zE6OGbU0HvwATdokQ==}
|
| 472 |
+
cpu: [ppc64]
|
| 473 |
+
os: [linux]
|
| 474 |
+
|
| 475 |
+
'@rollup/[email protected]':
|
| 476 |
+
resolution: {integrity: sha512-GZ5bkMFteAGkcmh8x0Ok4LSa+L62Ez0tMsHPX6JtR0wl4Xc3bQcrFHDiR5DGLEDFtGrXih4Nd/UDaFqs968/wA==}
|
| 477 |
+
cpu: [riscv64]
|
| 478 |
+
os: [linux]
|
| 479 |
+
|
| 480 |
+
'@rollup/[email protected]':
|
| 481 |
+
resolution: {integrity: sha512-7CjPw6FflFsVOUfWOrVrREiV3IYXG4RzZ1ZQUaT3BtSK8YXN6x286o+sruPZJESIaPebYuFowmg54ZdrkVBYog==}
|
| 482 |
+
cpu: [riscv64]
|
| 483 |
+
os: [linux]
|
| 484 |
+
|
| 485 |
+
'@rollup/[email protected]':
|
| 486 |
+
resolution: {integrity: sha512-nmvnl0ZiuysltcB/cKjUh40Rx4FbSyueERDsl2FLvLYr6pCgSsvGr3SocUT84svSpmloS7f1DRWqtRha74Gi1w==}
|
| 487 |
+
cpu: [s390x]
|
| 488 |
+
os: [linux]
|
| 489 |
+
|
| 490 |
+
'@rollup/[email protected]':
|
| 491 |
+
resolution: {integrity: sha512-Cv+moII5C8RM6gZbR3cb21o6rquVDZrN2o81maROg1LFzBz2dZUwIQSxFA8GtGZ/F2KtsqQ2z3eFPBb6akvQNg==}
|
| 492 |
+
cpu: [x64]
|
| 493 |
+
os: [linux]
|
| 494 |
+
|
| 495 |
+
'@rollup/[email protected]':
|
| 496 |
+
resolution: {integrity: sha512-PHcMG8DZTM9RCIjp8QIfN0VYtX0TtBPnWOTRurFhoCDoi9zptUZL2k7pCs+5rgut7JAiUsYy+huyhVKPcmxoog==}
|
| 497 |
+
cpu: [x64]
|
| 498 |
+
os: [linux]
|
| 499 |
+
|
| 500 |
+
'@rollup/[email protected]':
|
| 501 |
+
resolution: {integrity: sha512-1SI/Rd47e8aQJeFWMDg16ET+fjvCcD/CzeaRmIEPmb05hx+3cCcwIF4ebUag4yTt/D1peE+Mgp0+Po3M358cAA==}
|
| 502 |
+
cpu: [arm64]
|
| 503 |
+
os: [win32]
|
| 504 |
+
|
| 505 |
+
'@rollup/[email protected]':
|
| 506 |
+
resolution: {integrity: sha512-JwOCYxmumFDfDhx4kNyz6kTVK3gWzBIvVdMNzQMRDubcoGRDniOOmo6DDNP42qwZx3Bp9/6vWJ+kNzNqXoHmeA==}
|
| 507 |
+
cpu: [ia32]
|
| 508 |
+
os: [win32]
|
| 509 |
+
|
| 510 |
+
'@rollup/[email protected]':
|
| 511 |
+
resolution: {integrity: sha512-IPMIfrfkG1GaEXi+JSsQEx8x9b4b+hRZXO7KYc2pKio3zO2/VDXDs6B9Ts/nnO+25Fk1tdAVtUn60HKKPPzDig==}
|
| 512 |
+
cpu: [x64]
|
| 513 |
+
os: [win32]
|
| 514 |
+
|
| 515 |
+
'@serialport/[email protected]':
|
| 516 |
+
resolution: {integrity: sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==}
|
| 517 |
+
engines: {node: '>=12.0.0'}
|
| 518 |
+
|
| 519 |
+
'@serialport/[email protected]':
|
| 520 |
+
resolution: {integrity: sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==}
|
| 521 |
+
engines: {node: '>=16.0.0'}
|
| 522 |
+
|
| 523 |
+
'@serialport/[email protected]':
|
| 524 |
+
resolution: {integrity: sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==}
|
| 525 |
+
engines: {node: ^12.22 || ^14.13 || >=16}
|
| 526 |
+
|
| 527 |
+
'@serialport/[email protected]':
|
| 528 |
+
resolution: {integrity: sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==}
|
| 529 |
+
engines: {node: '>=12.0.0'}
|
| 530 |
+
|
| 531 |
+
'@serialport/[email protected]':
|
| 532 |
+
resolution: {integrity: sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==}
|
| 533 |
+
engines: {node: '>=12.0.0'}
|
| 534 |
+
|
| 535 |
+
'@serialport/[email protected]':
|
| 536 |
+
resolution: {integrity: sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==}
|
| 537 |
+
engines: {node: '>=12.0.0'}
|
| 538 |
+
|
| 539 |
+
'@serialport/[email protected]':
|
| 540 |
+
resolution: {integrity: sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==}
|
| 541 |
+
engines: {node: '>=12.0.0'}
|
| 542 |
+
|
| 543 |
+
'@serialport/[email protected]':
|
| 544 |
+
resolution: {integrity: sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==}
|
| 545 |
+
engines: {node: '>=12.0.0'}
|
| 546 |
+
|
| 547 |
+
'@serialport/[email protected]':
|
| 548 |
+
resolution: {integrity: sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==}
|
| 549 |
+
engines: {node: '>=8.6.0'}
|
| 550 |
+
|
| 551 |
+
'@serialport/[email protected]':
|
| 552 |
+
resolution: {integrity: sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==}
|
| 553 |
+
engines: {node: '>=12.0.0'}
|
| 554 |
+
|
| 555 |
+
'@serialport/[email protected]':
|
| 556 |
+
resolution: {integrity: sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==}
|
| 557 |
+
engines: {node: '>=12.0.0'}
|
| 558 |
+
|
| 559 |
+
'@serialport/[email protected]':
|
| 560 |
+
resolution: {integrity: sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==}
|
| 561 |
+
engines: {node: '>=12.0.0'}
|
| 562 |
+
|
| 563 |
+
'@serialport/[email protected]':
|
| 564 |
+
resolution: {integrity: sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==}
|
| 565 |
+
engines: {node: '>=12.0.0'}
|
| 566 |
+
|
| 567 |
+
'@serialport/[email protected]':
|
| 568 |
+
resolution: {integrity: sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==}
|
| 569 |
+
engines: {node: '>=12.0.0'}
|
| 570 |
+
|
| 571 |
+
'@serialport/[email protected]':
|
| 572 |
+
resolution: {integrity: sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==}
|
| 573 |
+
engines: {node: '>=12.0.0'}
|
| 574 |
+
|
| 575 |
+
'@serialport/[email protected]':
|
| 576 |
+
resolution: {integrity: sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==}
|
| 577 |
+
engines: {node: '>=12.0.0'}
|
| 578 |
+
|
| 579 |
+
'@types/[email protected]':
|
| 580 |
+
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
| 581 |
+
|
| 582 |
+
'@types/[email protected]':
|
| 583 |
+
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
| 584 |
+
|
| 585 |
+
'@types/[email protected]':
|
| 586 |
+
resolution: {integrity: sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==}
|
| 587 |
+
|
| 588 |
+
'@typescript-eslint/[email protected]':
|
| 589 |
+
resolution: {integrity: sha512-8fz6oa6wEKZrhXWro/S3n2eRJqlRcIa6SlDh59FXJ5Wp5XRZ8B9ixpJDcjadHq47hMx0u+HW6SNa6LjJQ6NLtw==}
|
| 590 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 591 |
+
peerDependencies:
|
| 592 |
+
'@typescript-eslint/parser': ^8.41.0
|
| 593 |
+
eslint: ^8.57.0 || ^9.0.0
|
| 594 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 595 |
+
|
| 596 |
+
'@typescript-eslint/[email protected]':
|
| 597 |
+
resolution: {integrity: sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==}
|
| 598 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 599 |
+
peerDependencies:
|
| 600 |
+
eslint: ^8.57.0 || ^9.0.0
|
| 601 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 602 |
+
|
| 603 |
+
'@typescript-eslint/[email protected]':
|
| 604 |
+
resolution: {integrity: sha512-b8V9SdGBQzQdjJ/IO3eDifGpDBJfvrNTp2QD9P2BeqWTGrRibgfgIlBSw6z3b6R7dPzg752tOs4u/7yCLxksSQ==}
|
| 605 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 606 |
+
peerDependencies:
|
| 607 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 608 |
+
|
| 609 |
+
'@typescript-eslint/[email protected]':
|
| 610 |
+
resolution: {integrity: sha512-n6m05bXn/Cd6DZDGyrpXrELCPVaTnLdPToyhBoFkLIMznRUQUEQdSp96s/pcWSQdqOhrgR1mzJ+yItK7T+WPMQ==}
|
| 611 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 612 |
+
|
| 613 |
+
'@typescript-eslint/[email protected]':
|
| 614 |
+
resolution: {integrity: sha512-TDhxYFPUYRFxFhuU5hTIJk+auzM/wKvWgoNYOPcOf6i4ReYlOoYN8q1dV5kOTjNQNJgzWN3TUUQMtlLOcUgdUw==}
|
| 615 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 616 |
+
peerDependencies:
|
| 617 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 618 |
+
|
| 619 |
+
'@typescript-eslint/[email protected]':
|
| 620 |
+
resolution: {integrity: sha512-63qt1h91vg3KsjVVonFJWjgSK7pZHSQFKH6uwqxAH9bBrsyRhO6ONoKyXxyVBzG1lJnFAJcKAcxLS54N1ee1OQ==}
|
| 621 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 622 |
+
peerDependencies:
|
| 623 |
+
eslint: ^8.57.0 || ^9.0.0
|
| 624 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 625 |
+
|
| 626 |
+
'@typescript-eslint/[email protected]':
|
| 627 |
+
resolution: {integrity: sha512-9EwxsWdVqh42afLbHP90n2VdHaWU/oWgbH2P0CfcNfdKL7CuKpwMQGjwev56vWu9cSKU7FWSu6r9zck6CVfnag==}
|
| 628 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 629 |
+
|
| 630 |
+
'@typescript-eslint/[email protected]':
|
| 631 |
+
resolution: {integrity: sha512-D43UwUYJmGhuwHfY7MtNKRZMmfd8+p/eNSfFe6tH5mbVDto+VQCayeAt35rOx3Cs6wxD16DQtIKw/YXxt5E0UQ==}
|
| 632 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 633 |
+
peerDependencies:
|
| 634 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 635 |
+
|
| 636 |
+
'@typescript-eslint/[email protected]':
|
| 637 |
+
resolution: {integrity: sha512-udbCVstxZ5jiPIXrdH+BZWnPatjlYwJuJkDA4Tbo3WyYLh8NvB+h/bKeSZHDOFKfphsZYJQqaFtLeXEqurQn1A==}
|
| 638 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 639 |
+
peerDependencies:
|
| 640 |
+
eslint: ^8.57.0 || ^9.0.0
|
| 641 |
+
typescript: '>=4.8.4 <6.0.0'
|
| 642 |
+
|
| 643 |
+
'@typescript-eslint/[email protected]':
|
| 644 |
+
resolution: {integrity: sha512-+GeGMebMCy0elMNg67LRNoVnUFPIm37iu5CmHESVx56/9Jsfdpsvbv605DQ81Pi/x11IdKUsS5nzgTYbCQU9fg==}
|
| 645 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 646 |
+
|
| 647 |
+
'@vitest/[email protected]':
|
| 648 |
+
resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==}
|
| 649 |
+
|
| 650 |
+
'@vitest/[email protected]':
|
| 651 |
+
resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==}
|
| 652 |
+
peerDependencies:
|
| 653 |
+
msw: ^2.4.9
|
| 654 |
+
vite: ^5.0.0
|
| 655 |
+
peerDependenciesMeta:
|
| 656 |
+
msw:
|
| 657 |
+
optional: true
|
| 658 |
+
vite:
|
| 659 |
+
optional: true
|
| 660 |
+
|
| 661 |
+
'@vitest/[email protected]':
|
| 662 |
+
resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==}
|
| 663 |
+
|
| 664 |
+
'@vitest/[email protected]':
|
| 665 |
+
resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==}
|
| 666 |
+
|
| 667 |
+
'@vitest/[email protected]':
|
| 668 |
+
resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==}
|
| 669 |
+
|
| 670 |
+
'@vitest/[email protected]':
|
| 671 |
+
resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==}
|
| 672 |
+
|
| 673 |
+
'@vitest/[email protected]':
|
| 674 |
+
resolution: {integrity: sha512-izzd2zmnk8Nl5ECYkW27328RbQ1nKvkm6Bb5DAaz1Gk59EbLkiCMa6OLT0NoaAYTjOFS6N+SMYW1nh4/9ljPiw==}
|
| 675 |
+
peerDependencies:
|
| 676 |
+
vitest: 2.1.9
|
| 677 |
+
|
| 678 |
+
'@vitest/[email protected]':
|
| 679 |
+
resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==}
|
| 680 |
+
|
| 681 | |
| 682 |
+
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
| 683 |
+
peerDependencies:
|
| 684 |
+
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
| 685 |
+
|
| 686 | |
| 687 |
+
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
|
| 688 |
+
engines: {node: '>=0.4.0'}
|
| 689 |
+
hasBin: true
|
| 690 |
+
|
| 691 | |
| 692 |
+
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
| 693 |
+
|
| 694 | |
| 695 |
+
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
| 696 |
+
engines: {node: '>=8'}
|
| 697 |
+
|
| 698 | |
| 699 |
+
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
| 700 |
+
|
| 701 | |
| 702 |
+
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
| 703 |
+
engines: {node: '>=12'}
|
| 704 |
+
|
| 705 | |
| 706 |
+
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
| 707 |
+
|
| 708 | |
| 709 |
+
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
| 710 |
+
|
| 711 | |
| 712 |
+
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
|
| 713 |
+
|
| 714 | |
| 715 |
+
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
| 716 |
+
engines: {node: '>=8'}
|
| 717 |
+
|
| 718 | |
| 719 |
+
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
| 720 |
+
engines: {node: '>=8'}
|
| 721 |
+
|
| 722 | |
| 723 |
+
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
| 724 |
+
engines: {node: '>=6'}
|
| 725 |
+
|
| 726 | |
| 727 |
+
resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==}
|
| 728 |
+
engines: {node: '>=18'}
|
| 729 |
+
|
| 730 | |
| 731 |
+
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
| 732 |
+
engines: {node: '>=10'}
|
| 733 |
+
|
| 734 | |
| 735 |
+
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
|
| 736 |
+
engines: {node: '>= 16'}
|
| 737 |
+
|
| 738 | |
| 739 |
+
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
| 740 |
+
engines: {node: '>=7.0.0'}
|
| 741 |
+
|
| 742 | |
| 743 |
+
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
| 744 |
+
|
| 745 | |
| 746 |
+
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
| 747 |
+
|
| 748 | |
| 749 |
+
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
| 750 |
+
engines: {node: '>= 8'}
|
| 751 |
+
|
| 752 | |
| 753 |
+
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
| 754 |
+
engines: {node: '>=6.0'}
|
| 755 |
+
peerDependencies:
|
| 756 |
+
supports-color: '*'
|
| 757 |
+
peerDependenciesMeta:
|
| 758 |
+
supports-color:
|
| 759 |
+
optional: true
|
| 760 |
+
|
| 761 | |
| 762 |
+
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
| 763 |
+
engines: {node: '>=6.0'}
|
| 764 |
+
peerDependencies:
|
| 765 |
+
supports-color: '*'
|
| 766 |
+
peerDependenciesMeta:
|
| 767 |
+
supports-color:
|
| 768 |
+
optional: true
|
| 769 |
+
|
| 770 | |
| 771 |
+
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
| 772 |
+
engines: {node: '>=6'}
|
| 773 |
+
|
| 774 | |
| 775 |
+
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
| 776 |
+
|
| 777 | |
| 778 |
+
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
| 779 |
+
|
| 780 | |
| 781 |
+
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
|
| 782 |
+
engines: {node: '>=12'}
|
| 783 |
+
hasBin: true
|
| 784 |
+
|
| 785 | |
| 786 |
+
resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==}
|
| 787 |
+
engines: {node: '>=18'}
|
| 788 |
+
hasBin: true
|
| 789 |
+
|
| 790 | |
| 791 |
+
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
| 792 |
+
engines: {node: '>=10'}
|
| 793 |
+
|
| 794 | |
| 795 |
+
resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
|
| 796 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 797 |
+
|
| 798 | |
| 799 |
+
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
|
| 800 |
+
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
| 801 |
+
|
| 802 | |
| 803 |
+
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
|
| 804 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 805 |
+
|
| 806 | |
| 807 |
+
resolution: {integrity: sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==}
|
| 808 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 809 |
+
hasBin: true
|
| 810 |
+
peerDependencies:
|
| 811 |
+
jiti: '*'
|
| 812 |
+
peerDependenciesMeta:
|
| 813 |
+
jiti:
|
| 814 |
+
optional: true
|
| 815 |
+
|
| 816 | |
| 817 |
+
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
|
| 818 |
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
| 819 |
+
|
| 820 | |
| 821 |
+
resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
|
| 822 |
+
engines: {node: '>=0.10'}
|
| 823 |
+
|
| 824 | |
| 825 |
+
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
|
| 826 |
+
engines: {node: '>=4.0'}
|
| 827 |
+
|
| 828 | |
| 829 |
+
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
|
| 830 |
+
engines: {node: '>=4.0'}
|
| 831 |
+
|
| 832 | |
| 833 |
+
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
| 834 |
+
|
| 835 | |
| 836 |
+
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
| 837 |
+
engines: {node: '>=0.10.0'}
|
| 838 |
+
|
| 839 | |
| 840 |
+
resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==}
|
| 841 |
+
engines: {node: '>=12.0.0'}
|
| 842 |
+
|
| 843 | |
| 844 |
+
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
| 845 |
+
|
| 846 | |
| 847 |
+
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
|
| 848 |
+
engines: {node: '>=8.6.0'}
|
| 849 |
+
|
| 850 | |
| 851 |
+
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
| 852 |
+
|
| 853 | |
| 854 |
+
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
| 855 |
+
|
| 856 | |
| 857 |
+
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
|
| 858 |
+
|
| 859 | |
| 860 |
+
resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
|
| 861 |
+
peerDependencies:
|
| 862 |
+
picomatch: ^3 || ^4
|
| 863 |
+
peerDependenciesMeta:
|
| 864 |
+
picomatch:
|
| 865 |
+
optional: true
|
| 866 |
+
|
| 867 | |
| 868 |
+
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
|
| 869 |
+
|
| 870 | |
| 871 |
+
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
| 872 |
+
engines: {node: '>=16.0.0'}
|
| 873 |
+
|
| 874 | |
| 875 |
+
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
| 876 |
+
engines: {node: '>=8'}
|
| 877 |
+
|
| 878 | |
| 879 |
+
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
| 880 |
+
engines: {node: '>=10'}
|
| 881 |
+
|
| 882 | |
| 883 |
+
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
|
| 884 |
+
engines: {node: '>=16'}
|
| 885 |
+
|
| 886 | |
| 887 |
+
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
|
| 888 |
+
|
| 889 | |
| 890 |
+
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
| 891 |
+
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
| 892 |
+
os: [darwin]
|
| 893 |
+
|
| 894 | |
| 895 |
+
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
| 896 |
+
engines: {node: '>= 6'}
|
| 897 |
+
|
| 898 | |
| 899 |
+
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
| 900 |
+
engines: {node: '>=10.13.0'}
|
| 901 |
+
|
| 902 | |
| 903 |
+
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
|
| 904 |
+
engines: {node: '>=18'}
|
| 905 |
+
|
| 906 | |
| 907 |
+
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
| 908 |
+
|
| 909 | |
| 910 |
+
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
| 911 |
+
engines: {node: '>=8'}
|
| 912 |
+
|
| 913 | |
| 914 |
+
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
| 915 |
+
engines: {node: '>= 4'}
|
| 916 |
+
|
| 917 | |
| 918 |
+
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
|
| 919 |
+
engines: {node: '>= 4'}
|
| 920 |
+
|
| 921 | |
| 922 |
+
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
| 923 |
+
engines: {node: '>=6'}
|
| 924 |
+
|
| 925 | |
| 926 |
+
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
| 927 |
+
engines: {node: '>=0.8.19'}
|
| 928 |
+
|
| 929 | |
| 930 |
+
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
| 931 |
+
engines: {node: '>=0.10.0'}
|
| 932 |
+
|
| 933 | |
| 934 |
+
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
| 935 |
+
engines: {node: '>=0.10.0'}
|
| 936 |
+
|
| 937 | |
| 938 |
+
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
| 939 |
+
engines: {node: '>=0.12.0'}
|
| 940 |
+
|
| 941 | |
| 942 |
+
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
| 943 |
+
|
| 944 | |
| 945 |
+
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
| 946 |
+
hasBin: true
|
| 947 |
+
|
| 948 | |
| 949 |
+
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
|
| 950 |
+
|
| 951 | |
| 952 |
+
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
| 953 |
+
|
| 954 | |
| 955 |
+
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
| 956 |
+
|
| 957 | |
| 958 |
+
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
| 959 |
+
|
| 960 | |
| 961 |
+
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
| 962 |
+
engines: {node: '>= 0.8.0'}
|
| 963 |
+
|
| 964 | |
| 965 |
+
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
| 966 |
+
engines: {node: '>=10'}
|
| 967 |
+
|
| 968 | |
| 969 |
+
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
| 970 |
+
|
| 971 | |
| 972 |
+
resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==}
|
| 973 |
+
|
| 974 | |
| 975 |
+
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
| 976 |
+
|
| 977 | |
| 978 |
+
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
| 979 |
+
engines: {node: '>= 8'}
|
| 980 |
+
|
| 981 | |
| 982 |
+
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
| 983 |
+
engines: {node: '>=8.6'}
|
| 984 |
+
|
| 985 | |
| 986 |
+
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
| 987 |
+
|
| 988 | |
| 989 |
+
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
| 990 |
+
engines: {node: '>=16 || 14 >=14.17'}
|
| 991 |
+
|
| 992 | |
| 993 |
+
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
| 994 |
+
engines: {node: '>=10'}
|
| 995 |
+
|
| 996 | |
| 997 |
+
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
| 998 |
+
|
| 999 | |
| 1000 |
+
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
| 1001 |
+
|
| 1002 | |
| 1003 |
+
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
| 1004 |
+
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
| 1005 |
+
hasBin: true
|
| 1006 |
+
|
| 1007 | |
| 1008 |
+
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
| 1009 |
+
|
| 1010 | |
| 1011 |
+
resolution: {integrity: sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==}
|
| 1012 |
+
|
| 1013 | |
| 1014 |
+
resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
|
| 1015 |
+
hasBin: true
|
| 1016 |
+
|
| 1017 | |
| 1018 |
+
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
| 1019 |
+
engines: {node: '>= 0.8.0'}
|
| 1020 |
+
|
| 1021 | |
| 1022 |
+
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
| 1023 |
+
engines: {node: '>=10'}
|
| 1024 |
+
|
| 1025 | |
| 1026 |
+
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
| 1027 |
+
engines: {node: '>=10'}
|
| 1028 |
+
|
| 1029 | |
| 1030 |
+
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
| 1031 |
+
engines: {node: '>=6'}
|
| 1032 |
+
|
| 1033 | |
| 1034 |
+
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
| 1035 |
+
engines: {node: '>=8'}
|
| 1036 |
+
|
| 1037 | |
| 1038 |
+
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
| 1039 |
+
engines: {node: '>=8'}
|
| 1040 |
+
|
| 1041 | |
| 1042 |
+
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
|
| 1043 |
+
|
| 1044 | |
| 1045 |
+
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
|
| 1046 |
+
engines: {node: '>= 14.16'}
|
| 1047 |
+
|
| 1048 | |
| 1049 |
+
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
| 1050 |
+
|
| 1051 | |
| 1052 |
+
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
| 1053 |
+
engines: {node: '>=8.6'}
|
| 1054 |
+
|
| 1055 | |
| 1056 |
+
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
| 1057 |
+
engines: {node: '>=12'}
|
| 1058 |
+
|
| 1059 | |
| 1060 |
+
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
| 1061 |
+
engines: {node: ^10 || ^12 || >=14}
|
| 1062 |
+
|
| 1063 | |
| 1064 |
+
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
| 1065 |
+
engines: {node: '>= 0.8.0'}
|
| 1066 |
+
|
| 1067 | |
| 1068 |
+
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
| 1069 |
+
engines: {node: '>=6'}
|
| 1070 |
+
|
| 1071 | |
| 1072 |
+
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
| 1073 |
+
|
| 1074 | |
| 1075 |
+
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
| 1076 |
+
engines: {node: '>=4'}
|
| 1077 |
+
|
| 1078 | |
| 1079 |
+
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
| 1080 |
+
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
| 1081 |
+
|
| 1082 | |
| 1083 |
+
resolution: {integrity: sha512-ONmkT3Ud3IfW15nl7l4qAZko5/2iZ5ALVBDh02ZSZ5IGVLJSYkRcRa3iB58VyEIyoofs9m2xdVrm+lTi97+3pw==}
|
| 1084 |
+
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
| 1085 |
+
hasBin: true
|
| 1086 |
+
|
| 1087 | |
| 1088 |
+
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
| 1089 |
+
|
| 1090 | |
| 1091 |
+
resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
|
| 1092 |
+
engines: {node: '>=10'}
|
| 1093 |
+
hasBin: true
|
| 1094 |
+
|
| 1095 | |
| 1096 |
+
resolution: {integrity: sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==}
|
| 1097 |
+
engines: {node: '>=16.0.0'}
|
| 1098 |
+
|
| 1099 | |
| 1100 |
+
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
| 1101 |
+
engines: {node: '>=8'}
|
| 1102 |
+
|
| 1103 | |
| 1104 |
+
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
| 1105 |
+
engines: {node: '>=8'}
|
| 1106 |
+
|
| 1107 | |
| 1108 |
+
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
| 1109 |
+
|
| 1110 | |
| 1111 |
+
resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==}
|
| 1112 |
+
engines: {node: '>=18'}
|
| 1113 |
+
|
| 1114 | |
| 1115 |
+
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
| 1116 |
+
engines: {node: '>=0.10.0'}
|
| 1117 |
+
|
| 1118 | |
| 1119 |
+
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
| 1120 |
+
|
| 1121 | |
| 1122 |
+
resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
|
| 1123 |
+
|
| 1124 | |
| 1125 |
+
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
| 1126 |
+
engines: {node: '>=8'}
|
| 1127 |
+
|
| 1128 | |
| 1129 |
+
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
| 1130 |
+
engines: {node: '>=8'}
|
| 1131 |
+
|
| 1132 | |
| 1133 |
+
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
| 1134 |
+
|
| 1135 | |
| 1136 |
+
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
| 1137 |
+
|
| 1138 | |
| 1139 |
+
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
|
| 1140 |
+
engines: {node: '>=12.0.0'}
|
| 1141 |
+
|
| 1142 | |
| 1143 |
+
resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
|
| 1144 |
+
engines: {node: ^18.0.0 || >=20.0.0}
|
| 1145 |
+
|
| 1146 | |
| 1147 |
+
resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
|
| 1148 |
+
engines: {node: '>=14.0.0'}
|
| 1149 |
+
|
| 1150 | |
| 1151 |
+
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
|
| 1152 |
+
engines: {node: '>=14.0.0'}
|
| 1153 |
+
|
| 1154 | |
| 1155 |
+
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
| 1156 |
+
engines: {node: '>=8.0'}
|
| 1157 |
+
|
| 1158 | |
| 1159 |
+
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
| 1160 |
+
engines: {node: '>=6'}
|
| 1161 |
+
|
| 1162 | |
| 1163 |
+
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
|
| 1164 |
+
engines: {node: '>=18.12'}
|
| 1165 |
+
peerDependencies:
|
| 1166 |
+
typescript: '>=4.8.4'
|
| 1167 |
+
|
| 1168 | |
| 1169 |
+
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
| 1170 |
+
engines: {node: '>= 0.8.0'}
|
| 1171 |
+
|
| 1172 | |
| 1173 |
+
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
| 1174 |
+
engines: {node: '>=14.17'}
|
| 1175 |
+
hasBin: true
|
| 1176 |
+
|
| 1177 | |
| 1178 |
+
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
| 1179 |
+
|
| 1180 | |
| 1181 |
+
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
| 1182 |
+
|
| 1183 | |
| 1184 |
+
resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==}
|
| 1185 |
+
engines: {node: ^18.0.0 || >=20.0.0}
|
| 1186 |
+
hasBin: true
|
| 1187 |
+
|
| 1188 | |
| 1189 |
+
resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==}
|
| 1190 |
+
engines: {node: ^18.0.0 || >=20.0.0}
|
| 1191 |
+
hasBin: true
|
| 1192 |
+
peerDependencies:
|
| 1193 |
+
'@types/node': ^18.0.0 || >=20.0.0
|
| 1194 |
+
less: '*'
|
| 1195 |
+
lightningcss: ^1.21.0
|
| 1196 |
+
sass: '*'
|
| 1197 |
+
sass-embedded: '*'
|
| 1198 |
+
stylus: '*'
|
| 1199 |
+
sugarss: '*'
|
| 1200 |
+
terser: ^5.4.0
|
| 1201 |
+
peerDependenciesMeta:
|
| 1202 |
+
'@types/node':
|
| 1203 |
+
optional: true
|
| 1204 |
+
less:
|
| 1205 |
+
optional: true
|
| 1206 |
+
lightningcss:
|
| 1207 |
+
optional: true
|
| 1208 |
+
sass:
|
| 1209 |
+
optional: true
|
| 1210 |
+
sass-embedded:
|
| 1211 |
+
optional: true
|
| 1212 |
+
stylus:
|
| 1213 |
+
optional: true
|
| 1214 |
+
sugarss:
|
| 1215 |
+
optional: true
|
| 1216 |
+
terser:
|
| 1217 |
+
optional: true
|
| 1218 |
+
|
| 1219 | |
| 1220 |
+
resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==}
|
| 1221 |
+
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
| 1222 |
+
hasBin: true
|
| 1223 |
+
peerDependencies:
|
| 1224 |
+
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
|
| 1225 |
+
jiti: '>=1.21.0'
|
| 1226 |
+
less: '*'
|
| 1227 |
+
lightningcss: ^1.21.0
|
| 1228 |
+
sass: '*'
|
| 1229 |
+
sass-embedded: '*'
|
| 1230 |
+
stylus: '*'
|
| 1231 |
+
sugarss: '*'
|
| 1232 |
+
terser: ^5.16.0
|
| 1233 |
+
tsx: ^4.8.1
|
| 1234 |
+
yaml: ^2.4.2
|
| 1235 |
+
peerDependenciesMeta:
|
| 1236 |
+
'@types/node':
|
| 1237 |
+
optional: true
|
| 1238 |
+
jiti:
|
| 1239 |
+
optional: true
|
| 1240 |
+
less:
|
| 1241 |
+
optional: true
|
| 1242 |
+
lightningcss:
|
| 1243 |
+
optional: true
|
| 1244 |
+
sass:
|
| 1245 |
+
optional: true
|
| 1246 |
+
sass-embedded:
|
| 1247 |
+
optional: true
|
| 1248 |
+
stylus:
|
| 1249 |
+
optional: true
|
| 1250 |
+
sugarss:
|
| 1251 |
+
optional: true
|
| 1252 |
+
terser:
|
| 1253 |
+
optional: true
|
| 1254 |
+
tsx:
|
| 1255 |
+
optional: true
|
| 1256 |
+
yaml:
|
| 1257 |
+
optional: true
|
| 1258 |
+
|
| 1259 | |
| 1260 |
+
resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==}
|
| 1261 |
+
engines: {node: ^18.0.0 || >=20.0.0}
|
| 1262 |
+
hasBin: true
|
| 1263 |
+
peerDependencies:
|
| 1264 |
+
'@edge-runtime/vm': '*'
|
| 1265 |
+
'@types/node': ^18.0.0 || >=20.0.0
|
| 1266 |
+
'@vitest/browser': 2.1.9
|
| 1267 |
+
'@vitest/ui': 2.1.9
|
| 1268 |
+
happy-dom: '*'
|
| 1269 |
+
jsdom: '*'
|
| 1270 |
+
peerDependenciesMeta:
|
| 1271 |
+
'@edge-runtime/vm':
|
| 1272 |
+
optional: true
|
| 1273 |
+
'@types/node':
|
| 1274 |
+
optional: true
|
| 1275 |
+
'@vitest/browser':
|
| 1276 |
+
optional: true
|
| 1277 |
+
'@vitest/ui':
|
| 1278 |
+
optional: true
|
| 1279 |
+
happy-dom:
|
| 1280 |
+
optional: true
|
| 1281 |
+
jsdom:
|
| 1282 |
+
optional: true
|
| 1283 |
+
|
| 1284 | |
| 1285 |
+
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
| 1286 |
+
engines: {node: '>= 8'}
|
| 1287 |
+
hasBin: true
|
| 1288 |
+
|
| 1289 | |
| 1290 |
+
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
| 1291 |
+
engines: {node: '>=8'}
|
| 1292 |
+
hasBin: true
|
| 1293 |
+
|
| 1294 | |
| 1295 |
+
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
| 1296 |
+
engines: {node: '>=0.10.0'}
|
| 1297 |
+
|
| 1298 | |
| 1299 |
+
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
| 1300 |
+
engines: {node: '>=10'}
|
| 1301 |
+
|
| 1302 |
+
snapshots:
|
| 1303 |
+
|
| 1304 |
+
'@esbuild/[email protected]':
|
| 1305 |
+
optional: true
|
| 1306 |
+
|
| 1307 |
+
'@esbuild/[email protected]':
|
| 1308 |
+
optional: true
|
| 1309 |
+
|
| 1310 |
+
'@esbuild/[email protected]':
|
| 1311 |
+
optional: true
|
| 1312 |
+
|
| 1313 |
+
'@esbuild/[email protected]':
|
| 1314 |
+
optional: true
|
| 1315 |
+
|
| 1316 |
+
'@esbuild/[email protected]':
|
| 1317 |
+
optional: true
|
| 1318 |
+
|
| 1319 |
+
'@esbuild/[email protected]':
|
| 1320 |
+
optional: true
|
| 1321 |
+
|
| 1322 |
+
'@esbuild/[email protected]':
|
| 1323 |
+
optional: true
|
| 1324 |
+
|
| 1325 |
+
'@esbuild/[email protected]':
|
| 1326 |
+
optional: true
|
| 1327 |
+
|
| 1328 |
+
'@esbuild/[email protected]':
|
| 1329 |
+
optional: true
|
| 1330 |
+
|
| 1331 |
+
'@esbuild/[email protected]':
|
| 1332 |
+
optional: true
|
| 1333 |
+
|
| 1334 |
+
'@esbuild/[email protected]':
|
| 1335 |
+
optional: true
|
| 1336 |
+
|
| 1337 |
+
'@esbuild/[email protected]':
|
| 1338 |
+
optional: true
|
| 1339 |
+
|
| 1340 |
+
'@esbuild/[email protected]':
|
| 1341 |
+
optional: true
|
| 1342 |
+
|
| 1343 |
+
'@esbuild/[email protected]':
|
| 1344 |
+
optional: true
|
| 1345 |
+
|
| 1346 |
+
'@esbuild/[email protected]':
|
| 1347 |
+
optional: true
|
| 1348 |
+
|
| 1349 |
+
'@esbuild/[email protected]':
|
| 1350 |
+
optional: true
|
| 1351 |
+
|
| 1352 |
+
'@esbuild/[email protected]':
|
| 1353 |
+
optional: true
|
| 1354 |
+
|
| 1355 |
+
'@esbuild/[email protected]':
|
| 1356 |
+
optional: true
|
| 1357 |
+
|
| 1358 |
+
'@esbuild/[email protected]':
|
| 1359 |
+
optional: true
|
| 1360 |
+
|
| 1361 |
+
'@esbuild/[email protected]':
|
| 1362 |
+
optional: true
|
| 1363 |
+
|
| 1364 |
+
'@esbuild/[email protected]':
|
| 1365 |
+
optional: true
|
| 1366 |
+
|
| 1367 |
+
'@esbuild/[email protected]':
|
| 1368 |
+
optional: true
|
| 1369 |
+
|
| 1370 |
+
'@esbuild/[email protected]':
|
| 1371 |
+
optional: true
|
| 1372 |
+
|
| 1373 |
+
'@esbuild/[email protected]':
|
| 1374 |
+
optional: true
|
| 1375 |
+
|
| 1376 |
+
'@esbuild/[email protected]':
|
| 1377 |
+
optional: true
|
| 1378 |
+
|
| 1379 |
+
'@esbuild/[email protected]':
|
| 1380 |
+
optional: true
|
| 1381 |
+
|
| 1382 |
+
'@esbuild/[email protected]':
|
| 1383 |
+
optional: true
|
| 1384 |
+
|
| 1385 |
+
'@esbuild/[email protected]':
|
| 1386 |
+
optional: true
|
| 1387 |
+
|
| 1388 |
+
'@esbuild/[email protected]':
|
| 1389 |
+
optional: true
|
| 1390 |
+
|
| 1391 |
+
'@esbuild/[email protected]':
|
| 1392 |
+
optional: true
|
| 1393 |
+
|
| 1394 |
+
'@esbuild/[email protected]':
|
| 1395 |
+
optional: true
|
| 1396 |
+
|
| 1397 |
+
'@esbuild/[email protected]':
|
| 1398 |
+
optional: true
|
| 1399 |
+
|
| 1400 |
+
'@esbuild/[email protected]':
|
| 1401 |
+
optional: true
|
| 1402 |
+
|
| 1403 |
+
'@esbuild/[email protected]':
|
| 1404 |
+
optional: true
|
| 1405 |
+
|
| 1406 |
+
'@esbuild/[email protected]':
|
| 1407 |
+
optional: true
|
| 1408 |
+
|
| 1409 |
+
'@esbuild/[email protected]':
|
| 1410 |
+
optional: true
|
| 1411 |
+
|
| 1412 |
+
'@esbuild/[email protected]':
|
| 1413 |
+
optional: true
|
| 1414 |
+
|
| 1415 |
+
'@esbuild/[email protected]':
|
| 1416 |
+
optional: true
|
| 1417 |
+
|
| 1418 |
+
'@esbuild/[email protected]':
|
| 1419 |
+
optional: true
|
| 1420 |
+
|
| 1421 |
+
'@esbuild/[email protected]':
|
| 1422 |
+
optional: true
|
| 1423 |
+
|
| 1424 |
+
'@esbuild/[email protected]':
|
| 1425 |
+
optional: true
|
| 1426 |
+
|
| 1427 |
+
'@esbuild/[email protected]':
|
| 1428 |
+
optional: true
|
| 1429 |
+
|
| 1430 |
+
'@esbuild/[email protected]':
|
| 1431 |
+
optional: true
|
| 1432 |
+
|
| 1433 |
+
'@esbuild/[email protected]':
|
| 1434 |
+
optional: true
|
| 1435 |
+
|
| 1436 |
+
'@esbuild/[email protected]':
|
| 1437 |
+
optional: true
|
| 1438 |
+
|
| 1439 |
+
'@esbuild/[email protected]':
|
| 1440 |
+
optional: true
|
| 1441 |
+
|
| 1442 |
+
'@esbuild/[email protected]':
|
| 1443 |
+
optional: true
|
| 1444 |
+
|
| 1445 |
+
'@esbuild/[email protected]':
|
| 1446 |
+
optional: true
|
| 1447 |
+
|
| 1448 |
+
'@esbuild/[email protected]':
|
| 1449 |
+
optional: true
|
| 1450 |
+
|
| 1451 |
+
'@eslint-community/[email protected]([email protected])':
|
| 1452 |
+
dependencies:
|
| 1453 |
+
eslint: 9.34.0
|
| 1454 |
+
eslint-visitor-keys: 3.4.3
|
| 1455 |
+
|
| 1456 |
+
'@eslint-community/[email protected]': {}
|
| 1457 |
+
|
| 1458 |
+
'@eslint/[email protected]':
|
| 1459 |
+
dependencies:
|
| 1460 |
+
'@eslint/object-schema': 2.1.6
|
| 1461 |
+
debug: 4.4.1
|
| 1462 |
+
minimatch: 3.1.2
|
| 1463 |
+
transitivePeerDependencies:
|
| 1464 |
+
- supports-color
|
| 1465 |
+
|
| 1466 |
+
'@eslint/[email protected]': {}
|
| 1467 |
+
|
| 1468 |
+
'@eslint/[email protected]':
|
| 1469 |
+
dependencies:
|
| 1470 |
+
'@types/json-schema': 7.0.15
|
| 1471 |
+
|
| 1472 |
+
'@eslint/[email protected]':
|
| 1473 |
+
dependencies:
|
| 1474 |
+
ajv: 6.12.6
|
| 1475 |
+
debug: 4.4.1
|
| 1476 |
+
espree: 10.4.0
|
| 1477 |
+
globals: 14.0.0
|
| 1478 |
+
ignore: 5.3.2
|
| 1479 |
+
import-fresh: 3.3.1
|
| 1480 |
+
js-yaml: 4.1.0
|
| 1481 |
+
minimatch: 3.1.2
|
| 1482 |
+
strip-json-comments: 3.1.1
|
| 1483 |
+
transitivePeerDependencies:
|
| 1484 |
+
- supports-color
|
| 1485 |
+
|
| 1486 |
+
'@eslint/[email protected]': {}
|
| 1487 |
+
|
| 1488 |
+
'@eslint/[email protected]': {}
|
| 1489 |
+
|
| 1490 |
+
'@eslint/[email protected]':
|
| 1491 |
+
dependencies:
|
| 1492 |
+
'@eslint/core': 0.15.2
|
| 1493 |
+
levn: 0.4.1
|
| 1494 |
+
|
| 1495 |
+
'@humanfs/[email protected]': {}
|
| 1496 |
+
|
| 1497 |
+
'@humanfs/[email protected]':
|
| 1498 |
+
dependencies:
|
| 1499 |
+
'@humanfs/core': 0.19.1
|
| 1500 |
+
'@humanwhocodes/retry': 0.3.1
|
| 1501 |
+
|
| 1502 |
+
'@humanwhocodes/[email protected]': {}
|
| 1503 |
+
|
| 1504 |
+
'@humanwhocodes/[email protected]': {}
|
| 1505 |
+
|
| 1506 |
+
'@humanwhocodes/[email protected]': {}
|
| 1507 |
+
|
| 1508 |
+
'@jridgewell/[email protected]': {}
|
| 1509 |
+
|
| 1510 |
+
'@nodelib/[email protected]':
|
| 1511 |
+
dependencies:
|
| 1512 |
+
'@nodelib/fs.stat': 2.0.5
|
| 1513 |
+
run-parallel: 1.2.0
|
| 1514 |
+
|
| 1515 |
+
'@nodelib/[email protected]': {}
|
| 1516 |
+
|
| 1517 |
+
'@nodelib/[email protected]':
|
| 1518 |
+
dependencies:
|
| 1519 |
+
'@nodelib/fs.scandir': 2.1.5
|
| 1520 |
+
fastq: 1.19.1
|
| 1521 |
+
|
| 1522 |
+
'@polka/[email protected]': {}
|
| 1523 |
+
|
| 1524 |
+
'@rollup/[email protected]':
|
| 1525 |
+
optional: true
|
| 1526 |
+
|
| 1527 |
+
'@rollup/[email protected]':
|
| 1528 |
+
optional: true
|
| 1529 |
+
|
| 1530 |
+
'@rollup/[email protected]':
|
| 1531 |
+
optional: true
|
| 1532 |
+
|
| 1533 |
+
'@rollup/[email protected]':
|
| 1534 |
+
optional: true
|
| 1535 |
+
|
| 1536 |
+
'@rollup/[email protected]':
|
| 1537 |
+
optional: true
|
| 1538 |
+
|
| 1539 |
+
'@rollup/[email protected]':
|
| 1540 |
+
optional: true
|
| 1541 |
+
|
| 1542 |
+
'@rollup/[email protected]':
|
| 1543 |
+
optional: true
|
| 1544 |
+
|
| 1545 |
+
'@rollup/[email protected]':
|
| 1546 |
+
optional: true
|
| 1547 |
+
|
| 1548 |
+
'@rollup/[email protected]':
|
| 1549 |
+
optional: true
|
| 1550 |
+
|
| 1551 |
+
'@rollup/[email protected]':
|
| 1552 |
+
optional: true
|
| 1553 |
+
|
| 1554 |
+
'@rollup/[email protected]':
|
| 1555 |
+
optional: true
|
| 1556 |
+
|
| 1557 |
+
'@rollup/[email protected]':
|
| 1558 |
+
optional: true
|
| 1559 |
+
|
| 1560 |
+
'@rollup/[email protected]':
|
| 1561 |
+
optional: true
|
| 1562 |
+
|
| 1563 |
+
'@rollup/[email protected]':
|
| 1564 |
+
optional: true
|
| 1565 |
+
|
| 1566 |
+
'@rollup/[email protected]':
|
| 1567 |
+
optional: true
|
| 1568 |
+
|
| 1569 |
+
'@rollup/[email protected]':
|
| 1570 |
+
optional: true
|
| 1571 |
+
|
| 1572 |
+
'@rollup/[email protected]':
|
| 1573 |
+
optional: true
|
| 1574 |
+
|
| 1575 |
+
'@rollup/[email protected]':
|
| 1576 |
+
optional: true
|
| 1577 |
+
|
| 1578 |
+
'@rollup/[email protected]':
|
| 1579 |
+
optional: true
|
| 1580 |
+
|
| 1581 |
+
'@rollup/[email protected]':
|
| 1582 |
+
optional: true
|
| 1583 |
+
|
| 1584 |
+
'@serialport/[email protected]':
|
| 1585 |
+
dependencies:
|
| 1586 |
+
'@serialport/bindings-interface': 1.2.2
|
| 1587 |
+
debug: 4.3.4
|
| 1588 |
+
transitivePeerDependencies:
|
| 1589 |
+
- supports-color
|
| 1590 |
+
|
| 1591 |
+
'@serialport/[email protected]':
|
| 1592 |
+
dependencies:
|
| 1593 |
+
'@serialport/bindings-interface': 1.2.2
|
| 1594 |
+
'@serialport/parser-readline': 11.0.0
|
| 1595 |
+
debug: 4.3.4
|
| 1596 |
+
node-addon-api: 7.0.0
|
| 1597 |
+
node-gyp-build: 4.6.0
|
| 1598 |
+
transitivePeerDependencies:
|
| 1599 |
+
- supports-color
|
| 1600 |
+
|
| 1601 |
+
'@serialport/[email protected]': {}
|
| 1602 |
+
|
| 1603 |
+
'@serialport/[email protected]': {}
|
| 1604 |
+
|
| 1605 |
+
'@serialport/[email protected]': {}
|
| 1606 |
+
|
| 1607 |
+
'@serialport/[email protected]': {}
|
| 1608 |
+
|
| 1609 |
+
'@serialport/[email protected]': {}
|
| 1610 |
+
|
| 1611 |
+
'@serialport/[email protected]': {}
|
| 1612 |
+
|
| 1613 |
+
'@serialport/[email protected]': {}
|
| 1614 |
+
|
| 1615 |
+
'@serialport/[email protected]':
|
| 1616 |
+
dependencies:
|
| 1617 |
+
'@serialport/parser-delimiter': 11.0.0
|
| 1618 |
+
|
| 1619 |
+
'@serialport/[email protected]':
|
| 1620 |
+
dependencies:
|
| 1621 |
+
'@serialport/parser-delimiter': 12.0.0
|
| 1622 |
+
|
| 1623 |
+
'@serialport/[email protected]': {}
|
| 1624 |
+
|
| 1625 |
+
'@serialport/[email protected]': {}
|
| 1626 |
+
|
| 1627 |
+
'@serialport/[email protected]': {}
|
| 1628 |
+
|
| 1629 |
+
'@serialport/[email protected]': {}
|
| 1630 |
+
|
| 1631 |
+
'@serialport/[email protected]':
|
| 1632 |
+
dependencies:
|
| 1633 |
+
'@serialport/bindings-interface': 1.2.2
|
| 1634 |
+
debug: 4.3.4
|
| 1635 |
+
transitivePeerDependencies:
|
| 1636 |
+
- supports-color
|
| 1637 |
+
|
| 1638 |
+
'@types/[email protected]': {}
|
| 1639 |
+
|
| 1640 |
+
'@types/[email protected]': {}
|
| 1641 |
+
|
| 1642 |
+
'@types/[email protected]':
|
| 1643 |
+
dependencies:
|
| 1644 |
+
undici-types: 5.26.5
|
| 1645 |
+
|
| 1646 |
+
'@typescript-eslint/[email protected](@typescript-eslint/[email protected]([email protected])([email protected]))([email protected])([email protected])':
|
| 1647 |
+
dependencies:
|
| 1648 |
+
'@eslint-community/regexpp': 4.12.1
|
| 1649 |
+
'@typescript-eslint/parser': 8.41.0([email protected])([email protected])
|
| 1650 |
+
'@typescript-eslint/scope-manager': 8.41.0
|
| 1651 |
+
'@typescript-eslint/type-utils': 8.41.0([email protected])([email protected])
|
| 1652 |
+
'@typescript-eslint/utils': 8.41.0([email protected])([email protected])
|
| 1653 |
+
'@typescript-eslint/visitor-keys': 8.41.0
|
| 1654 |
+
eslint: 9.34.0
|
| 1655 |
+
graphemer: 1.4.0
|
| 1656 |
+
ignore: 7.0.5
|
| 1657 |
+
natural-compare: 1.4.0
|
| 1658 |
+
ts-api-utils: 2.1.0([email protected])
|
| 1659 |
+
typescript: 5.8.3
|
| 1660 |
+
transitivePeerDependencies:
|
| 1661 |
+
- supports-color
|
| 1662 |
+
|
| 1663 |
+
'@typescript-eslint/[email protected]([email protected])([email protected])':
|
| 1664 |
+
dependencies:
|
| 1665 |
+
'@typescript-eslint/scope-manager': 8.41.0
|
| 1666 |
+
'@typescript-eslint/types': 8.41.0
|
| 1667 |
+
'@typescript-eslint/typescript-estree': 8.41.0([email protected])
|
| 1668 |
+
'@typescript-eslint/visitor-keys': 8.41.0
|
| 1669 |
+
debug: 4.4.1
|
| 1670 |
+
eslint: 9.34.0
|
| 1671 |
+
typescript: 5.8.3
|
| 1672 |
+
transitivePeerDependencies:
|
| 1673 |
+
- supports-color
|
| 1674 |
+
|
| 1675 |
+
'@typescript-eslint/[email protected]([email protected])':
|
| 1676 |
+
dependencies:
|
| 1677 |
+
'@typescript-eslint/tsconfig-utils': 8.41.0([email protected])
|
| 1678 |
+
'@typescript-eslint/types': 8.41.0
|
| 1679 |
+
debug: 4.4.1
|
| 1680 |
+
typescript: 5.8.3
|
| 1681 |
+
transitivePeerDependencies:
|
| 1682 |
+
- supports-color
|
| 1683 |
+
|
| 1684 |
+
'@typescript-eslint/[email protected]':
|
| 1685 |
+
dependencies:
|
| 1686 |
+
'@typescript-eslint/types': 8.41.0
|
| 1687 |
+
'@typescript-eslint/visitor-keys': 8.41.0
|
| 1688 |
+
|
| 1689 |
+
'@typescript-eslint/[email protected]([email protected])':
|
| 1690 |
+
dependencies:
|
| 1691 |
+
typescript: 5.8.3
|
| 1692 |
+
|
| 1693 |
+
'@typescript-eslint/[email protected]([email protected])([email protected])':
|
| 1694 |
+
dependencies:
|
| 1695 |
+
'@typescript-eslint/types': 8.41.0
|
| 1696 |
+
'@typescript-eslint/typescript-estree': 8.41.0([email protected])
|
| 1697 |
+
'@typescript-eslint/utils': 8.41.0([email protected])([email protected])
|
| 1698 |
+
debug: 4.4.1
|
| 1699 |
+
eslint: 9.34.0
|
| 1700 |
+
ts-api-utils: 2.1.0([email protected])
|
| 1701 |
+
typescript: 5.8.3
|
| 1702 |
+
transitivePeerDependencies:
|
| 1703 |
+
- supports-color
|
| 1704 |
+
|
| 1705 |
+
'@typescript-eslint/[email protected]': {}
|
| 1706 |
+
|
| 1707 |
+
'@typescript-eslint/[email protected]([email protected])':
|
| 1708 |
+
dependencies:
|
| 1709 |
+
'@typescript-eslint/project-service': 8.41.0([email protected])
|
| 1710 |
+
'@typescript-eslint/tsconfig-utils': 8.41.0([email protected])
|
| 1711 |
+
'@typescript-eslint/types': 8.41.0
|
| 1712 |
+
'@typescript-eslint/visitor-keys': 8.41.0
|
| 1713 |
+
debug: 4.4.1
|
| 1714 |
+
fast-glob: 3.3.3
|
| 1715 |
+
is-glob: 4.0.3
|
| 1716 |
+
minimatch: 9.0.5
|
| 1717 |
+
semver: 7.7.2
|
| 1718 |
+
ts-api-utils: 2.1.0([email protected])
|
| 1719 |
+
typescript: 5.8.3
|
| 1720 |
+
transitivePeerDependencies:
|
| 1721 |
+
- supports-color
|
| 1722 |
+
|
| 1723 |
+
'@typescript-eslint/[email protected]([email protected])([email protected])':
|
| 1724 |
+
dependencies:
|
| 1725 |
+
'@eslint-community/eslint-utils': 4.7.0([email protected])
|
| 1726 |
+
'@typescript-eslint/scope-manager': 8.41.0
|
| 1727 |
+
'@typescript-eslint/types': 8.41.0
|
| 1728 |
+
'@typescript-eslint/typescript-estree': 8.41.0([email protected])
|
| 1729 |
+
eslint: 9.34.0
|
| 1730 |
+
typescript: 5.8.3
|
| 1731 |
+
transitivePeerDependencies:
|
| 1732 |
+
- supports-color
|
| 1733 |
+
|
| 1734 |
+
'@typescript-eslint/[email protected]':
|
| 1735 |
+
dependencies:
|
| 1736 |
+
'@typescript-eslint/types': 8.41.0
|
| 1737 |
+
eslint-visitor-keys: 4.2.1
|
| 1738 |
+
|
| 1739 |
+
'@vitest/[email protected]':
|
| 1740 |
+
dependencies:
|
| 1741 |
+
'@vitest/spy': 2.1.9
|
| 1742 |
+
'@vitest/utils': 2.1.9
|
| 1743 |
+
chai: 5.2.1
|
| 1744 |
+
tinyrainbow: 1.2.0
|
| 1745 |
+
|
| 1746 |
+
'@vitest/[email protected]([email protected](@types/[email protected]))':
|
| 1747 |
+
dependencies:
|
| 1748 |
+
'@vitest/spy': 2.1.9
|
| 1749 |
+
estree-walker: 3.0.3
|
| 1750 |
+
magic-string: 0.30.17
|
| 1751 |
+
optionalDependencies:
|
| 1752 |
+
vite: 5.4.19(@types/[email protected])
|
| 1753 |
+
|
| 1754 |
+
'@vitest/[email protected]':
|
| 1755 |
+
dependencies:
|
| 1756 |
+
tinyrainbow: 1.2.0
|
| 1757 |
+
|
| 1758 |
+
'@vitest/[email protected]':
|
| 1759 |
+
dependencies:
|
| 1760 |
+
'@vitest/utils': 2.1.9
|
| 1761 |
+
pathe: 1.1.2
|
| 1762 |
+
|
| 1763 |
+
'@vitest/[email protected]':
|
| 1764 |
+
dependencies:
|
| 1765 |
+
'@vitest/pretty-format': 2.1.9
|
| 1766 |
+
magic-string: 0.30.17
|
| 1767 |
+
pathe: 1.1.2
|
| 1768 |
+
|
| 1769 |
+
'@vitest/[email protected]':
|
| 1770 |
+
dependencies:
|
| 1771 |
+
tinyspy: 3.0.2
|
| 1772 |
+
|
| 1773 |
+
'@vitest/[email protected]([email protected])':
|
| 1774 |
+
dependencies:
|
| 1775 |
+
'@vitest/utils': 2.1.9
|
| 1776 |
+
fflate: 0.8.2
|
| 1777 |
+
flatted: 3.3.3
|
| 1778 |
+
pathe: 1.1.2
|
| 1779 |
+
sirv: 3.0.1
|
| 1780 |
+
tinyglobby: 0.2.14
|
| 1781 |
+
tinyrainbow: 1.2.0
|
| 1782 |
+
vitest: 2.1.9(@types/[email protected])(@vitest/[email protected])
|
| 1783 |
+
|
| 1784 |
+
'@vitest/[email protected]':
|
| 1785 |
+
dependencies:
|
| 1786 |
+
'@vitest/pretty-format': 2.1.9
|
| 1787 |
+
loupe: 3.2.0
|
| 1788 |
+
tinyrainbow: 1.2.0
|
| 1789 |
+
|
| 1790 | |
| 1791 |
+
dependencies:
|
| 1792 |
+
acorn: 8.15.0
|
| 1793 |
+
|
| 1794 |
+
[email protected]: {}
|
| 1795 |
+
|
| 1796 | |
| 1797 |
+
dependencies:
|
| 1798 |
+
fast-deep-equal: 3.1.3
|
| 1799 |
+
fast-json-stable-stringify: 2.1.0
|
| 1800 |
+
json-schema-traverse: 0.4.1
|
| 1801 |
+
uri-js: 4.4.1
|
| 1802 |
+
|
| 1803 | |
| 1804 |
+
dependencies:
|
| 1805 |
+
color-convert: 2.0.1
|
| 1806 |
+
|
| 1807 |
+
[email protected]: {}
|
| 1808 |
+
|
| 1809 |
+
[email protected]: {}
|
| 1810 |
+
|
| 1811 |
+
[email protected]: {}
|
| 1812 |
+
|
| 1813 | |
| 1814 |
+
dependencies:
|
| 1815 |
+
balanced-match: 1.0.2
|
| 1816 |
+
concat-map: 0.0.1
|
| 1817 |
+
|
| 1818 | |
| 1819 |
+
dependencies:
|
| 1820 |
+
balanced-match: 1.0.2
|
| 1821 |
+
|
| 1822 | |
| 1823 |
+
dependencies:
|
| 1824 |
+
fill-range: 7.1.1
|
| 1825 |
+
|
| 1826 |
+
[email protected]: {}
|
| 1827 |
+
|
| 1828 |
+
[email protected]: {}
|
| 1829 |
+
|
| 1830 | |
| 1831 |
+
dependencies:
|
| 1832 |
+
assertion-error: 2.0.1
|
| 1833 |
+
check-error: 2.1.1
|
| 1834 |
+
deep-eql: 5.0.2
|
| 1835 |
+
loupe: 3.2.0
|
| 1836 |
+
pathval: 2.0.1
|
| 1837 |
+
|
| 1838 | |
| 1839 |
+
dependencies:
|
| 1840 |
+
ansi-styles: 4.3.0
|
| 1841 |
+
supports-color: 7.2.0
|
| 1842 |
+
|
| 1843 |
+
[email protected]: {}
|
| 1844 |
+
|
| 1845 | |
| 1846 |
+
dependencies:
|
| 1847 |
+
color-name: 1.1.4
|
| 1848 |
+
|
| 1849 |
+
[email protected]: {}
|
| 1850 |
+
|
| 1851 |
+
[email protected]: {}
|
| 1852 |
+
|
| 1853 | |
| 1854 |
+
dependencies:
|
| 1855 |
+
path-key: 3.1.1
|
| 1856 |
+
shebang-command: 2.0.0
|
| 1857 |
+
which: 2.0.2
|
| 1858 |
+
|
| 1859 | |
| 1860 |
+
dependencies:
|
| 1861 |
+
ms: 2.1.2
|
| 1862 |
+
|
| 1863 | |
| 1864 |
+
dependencies:
|
| 1865 |
+
ms: 2.1.3
|
| 1866 |
+
|
| 1867 |
+
[email protected]: {}
|
| 1868 |
+
|
| 1869 |
+
[email protected]: {}
|
| 1870 |
+
|
| 1871 |
+
[email protected]: {}
|
| 1872 |
+
|
| 1873 | |
| 1874 |
+
optionalDependencies:
|
| 1875 |
+
'@esbuild/aix-ppc64': 0.21.5
|
| 1876 |
+
'@esbuild/android-arm': 0.21.5
|
| 1877 |
+
'@esbuild/android-arm64': 0.21.5
|
| 1878 |
+
'@esbuild/android-x64': 0.21.5
|
| 1879 |
+
'@esbuild/darwin-arm64': 0.21.5
|
| 1880 |
+
'@esbuild/darwin-x64': 0.21.5
|
| 1881 |
+
'@esbuild/freebsd-arm64': 0.21.5
|
| 1882 |
+
'@esbuild/freebsd-x64': 0.21.5
|
| 1883 |
+
'@esbuild/linux-arm': 0.21.5
|
| 1884 |
+
'@esbuild/linux-arm64': 0.21.5
|
| 1885 |
+
'@esbuild/linux-ia32': 0.21.5
|
| 1886 |
+
'@esbuild/linux-loong64': 0.21.5
|
| 1887 |
+
'@esbuild/linux-mips64el': 0.21.5
|
| 1888 |
+
'@esbuild/linux-ppc64': 0.21.5
|
| 1889 |
+
'@esbuild/linux-riscv64': 0.21.5
|
| 1890 |
+
'@esbuild/linux-s390x': 0.21.5
|
| 1891 |
+
'@esbuild/linux-x64': 0.21.5
|
| 1892 |
+
'@esbuild/netbsd-x64': 0.21.5
|
| 1893 |
+
'@esbuild/openbsd-x64': 0.21.5
|
| 1894 |
+
'@esbuild/sunos-x64': 0.21.5
|
| 1895 |
+
'@esbuild/win32-arm64': 0.21.5
|
| 1896 |
+
'@esbuild/win32-ia32': 0.21.5
|
| 1897 |
+
'@esbuild/win32-x64': 0.21.5
|
| 1898 |
+
|
| 1899 | |
| 1900 |
+
optionalDependencies:
|
| 1901 |
+
'@esbuild/aix-ppc64': 0.25.8
|
| 1902 |
+
'@esbuild/android-arm': 0.25.8
|
| 1903 |
+
'@esbuild/android-arm64': 0.25.8
|
| 1904 |
+
'@esbuild/android-x64': 0.25.8
|
| 1905 |
+
'@esbuild/darwin-arm64': 0.25.8
|
| 1906 |
+
'@esbuild/darwin-x64': 0.25.8
|
| 1907 |
+
'@esbuild/freebsd-arm64': 0.25.8
|
| 1908 |
+
'@esbuild/freebsd-x64': 0.25.8
|
| 1909 |
+
'@esbuild/linux-arm': 0.25.8
|
| 1910 |
+
'@esbuild/linux-arm64': 0.25.8
|
| 1911 |
+
'@esbuild/linux-ia32': 0.25.8
|
| 1912 |
+
'@esbuild/linux-loong64': 0.25.8
|
| 1913 |
+
'@esbuild/linux-mips64el': 0.25.8
|
| 1914 |
+
'@esbuild/linux-ppc64': 0.25.8
|
| 1915 |
+
'@esbuild/linux-riscv64': 0.25.8
|
| 1916 |
+
'@esbuild/linux-s390x': 0.25.8
|
| 1917 |
+
'@esbuild/linux-x64': 0.25.8
|
| 1918 |
+
'@esbuild/netbsd-arm64': 0.25.8
|
| 1919 |
+
'@esbuild/netbsd-x64': 0.25.8
|
| 1920 |
+
'@esbuild/openbsd-arm64': 0.25.8
|
| 1921 |
+
'@esbuild/openbsd-x64': 0.25.8
|
| 1922 |
+
'@esbuild/openharmony-arm64': 0.25.8
|
| 1923 |
+
'@esbuild/sunos-x64': 0.25.8
|
| 1924 |
+
'@esbuild/win32-arm64': 0.25.8
|
| 1925 |
+
'@esbuild/win32-ia32': 0.25.8
|
| 1926 |
+
'@esbuild/win32-x64': 0.25.8
|
| 1927 |
+
|
| 1928 |
+
[email protected]: {}
|
| 1929 |
+
|
| 1930 | |
| 1931 |
+
dependencies:
|
| 1932 |
+
esrecurse: 4.3.0
|
| 1933 |
+
estraverse: 5.3.0
|
| 1934 |
+
|
| 1935 |
+
[email protected]: {}
|
| 1936 |
+
|
| 1937 |
+
[email protected]: {}
|
| 1938 |
+
|
| 1939 | |
| 1940 |
+
dependencies:
|
| 1941 |
+
'@eslint-community/eslint-utils': 4.7.0([email protected])
|
| 1942 |
+
'@eslint-community/regexpp': 4.12.1
|
| 1943 |
+
'@eslint/config-array': 0.21.0
|
| 1944 |
+
'@eslint/config-helpers': 0.3.1
|
| 1945 |
+
'@eslint/core': 0.15.2
|
| 1946 |
+
'@eslint/eslintrc': 3.3.1
|
| 1947 |
+
'@eslint/js': 9.34.0
|
| 1948 |
+
'@eslint/plugin-kit': 0.3.5
|
| 1949 |
+
'@humanfs/node': 0.16.6
|
| 1950 |
+
'@humanwhocodes/module-importer': 1.0.1
|
| 1951 |
+
'@humanwhocodes/retry': 0.4.3
|
| 1952 |
+
'@types/estree': 1.0.8
|
| 1953 |
+
'@types/json-schema': 7.0.15
|
| 1954 |
+
ajv: 6.12.6
|
| 1955 |
+
chalk: 4.1.2
|
| 1956 |
+
cross-spawn: 7.0.6
|
| 1957 |
+
debug: 4.4.1
|
| 1958 |
+
escape-string-regexp: 4.0.0
|
| 1959 |
+
eslint-scope: 8.4.0
|
| 1960 |
+
eslint-visitor-keys: 4.2.1
|
| 1961 |
+
espree: 10.4.0
|
| 1962 |
+
esquery: 1.6.0
|
| 1963 |
+
esutils: 2.0.3
|
| 1964 |
+
fast-deep-equal: 3.1.3
|
| 1965 |
+
file-entry-cache: 8.0.0
|
| 1966 |
+
find-up: 5.0.0
|
| 1967 |
+
glob-parent: 6.0.2
|
| 1968 |
+
ignore: 5.3.2
|
| 1969 |
+
imurmurhash: 0.1.4
|
| 1970 |
+
is-glob: 4.0.3
|
| 1971 |
+
json-stable-stringify-without-jsonify: 1.0.1
|
| 1972 |
+
lodash.merge: 4.6.2
|
| 1973 |
+
minimatch: 3.1.2
|
| 1974 |
+
natural-compare: 1.4.0
|
| 1975 |
+
optionator: 0.9.4
|
| 1976 |
+
transitivePeerDependencies:
|
| 1977 |
+
- supports-color
|
| 1978 |
+
|
| 1979 | |
| 1980 |
+
dependencies:
|
| 1981 |
+
acorn: 8.15.0
|
| 1982 |
+
acorn-jsx: 5.3.2([email protected])
|
| 1983 |
+
eslint-visitor-keys: 4.2.1
|
| 1984 |
+
|
| 1985 | |
| 1986 |
+
dependencies:
|
| 1987 |
+
estraverse: 5.3.0
|
| 1988 |
+
|
| 1989 | |
| 1990 |
+
dependencies:
|
| 1991 |
+
estraverse: 5.3.0
|
| 1992 |
+
|
| 1993 |
+
[email protected]: {}
|
| 1994 |
+
|
| 1995 | |
| 1996 |
+
dependencies:
|
| 1997 |
+
'@types/estree': 1.0.8
|
| 1998 |
+
|
| 1999 |
+
[email protected]: {}
|
| 2000 |
+
|
| 2001 |
+
[email protected]: {}
|
| 2002 |
+
|
| 2003 |
+
[email protected]: {}
|
| 2004 |
+
|
| 2005 | |
| 2006 |
+
dependencies:
|
| 2007 |
+
'@nodelib/fs.stat': 2.0.5
|
| 2008 |
+
'@nodelib/fs.walk': 1.2.8
|
| 2009 |
+
glob-parent: 5.1.2
|
| 2010 |
+
merge2: 1.4.1
|
| 2011 |
+
micromatch: 4.0.8
|
| 2012 |
+
|
| 2013 |
+
[email protected]: {}
|
| 2014 |
+
|
| 2015 |
+
[email protected]: {}
|
| 2016 |
+
|
| 2017 | |
| 2018 |
+
dependencies:
|
| 2019 |
+
reusify: 1.1.0
|
| 2020 |
+
|
| 2021 | |
| 2022 |
+
optionalDependencies:
|
| 2023 |
+
picomatch: 4.0.3
|
| 2024 |
+
|
| 2025 |
+
[email protected]: {}
|
| 2026 |
+
|
| 2027 | |
| 2028 |
+
dependencies:
|
| 2029 |
+
flat-cache: 4.0.1
|
| 2030 |
+
|
| 2031 | |
| 2032 |
+
dependencies:
|
| 2033 |
+
to-regex-range: 5.0.1
|
| 2034 |
+
|
| 2035 | |
| 2036 |
+
dependencies:
|
| 2037 |
+
locate-path: 6.0.0
|
| 2038 |
+
path-exists: 4.0.0
|
| 2039 |
+
|
| 2040 | |
| 2041 |
+
dependencies:
|
| 2042 |
+
flatted: 3.3.3
|
| 2043 |
+
keyv: 4.5.4
|
| 2044 |
+
|
| 2045 |
+
[email protected]: {}
|
| 2046 |
+
|
| 2047 | |
| 2048 |
+
optional: true
|
| 2049 |
+
|
| 2050 | |
| 2051 |
+
dependencies:
|
| 2052 |
+
is-glob: 4.0.3
|
| 2053 |
+
|
| 2054 | |
| 2055 |
+
dependencies:
|
| 2056 |
+
is-glob: 4.0.3
|
| 2057 |
+
|
| 2058 |
+
[email protected]: {}
|
| 2059 |
+
|
| 2060 |
+
[email protected]: {}
|
| 2061 |
+
|
| 2062 |
+
[email protected]: {}
|
| 2063 |
+
|
| 2064 |
+
[email protected]: {}
|
| 2065 |
+
|
| 2066 |
+
[email protected]: {}
|
| 2067 |
+
|
| 2068 | |
| 2069 |
+
dependencies:
|
| 2070 |
+
parent-module: 1.0.1
|
| 2071 |
+
resolve-from: 4.0.0
|
| 2072 |
+
|
| 2073 |
+
[email protected]: {}
|
| 2074 |
+
|
| 2075 |
+
[email protected]: {}
|
| 2076 |
+
|
| 2077 | |
| 2078 |
+
dependencies:
|
| 2079 |
+
is-extglob: 2.1.1
|
| 2080 |
+
|
| 2081 |
+
[email protected]: {}
|
| 2082 |
+
|
| 2083 |
+
[email protected]: {}
|
| 2084 |
+
|
| 2085 | |
| 2086 |
+
dependencies:
|
| 2087 |
+
argparse: 2.0.1
|
| 2088 |
+
|
| 2089 |
+
[email protected]: {}
|
| 2090 |
+
|
| 2091 |
+
[email protected]: {}
|
| 2092 |
+
|
| 2093 |
+
[email protected]: {}
|
| 2094 |
+
|
| 2095 | |
| 2096 |
+
dependencies:
|
| 2097 |
+
json-buffer: 3.0.1
|
| 2098 |
+
|
| 2099 | |
| 2100 |
+
dependencies:
|
| 2101 |
+
prelude-ls: 1.2.1
|
| 2102 |
+
type-check: 0.4.0
|
| 2103 |
+
|
| 2104 | |
| 2105 |
+
dependencies:
|
| 2106 |
+
p-locate: 5.0.0
|
| 2107 |
+
|
| 2108 |
+
[email protected]: {}
|
| 2109 |
+
|
| 2110 |
+
[email protected]: {}
|
| 2111 |
+
|
| 2112 | |
| 2113 |
+
dependencies:
|
| 2114 |
+
'@jridgewell/sourcemap-codec': 1.5.4
|
| 2115 |
+
|
| 2116 |
+
[email protected]: {}
|
| 2117 |
+
|
| 2118 | |
| 2119 |
+
dependencies:
|
| 2120 |
+
braces: 3.0.3
|
| 2121 |
+
picomatch: 2.3.1
|
| 2122 |
+
|
| 2123 | |
| 2124 |
+
dependencies:
|
| 2125 |
+
brace-expansion: 1.1.12
|
| 2126 |
+
|
| 2127 | |
| 2128 |
+
dependencies:
|
| 2129 |
+
brace-expansion: 2.0.2
|
| 2130 |
+
|
| 2131 |
+
[email protected]: {}
|
| 2132 |
+
|
| 2133 |
+
[email protected]: {}
|
| 2134 |
+
|
| 2135 |
+
[email protected]: {}
|
| 2136 |
+
|
| 2137 |
+
[email protected]: {}
|
| 2138 |
+
|
| 2139 |
+
[email protected]: {}
|
| 2140 |
+
|
| 2141 |
+
[email protected]: {}
|
| 2142 |
+
|
| 2143 |
+
[email protected]: {}
|
| 2144 |
+
|
| 2145 | |
| 2146 |
+
dependencies:
|
| 2147 |
+
deep-is: 0.1.4
|
| 2148 |
+
fast-levenshtein: 2.0.6
|
| 2149 |
+
levn: 0.4.1
|
| 2150 |
+
prelude-ls: 1.2.1
|
| 2151 |
+
type-check: 0.4.0
|
| 2152 |
+
word-wrap: 1.2.5
|
| 2153 |
+
|
| 2154 | |
| 2155 |
+
dependencies:
|
| 2156 |
+
yocto-queue: 0.1.0
|
| 2157 |
+
|
| 2158 | |
| 2159 |
+
dependencies:
|
| 2160 |
+
p-limit: 3.1.0
|
| 2161 |
+
|
| 2162 | |
| 2163 |
+
dependencies:
|
| 2164 |
+
callsites: 3.1.0
|
| 2165 |
+
|
| 2166 |
+
[email protected]: {}
|
| 2167 |
+
|
| 2168 |
+
[email protected]: {}
|
| 2169 |
+
|
| 2170 |
+
[email protected]: {}
|
| 2171 |
+
|
| 2172 |
+
[email protected]: {}
|
| 2173 |
+
|
| 2174 |
+
[email protected]: {}
|
| 2175 |
+
|
| 2176 |
+
[email protected]: {}
|
| 2177 |
+
|
| 2178 |
+
[email protected]: {}
|
| 2179 |
+
|
| 2180 | |
| 2181 |
+
dependencies:
|
| 2182 |
+
nanoid: 3.3.11
|
| 2183 |
+
picocolors: 1.1.1
|
| 2184 |
+
source-map-js: 1.2.1
|
| 2185 |
+
|
| 2186 |
+
[email protected]: {}
|
| 2187 |
+
|
| 2188 |
+
[email protected]: {}
|
| 2189 |
+
|
| 2190 |
+
[email protected]: {}
|
| 2191 |
+
|
| 2192 |
+
[email protected]: {}
|
| 2193 |
+
|
| 2194 |
+
[email protected]: {}
|
| 2195 |
+
|
| 2196 | |
| 2197 |
+
dependencies:
|
| 2198 |
+
'@types/estree': 1.0.8
|
| 2199 |
+
optionalDependencies:
|
| 2200 |
+
'@rollup/rollup-android-arm-eabi': 4.46.0
|
| 2201 |
+
'@rollup/rollup-android-arm64': 4.46.0
|
| 2202 |
+
'@rollup/rollup-darwin-arm64': 4.46.0
|
| 2203 |
+
'@rollup/rollup-darwin-x64': 4.46.0
|
| 2204 |
+
'@rollup/rollup-freebsd-arm64': 4.46.0
|
| 2205 |
+
'@rollup/rollup-freebsd-x64': 4.46.0
|
| 2206 |
+
'@rollup/rollup-linux-arm-gnueabihf': 4.46.0
|
| 2207 |
+
'@rollup/rollup-linux-arm-musleabihf': 4.46.0
|
| 2208 |
+
'@rollup/rollup-linux-arm64-gnu': 4.46.0
|
| 2209 |
+
'@rollup/rollup-linux-arm64-musl': 4.46.0
|
| 2210 |
+
'@rollup/rollup-linux-loongarch64-gnu': 4.46.0
|
| 2211 |
+
'@rollup/rollup-linux-ppc64-gnu': 4.46.0
|
| 2212 |
+
'@rollup/rollup-linux-riscv64-gnu': 4.46.0
|
| 2213 |
+
'@rollup/rollup-linux-riscv64-musl': 4.46.0
|
| 2214 |
+
'@rollup/rollup-linux-s390x-gnu': 4.46.0
|
| 2215 |
+
'@rollup/rollup-linux-x64-gnu': 4.46.0
|
| 2216 |
+
'@rollup/rollup-linux-x64-musl': 4.46.0
|
| 2217 |
+
'@rollup/rollup-win32-arm64-msvc': 4.46.0
|
| 2218 |
+
'@rollup/rollup-win32-ia32-msvc': 4.46.0
|
| 2219 |
+
'@rollup/rollup-win32-x64-msvc': 4.46.0
|
| 2220 |
+
fsevents: 2.3.3
|
| 2221 |
+
|
| 2222 | |
| 2223 |
+
dependencies:
|
| 2224 |
+
queue-microtask: 1.2.3
|
| 2225 |
+
|
| 2226 |
+
[email protected]: {}
|
| 2227 |
+
|
| 2228 | |
| 2229 |
+
dependencies:
|
| 2230 |
+
'@serialport/binding-mock': 10.2.2
|
| 2231 |
+
'@serialport/bindings-cpp': 12.0.1
|
| 2232 |
+
'@serialport/parser-byte-length': 12.0.0
|
| 2233 |
+
'@serialport/parser-cctalk': 12.0.0
|
| 2234 |
+
'@serialport/parser-delimiter': 12.0.0
|
| 2235 |
+
'@serialport/parser-inter-byte-timeout': 12.0.0
|
| 2236 |
+
'@serialport/parser-packet-length': 12.0.0
|
| 2237 |
+
'@serialport/parser-readline': 12.0.0
|
| 2238 |
+
'@serialport/parser-ready': 12.0.0
|
| 2239 |
+
'@serialport/parser-regex': 12.0.0
|
| 2240 |
+
'@serialport/parser-slip-encoder': 12.0.0
|
| 2241 |
+
'@serialport/parser-spacepacket': 12.0.0
|
| 2242 |
+
'@serialport/stream': 12.0.0
|
| 2243 |
+
debug: 4.3.4
|
| 2244 |
+
transitivePeerDependencies:
|
| 2245 |
+
- supports-color
|
| 2246 |
+
|
| 2247 | |
| 2248 |
+
dependencies:
|
| 2249 |
+
shebang-regex: 3.0.0
|
| 2250 |
+
|
| 2251 |
+
[email protected]: {}
|
| 2252 |
+
|
| 2253 |
+
[email protected]: {}
|
| 2254 |
+
|
| 2255 | |
| 2256 |
+
dependencies:
|
| 2257 |
+
'@polka/url': 1.0.0-next.29
|
| 2258 |
+
mrmime: 2.0.1
|
| 2259 |
+
totalist: 3.0.1
|
| 2260 |
+
|
| 2261 |
+
[email protected]: {}
|
| 2262 |
+
|
| 2263 |
+
[email protected]: {}
|
| 2264 |
+
|
| 2265 |
+
[email protected]: {}
|
| 2266 |
+
|
| 2267 |
+
[email protected]: {}
|
| 2268 |
+
|
| 2269 | |
| 2270 |
+
dependencies:
|
| 2271 |
+
has-flag: 4.0.0
|
| 2272 |
+
|
| 2273 |
+
[email protected]: {}
|
| 2274 |
+
|
| 2275 |
+
[email protected]: {}
|
| 2276 |
+
|
| 2277 | |
| 2278 |
+
dependencies:
|
| 2279 |
+
fdir: 6.4.6([email protected])
|
| 2280 |
+
picomatch: 4.0.3
|
| 2281 |
+
|
| 2282 |
+
[email protected]: {}
|
| 2283 |
+
|
| 2284 |
+
[email protected]: {}
|
| 2285 |
+
|
| 2286 |
+
[email protected]: {}
|
| 2287 |
+
|
| 2288 | |
| 2289 |
+
dependencies:
|
| 2290 |
+
is-number: 7.0.0
|
| 2291 |
+
|
| 2292 |
+
[email protected]: {}
|
| 2293 |
+
|
| 2294 | |
| 2295 |
+
dependencies:
|
| 2296 |
+
typescript: 5.8.3
|
| 2297 |
+
|
| 2298 | |
| 2299 |
+
dependencies:
|
| 2300 |
+
prelude-ls: 1.2.1
|
| 2301 |
+
|
| 2302 |
+
[email protected]: {}
|
| 2303 |
+
|
| 2304 |
+
[email protected]: {}
|
| 2305 |
+
|
| 2306 | |
| 2307 |
+
dependencies:
|
| 2308 |
+
punycode: 2.3.1
|
| 2309 |
+
|
| 2310 |
+
[email protected](@types/[email protected]):
|
| 2311 |
+
dependencies:
|
| 2312 |
+
cac: 6.7.14
|
| 2313 |
+
debug: 4.4.1
|
| 2314 |
+
es-module-lexer: 1.7.0
|
| 2315 |
+
pathe: 1.1.2
|
| 2316 |
+
vite: 5.4.19(@types/[email protected])
|
| 2317 |
+
transitivePeerDependencies:
|
| 2318 |
+
- '@types/node'
|
| 2319 |
+
- less
|
| 2320 |
+
- lightningcss
|
| 2321 |
+
- sass
|
| 2322 |
+
- sass-embedded
|
| 2323 |
+
- stylus
|
| 2324 |
+
- sugarss
|
| 2325 |
+
- supports-color
|
| 2326 |
+
- terser
|
| 2327 |
+
|
| 2328 |
+
[email protected](@types/[email protected]):
|
| 2329 |
+
dependencies:
|
| 2330 |
+
esbuild: 0.21.5
|
| 2331 |
+
postcss: 8.5.6
|
| 2332 |
+
rollup: 4.46.0
|
| 2333 |
+
optionalDependencies:
|
| 2334 |
+
'@types/node': 18.19.123
|
| 2335 |
+
fsevents: 2.3.3
|
| 2336 |
+
|
| 2337 |
+
[email protected](@types/[email protected]):
|
| 2338 |
+
dependencies:
|
| 2339 |
+
esbuild: 0.25.8
|
| 2340 |
+
fdir: 6.4.6([email protected])
|
| 2341 |
+
picomatch: 4.0.3
|
| 2342 |
+
postcss: 8.5.6
|
| 2343 |
+
rollup: 4.46.0
|
| 2344 |
+
tinyglobby: 0.2.14
|
| 2345 |
+
optionalDependencies:
|
| 2346 |
+
'@types/node': 18.19.123
|
| 2347 |
+
fsevents: 2.3.3
|
| 2348 |
+
|
| 2349 |
+
[email protected](@types/[email protected])(@vitest/[email protected]):
|
| 2350 |
+
dependencies:
|
| 2351 |
+
'@vitest/expect': 2.1.9
|
| 2352 |
+
'@vitest/mocker': 2.1.9([email protected](@types/[email protected]))
|
| 2353 |
+
'@vitest/pretty-format': 2.1.9
|
| 2354 |
+
'@vitest/runner': 2.1.9
|
| 2355 |
+
'@vitest/snapshot': 2.1.9
|
| 2356 |
+
'@vitest/spy': 2.1.9
|
| 2357 |
+
'@vitest/utils': 2.1.9
|
| 2358 |
+
chai: 5.2.1
|
| 2359 |
+
debug: 4.4.1
|
| 2360 |
+
expect-type: 1.2.2
|
| 2361 |
+
magic-string: 0.30.17
|
| 2362 |
+
pathe: 1.1.2
|
| 2363 |
+
std-env: 3.9.0
|
| 2364 |
+
tinybench: 2.9.0
|
| 2365 |
+
tinyexec: 0.3.2
|
| 2366 |
+
tinypool: 1.1.1
|
| 2367 |
+
tinyrainbow: 1.2.0
|
| 2368 |
+
vite: 5.4.19(@types/[email protected])
|
| 2369 |
+
vite-node: 2.1.9(@types/[email protected])
|
| 2370 |
+
why-is-node-running: 2.3.0
|
| 2371 |
+
optionalDependencies:
|
| 2372 |
+
'@types/node': 18.19.123
|
| 2373 |
+
'@vitest/ui': 2.1.9([email protected])
|
| 2374 |
+
transitivePeerDependencies:
|
| 2375 |
+
- less
|
| 2376 |
+
- lightningcss
|
| 2377 |
+
- msw
|
| 2378 |
+
- sass
|
| 2379 |
+
- sass-embedded
|
| 2380 |
+
- stylus
|
| 2381 |
+
- sugarss
|
| 2382 |
+
- supports-color
|
| 2383 |
+
- terser
|
| 2384 |
+
|
| 2385 | |
| 2386 |
+
dependencies:
|
| 2387 |
+
isexe: 2.0.0
|
| 2388 |
+
|
| 2389 | |
| 2390 |
+
dependencies:
|
| 2391 |
+
siginfo: 2.0.0
|
| 2392 |
+
stackback: 0.0.2
|
| 2393 |
+
|
| 2394 |
+
[email protected]: {}
|
| 2395 |
+
|
| 2396 |
+
[email protected]: {}
|
packages/node/src/calibrate.ts
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Node.js calibration functionality using serialport API
|
| 3 |
+
* Provides both Python lerobot compatible CLI behavior and programmatic usage
|
| 4 |
+
* Uses proven calibration algorithms with web-compatible API
|
| 5 |
+
*/
|
| 6 |
+
|
| 7 |
+
import { NodeSerialPortWrapper } from "./utils/serial-port-wrapper.js";
|
| 8 |
+
import { createSO100Config } from "./robots/so100_config.js";
|
| 9 |
+
import {
|
| 10 |
+
readAllMotorPositions,
|
| 11 |
+
releaseMotors as releaseMotorsLowLevel,
|
| 12 |
+
type MotorCommunicationPort,
|
| 13 |
+
} from "./utils/motor-communication.js";
|
| 14 |
+
import {
|
| 15 |
+
setHomingOffsets,
|
| 16 |
+
writeHardwarePositionLimits,
|
| 17 |
+
} from "./utils/motor-calibration.js";
|
| 18 |
+
import { createInterface } from "readline";
|
| 19 |
+
import { writeFile } from "fs/promises";
|
| 20 |
+
import { join } from "path";
|
| 21 |
+
import { homedir } from "os";
|
| 22 |
+
|
| 23 |
+
// Debug logging removed - calibration working perfectly
|
| 24 |
+
import type {
|
| 25 |
+
CalibrateConfig,
|
| 26 |
+
CalibrationResults,
|
| 27 |
+
LiveCalibrationData,
|
| 28 |
+
CalibrationProcess,
|
| 29 |
+
} from "./types/calibration.js";
|
| 30 |
+
import type { RobotConnection } from "./types/robot-connection.js";
|
| 31 |
+
|
| 32 |
+
/**
|
| 33 |
+
* Get calibration file path (matches Python lerobot location)
|
| 34 |
+
*/
|
| 35 |
+
function getCalibrationFilePath(robotType: string, robotId: string): string {
|
| 36 |
+
const HF_HOME =
|
| 37 |
+
process.env.HF_HOME || join(homedir(), ".cache", "huggingface");
|
| 38 |
+
const calibrationDir = join(
|
| 39 |
+
HF_HOME,
|
| 40 |
+
"lerobot",
|
| 41 |
+
"calibration",
|
| 42 |
+
"robots",
|
| 43 |
+
robotType
|
| 44 |
+
);
|
| 45 |
+
return join(calibrationDir, `${robotId}.json`);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
/**
|
| 49 |
+
* Create readline interface for user input
|
| 50 |
+
*/
|
| 51 |
+
function createReadlineInterface() {
|
| 52 |
+
return createInterface({
|
| 53 |
+
input: process.stdin,
|
| 54 |
+
output: process.stdout,
|
| 55 |
+
});
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
/**
|
| 59 |
+
* Wait for user input with a prompt
|
| 60 |
+
*/
|
| 61 |
+
function waitForInput(rl: any, prompt: string): Promise<string> {
|
| 62 |
+
return new Promise((resolve) => {
|
| 63 |
+
rl.question(prompt, (answer: string) => {
|
| 64 |
+
resolve(answer);
|
| 65 |
+
});
|
| 66 |
+
});
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
/**
|
| 70 |
+
* Record ranges of motion with live updates
|
| 71 |
+
*/
|
| 72 |
+
async function recordRangesOfMotion(
|
| 73 |
+
port: MotorCommunicationPort,
|
| 74 |
+
motorIds: number[],
|
| 75 |
+
motorNames: string[],
|
| 76 |
+
shouldStop: () => boolean,
|
| 77 |
+
onLiveUpdate?: (data: LiveCalibrationData) => void,
|
| 78 |
+
onProgress?: (message: string) => void
|
| 79 |
+
): Promise<{
|
| 80 |
+
rangeMins: { [motor: string]: number };
|
| 81 |
+
rangeMaxes: { [motor: string]: number };
|
| 82 |
+
}> {
|
| 83 |
+
const rangeMins: { [motor: string]: number } = {};
|
| 84 |
+
const rangeMaxes: { [motor: string]: number } = {};
|
| 85 |
+
|
| 86 |
+
// Read actual current positions (now centered due to applied homing offsets)
|
| 87 |
+
const startPositions = await readAllMotorPositions(port, motorIds);
|
| 88 |
+
|
| 89 |
+
for (let i = 0; i < motorNames.length; i++) {
|
| 90 |
+
const motorName = motorNames[i];
|
| 91 |
+
rangeMins[motorName] = startPositions[i];
|
| 92 |
+
rangeMaxes[motorName] = startPositions[i];
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
if (onProgress) {
|
| 96 |
+
onProgress(
|
| 97 |
+
"Move each motor through its full range of motion. The ranges will be recorded automatically."
|
| 98 |
+
);
|
| 99 |
+
onProgress(
|
| 100 |
+
"Press Enter when you have finished moving all motors through their ranges."
|
| 101 |
+
);
|
| 102 |
+
} else {
|
| 103 |
+
console.log(
|
| 104 |
+
"Move each motor through its full range of motion. The ranges will be recorded automatically."
|
| 105 |
+
);
|
| 106 |
+
console.log(
|
| 107 |
+
"Press Enter when you have finished moving all motors through their ranges."
|
| 108 |
+
);
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
// Set up readline for user input
|
| 112 |
+
const rl = createReadlineInterface();
|
| 113 |
+
let isRecording = true;
|
| 114 |
+
|
| 115 |
+
// Start recording in background
|
| 116 |
+
const recordingInterval = setInterval(async () => {
|
| 117 |
+
if (!isRecording) return;
|
| 118 |
+
|
| 119 |
+
try {
|
| 120 |
+
const currentPositions = await readAllMotorPositions(port, motorIds);
|
| 121 |
+
const liveData: LiveCalibrationData = {};
|
| 122 |
+
|
| 123 |
+
for (let i = 0; i < motorNames.length; i++) {
|
| 124 |
+
const motorName = motorNames[i];
|
| 125 |
+
const position = currentPositions[i];
|
| 126 |
+
|
| 127 |
+
// Update ranges
|
| 128 |
+
rangeMins[motorName] = Math.min(rangeMins[motorName], position);
|
| 129 |
+
rangeMaxes[motorName] = Math.max(rangeMaxes[motorName], position);
|
| 130 |
+
|
| 131 |
+
// Build live data
|
| 132 |
+
liveData[motorName] = {
|
| 133 |
+
current: position,
|
| 134 |
+
min: rangeMins[motorName],
|
| 135 |
+
max: rangeMaxes[motorName],
|
| 136 |
+
range: rangeMaxes[motorName] - rangeMins[motorName],
|
| 137 |
+
};
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
if (onLiveUpdate) {
|
| 141 |
+
onLiveUpdate(liveData);
|
| 142 |
+
}
|
| 143 |
+
} catch (error) {
|
| 144 |
+
// Silent - continue recording
|
| 145 |
+
}
|
| 146 |
+
}, 100); // Update every 100ms
|
| 147 |
+
|
| 148 |
+
// Wait for user to finish
|
| 149 |
+
try {
|
| 150 |
+
await waitForInput(rl, "");
|
| 151 |
+
// IMMEDIATELY stop recording and live updates
|
| 152 |
+
isRecording = false;
|
| 153 |
+
clearInterval(recordingInterval);
|
| 154 |
+
} finally {
|
| 155 |
+
// Ensure cleanup even if there's an error
|
| 156 |
+
isRecording = false;
|
| 157 |
+
clearInterval(recordingInterval);
|
| 158 |
+
rl.close();
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
return { rangeMins, rangeMaxes };
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
/**
|
| 165 |
+
* Main calibrate function with web-compatible API
|
| 166 |
+
*/
|
| 167 |
+
export async function calibrate(
|
| 168 |
+
config: CalibrateConfig
|
| 169 |
+
): Promise<CalibrationProcess> {
|
| 170 |
+
const { robot, onLiveUpdate, onProgress, outputPath } = config;
|
| 171 |
+
|
| 172 |
+
// Validate robot configuration
|
| 173 |
+
if (!robot.robotType) {
|
| 174 |
+
throw new Error(
|
| 175 |
+
"Robot type is required for calibration. Please configure the robot first."
|
| 176 |
+
);
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
if (!robot.isConnected || !robot.port) {
|
| 180 |
+
throw new Error(
|
| 181 |
+
"Robot is not connected. Please use findPort() to connect first."
|
| 182 |
+
);
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
let shouldStop = false;
|
| 186 |
+
let port: NodeSerialPortWrapper | null = null;
|
| 187 |
+
|
| 188 |
+
const calibrationPromise = (async (): Promise<CalibrationResults> => {
|
| 189 |
+
try {
|
| 190 |
+
// Use the EXISTING port connection (don't create new one!)
|
| 191 |
+
port = robot.port;
|
| 192 |
+
|
| 193 |
+
// Get robot-specific configuration
|
| 194 |
+
let robotConfig;
|
| 195 |
+
if (robot.robotType.startsWith("so100")) {
|
| 196 |
+
robotConfig = createSO100Config(robot.robotType);
|
| 197 |
+
} else {
|
| 198 |
+
throw new Error(`Unsupported robot type: ${robot.robotType}`);
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
const { motorIds, motorNames, driveModes } = robotConfig;
|
| 202 |
+
|
| 203 |
+
// Debug logging removed - calibration working perfectly
|
| 204 |
+
|
| 205 |
+
// Starting calibration silently
|
| 206 |
+
|
| 207 |
+
// Step 1: Set homing offsets (motors should already be released and positioned)
|
| 208 |
+
// Note: Motors should be released BEFORE calling calibrate(), not inside it
|
| 209 |
+
// Setting homing offsets silently
|
| 210 |
+
const homingOffsets = await setHomingOffsets(port, motorIds, motorNames);
|
| 211 |
+
|
| 212 |
+
// Early debug test removed - calibration working perfectly
|
| 213 |
+
|
| 214 |
+
if (shouldStop) throw new Error("Calibration stopped by user");
|
| 215 |
+
|
| 216 |
+
// Step 2: Record ranges of motion silently
|
| 217 |
+
const { rangeMins, rangeMaxes } = await recordRangesOfMotion(
|
| 218 |
+
port,
|
| 219 |
+
motorIds,
|
| 220 |
+
motorNames,
|
| 221 |
+
() => shouldStop,
|
| 222 |
+
onLiveUpdate,
|
| 223 |
+
onProgress
|
| 224 |
+
);
|
| 225 |
+
|
| 226 |
+
if (shouldStop) throw new Error("Calibration stopped by user");
|
| 227 |
+
|
| 228 |
+
// Step 3: Write hardware position limits silently
|
| 229 |
+
await writeHardwarePositionLimits(
|
| 230 |
+
port,
|
| 231 |
+
motorIds,
|
| 232 |
+
motorNames,
|
| 233 |
+
rangeMins,
|
| 234 |
+
rangeMaxes
|
| 235 |
+
);
|
| 236 |
+
|
| 237 |
+
// Step 4: Skip motor locking (Python lerobot doesn't lock motors after calibration)
|
| 238 |
+
|
| 239 |
+
// Build calibration results (Python lerobot compatible format)
|
| 240 |
+
|
| 241 |
+
const calibrationResults: CalibrationResults = {};
|
| 242 |
+
for (let i = 0; i < motorNames.length; i++) {
|
| 243 |
+
const motorName = motorNames[i];
|
| 244 |
+
const homingOffsetValue = homingOffsets[motorName];
|
| 245 |
+
|
| 246 |
+
calibrationResults[motorName] = {
|
| 247 |
+
id: motorIds[i],
|
| 248 |
+
drive_mode: driveModes[i],
|
| 249 |
+
homing_offset: homingOffsetValue,
|
| 250 |
+
range_min: rangeMins[motorName],
|
| 251 |
+
range_max: rangeMaxes[motorName],
|
| 252 |
+
};
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
// Save calibration file
|
| 256 |
+
const calibrationPath =
|
| 257 |
+
outputPath ||
|
| 258 |
+
getCalibrationFilePath(robot.robotType, robot.robotId || "default");
|
| 259 |
+
|
| 260 |
+
// Ensure directory exists
|
| 261 |
+
const { mkdir } = await import("fs/promises");
|
| 262 |
+
const { dirname } = await import("path");
|
| 263 |
+
await mkdir(dirname(calibrationPath), { recursive: true });
|
| 264 |
+
|
| 265 |
+
await writeFile(
|
| 266 |
+
calibrationPath,
|
| 267 |
+
JSON.stringify(calibrationResults, null, 2)
|
| 268 |
+
);
|
| 269 |
+
|
| 270 |
+
if (onProgress) {
|
| 271 |
+
onProgress(`Calibration complete! Saved to: ${calibrationPath}`);
|
| 272 |
+
} else {
|
| 273 |
+
console.log(`Calibration complete! Saved to: ${calibrationPath}`);
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
return calibrationResults;
|
| 277 |
+
} finally {
|
| 278 |
+
// Note: Don't close the port - it belongs to the robot connection
|
| 279 |
+
}
|
| 280 |
+
})();
|
| 281 |
+
|
| 282 |
+
return {
|
| 283 |
+
stop(): void {
|
| 284 |
+
shouldStop = true;
|
| 285 |
+
},
|
| 286 |
+
result: calibrationPromise,
|
| 287 |
+
};
|
| 288 |
+
}
|
packages/node/src/find_port.ts
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Node.js port discovery using serialport API
|
| 3 |
+
* Provides programmatic port discovery compatible with @lerobot/web API
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import { SerialPort } from "serialport";
|
| 7 |
+
import { platform } from "os";
|
| 8 |
+
import { readdir } from "fs/promises";
|
| 9 |
+
import { join } from "path";
|
| 10 |
+
import { NodeSerialPortWrapper } from "./utils/serial-port-wrapper.js";
|
| 11 |
+
import type {
|
| 12 |
+
FindPortConfig,
|
| 13 |
+
FindPortProcess,
|
| 14 |
+
DiscoveredPort,
|
| 15 |
+
RobotConnection,
|
| 16 |
+
} from "./types/port-discovery.js";
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* Find available serial ports on the system
|
| 20 |
+
* Mirrors Python's find_available_ports() function
|
| 21 |
+
* Exported for CLI usage
|
| 22 |
+
*/
|
| 23 |
+
export async function findAvailablePorts(): Promise<string[]> {
|
| 24 |
+
if (platform() === "win32") {
|
| 25 |
+
// List COM ports using serialport library (equivalent to pyserial)
|
| 26 |
+
const ports = await SerialPort.list();
|
| 27 |
+
return ports.map((port) => port.path);
|
| 28 |
+
} else {
|
| 29 |
+
// List /dev/tty* ports for Unix-based systems (Linux/macOS)
|
| 30 |
+
try {
|
| 31 |
+
const devFiles = await readdir("/dev");
|
| 32 |
+
const ttyPorts = devFiles
|
| 33 |
+
.filter((file) => file.startsWith("tty"))
|
| 34 |
+
.map((file) => join("/dev", file));
|
| 35 |
+
return ttyPorts;
|
| 36 |
+
} catch (error) {
|
| 37 |
+
// Fallback to serialport library if /dev reading fails
|
| 38 |
+
const ports = await SerialPort.list();
|
| 39 |
+
return ports.map((port) => port.path);
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
/**
|
| 45 |
+
* Connect directly to a robot port (Python lerobot compatible)
|
| 46 |
+
* Equivalent to robot.connect() in Python lerobot
|
| 47 |
+
*/
|
| 48 |
+
export async function connectPort(
|
| 49 |
+
portPath: string,
|
| 50 |
+
robotType: "so100_follower" | "so100_leader" = "so100_follower",
|
| 51 |
+
robotId: string = "robot"
|
| 52 |
+
): Promise<RobotConnection> {
|
| 53 |
+
// Test connection
|
| 54 |
+
const port = new NodeSerialPortWrapper(portPath);
|
| 55 |
+
let isConnected = false;
|
| 56 |
+
|
| 57 |
+
try {
|
| 58 |
+
await port.initialize();
|
| 59 |
+
isConnected = true;
|
| 60 |
+
await port.close();
|
| 61 |
+
} catch (error) {
|
| 62 |
+
// Connection failed
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
// Return the ACTUAL working port, properly initialized!
|
| 66 |
+
const workingPort = new NodeSerialPortWrapper(portPath);
|
| 67 |
+
|
| 68 |
+
// Initialize the working port if connection test succeeded
|
| 69 |
+
if (isConnected) {
|
| 70 |
+
try {
|
| 71 |
+
await workingPort.initialize();
|
| 72 |
+
} catch (error) {
|
| 73 |
+
isConnected = false;
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
return {
|
| 78 |
+
port: workingPort, // ← Return the initialized working port!
|
| 79 |
+
name: `Robot on ${portPath}`,
|
| 80 |
+
robotType,
|
| 81 |
+
robotId,
|
| 82 |
+
isConnected,
|
| 83 |
+
serialNumber: portPath, // Use port path as serial number for Node.js
|
| 84 |
+
error: isConnected ? undefined : "Connection failed",
|
| 85 |
+
};
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
/**
|
| 89 |
+
* Interactive mode: Return discovered robot ports (Node.js style)
|
| 90 |
+
* Unlike web version, this only discovers - user must call connectPort() separately
|
| 91 |
+
*/
|
| 92 |
+
async function findPortInteractive(
|
| 93 |
+
options: FindPortConfig
|
| 94 |
+
): Promise<DiscoveredPort[]> {
|
| 95 |
+
const { onMessage } = options;
|
| 96 |
+
|
| 97 |
+
onMessage?.("🔍 Searching for available robot ports...");
|
| 98 |
+
|
| 99 |
+
// Get all available ports
|
| 100 |
+
const availablePorts = await findAvailablePorts();
|
| 101 |
+
|
| 102 |
+
if (availablePorts.length === 0) {
|
| 103 |
+
throw new Error("No serial ports found");
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
onMessage?.(
|
| 107 |
+
`Found ${availablePorts.length} port(s), first available: ${availablePorts[0]}`
|
| 108 |
+
);
|
| 109 |
+
|
| 110 |
+
// Return discovered ports (no connection attempt)
|
| 111 |
+
return availablePorts.map((path) => ({
|
| 112 |
+
path,
|
| 113 |
+
robotType: "so100_follower" as const, // Default type, user can override
|
| 114 |
+
}));
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
/**
|
| 118 |
+
* Auto-connect mode: Connect to robots by serial number/port path
|
| 119 |
+
* Returns all connection attempts (successful and failed)
|
| 120 |
+
*/
|
| 121 |
+
async function findPortAutoConnect(
|
| 122 |
+
robotConfigs: NonNullable<FindPortConfig["robotConfigs"]>,
|
| 123 |
+
options: FindPortConfig
|
| 124 |
+
): Promise<RobotConnection[]> {
|
| 125 |
+
const { onMessage } = options;
|
| 126 |
+
const results: RobotConnection[] = [];
|
| 127 |
+
|
| 128 |
+
onMessage?.(`🔍 Auto-connecting to ${robotConfigs.length} robot(s)...`);
|
| 129 |
+
|
| 130 |
+
for (const config of robotConfigs) {
|
| 131 |
+
try {
|
| 132 |
+
onMessage?.(
|
| 133 |
+
`Connecting to ${config.robotId} (${config.serialNumber})...`
|
| 134 |
+
);
|
| 135 |
+
|
| 136 |
+
// Use serialNumber as port path for Node.js
|
| 137 |
+
const connection = await connectPort(config.serialNumber);
|
| 138 |
+
|
| 139 |
+
if (connection.isConnected) {
|
| 140 |
+
onMessage?.(`✅ Connected to ${config.robotId}`);
|
| 141 |
+
results.push({
|
| 142 |
+
...connection,
|
| 143 |
+
robotType: config.robotType,
|
| 144 |
+
robotId: config.robotId,
|
| 145 |
+
serialNumber: config.serialNumber,
|
| 146 |
+
});
|
| 147 |
+
} else {
|
| 148 |
+
onMessage?.(`❌ Failed to connect to ${config.robotId}`);
|
| 149 |
+
results.push({
|
| 150 |
+
...connection,
|
| 151 |
+
robotType: config.robotType,
|
| 152 |
+
robotId: config.robotId,
|
| 153 |
+
serialNumber: config.serialNumber,
|
| 154 |
+
isConnected: false,
|
| 155 |
+
error: connection.error || "Connection failed",
|
| 156 |
+
});
|
| 157 |
+
}
|
| 158 |
+
} catch (error) {
|
| 159 |
+
onMessage?.(
|
| 160 |
+
`❌ Error connecting to ${config.robotId}: ${
|
| 161 |
+
error instanceof Error ? error.message : error
|
| 162 |
+
}`
|
| 163 |
+
);
|
| 164 |
+
results.push({
|
| 165 |
+
port: {
|
| 166 |
+
path: config.serialNumber,
|
| 167 |
+
write: async () => {},
|
| 168 |
+
read: async () => null,
|
| 169 |
+
open: async () => {},
|
| 170 |
+
close: async () => {},
|
| 171 |
+
isOpen: false,
|
| 172 |
+
},
|
| 173 |
+
name: `Failed: ${config.robotId}`,
|
| 174 |
+
isConnected: false,
|
| 175 |
+
robotType: config.robotType,
|
| 176 |
+
robotId: config.robotId,
|
| 177 |
+
serialNumber: config.serialNumber,
|
| 178 |
+
error: error instanceof Error ? error.message : "Unknown error",
|
| 179 |
+
});
|
| 180 |
+
}
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
const successCount = results.filter((r) => r.isConnected).length;
|
| 184 |
+
onMessage?.(
|
| 185 |
+
`🎯 Connected to ${successCount}/${robotConfigs.length} robot(s)`
|
| 186 |
+
);
|
| 187 |
+
|
| 188 |
+
return results;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
/**
|
| 192 |
+
* Main findPort function - Node.js discovery-only API
|
| 193 |
+
*
|
| 194 |
+
* Discovers available robot ports without connecting.
|
| 195 |
+
* User must call connectPort() separately to establish connections.
|
| 196 |
+
*/
|
| 197 |
+
export async function findPort(
|
| 198 |
+
config: FindPortConfig = {}
|
| 199 |
+
): Promise<FindPortProcess> {
|
| 200 |
+
const { onMessage } = config;
|
| 201 |
+
let stopped = false;
|
| 202 |
+
|
| 203 |
+
onMessage?.("🤖 Interactive port discovery started");
|
| 204 |
+
|
| 205 |
+
// Create result promise
|
| 206 |
+
const resultPromise = (async () => {
|
| 207 |
+
if (stopped) {
|
| 208 |
+
throw new Error("Port discovery was stopped");
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
return await findPortInteractive(config);
|
| 212 |
+
})();
|
| 213 |
+
|
| 214 |
+
// Return process object
|
| 215 |
+
return {
|
| 216 |
+
result: resultPromise,
|
| 217 |
+
stop: () => {
|
| 218 |
+
stopped = true;
|
| 219 |
+
onMessage?.("🛑 Port discovery stopped");
|
| 220 |
+
},
|
| 221 |
+
};
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
/**
|
| 225 |
+
* Interactive port detection for CLI usage only
|
| 226 |
+
* Matches Python lerobot's unplug/replug cable detection exactly
|
| 227 |
+
* This function should only be used by the CLI, not the library
|
| 228 |
+
*/
|
| 229 |
+
export async function detectPortInteractive(
|
| 230 |
+
onMessage?: (message: string) => void
|
| 231 |
+
): Promise<string> {
|
| 232 |
+
const { createInterface } = await import("readline");
|
| 233 |
+
|
| 234 |
+
const rl = createInterface({
|
| 235 |
+
input: process.stdin,
|
| 236 |
+
output: process.stdout,
|
| 237 |
+
});
|
| 238 |
+
|
| 239 |
+
function waitForInput(prompt: string): Promise<string> {
|
| 240 |
+
return new Promise((resolve) => {
|
| 241 |
+
rl.question(prompt, (answer: string) => {
|
| 242 |
+
resolve(answer);
|
| 243 |
+
});
|
| 244 |
+
});
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
try {
|
| 248 |
+
const message = "Finding all available ports for the MotorsBus.";
|
| 249 |
+
if (onMessage) onMessage(message);
|
| 250 |
+
else console.log(message);
|
| 251 |
+
|
| 252 |
+
// Get initial port list
|
| 253 |
+
const portsBefore = await findAvailablePorts();
|
| 254 |
+
|
| 255 |
+
const disconnectPrompt =
|
| 256 |
+
"Remove the USB cable from your MotorsBus and press Enter when done.";
|
| 257 |
+
await waitForInput(disconnectPrompt);
|
| 258 |
+
|
| 259 |
+
// Get port list after disconnect
|
| 260 |
+
const portsAfter = await findAvailablePorts();
|
| 261 |
+
|
| 262 |
+
// Find the difference
|
| 263 |
+
const portsDiff = portsBefore.filter((port) => !portsAfter.includes(port));
|
| 264 |
+
|
| 265 |
+
if (portsDiff.length === 1) {
|
| 266 |
+
const detectedPort = portsDiff[0];
|
| 267 |
+
const successMessage = `Detected port: ${detectedPort}`;
|
| 268 |
+
if (onMessage) onMessage(successMessage);
|
| 269 |
+
else console.log(successMessage);
|
| 270 |
+
|
| 271 |
+
const reconnectPrompt =
|
| 272 |
+
"Reconnect the USB cable to your MotorsBus and press Enter when done.";
|
| 273 |
+
await waitForInput(reconnectPrompt);
|
| 274 |
+
|
| 275 |
+
// Verify the port is back
|
| 276 |
+
const portsReconnected = await findAvailablePorts();
|
| 277 |
+
if (portsReconnected.includes(detectedPort)) {
|
| 278 |
+
const verifyMessage = `Verified port: ${detectedPort}`;
|
| 279 |
+
if (onMessage) onMessage(verifyMessage);
|
| 280 |
+
else console.log(verifyMessage);
|
| 281 |
+
return detectedPort;
|
| 282 |
+
} else {
|
| 283 |
+
throw new Error("Port not found after reconnection");
|
| 284 |
+
}
|
| 285 |
+
} else if (portsDiff.length === 0) {
|
| 286 |
+
throw new Error(
|
| 287 |
+
"No port difference detected. Please check cable connection."
|
| 288 |
+
);
|
| 289 |
+
} else {
|
| 290 |
+
throw new Error(
|
| 291 |
+
`Multiple ports detected: ${portsDiff.join(
|
| 292 |
+
", "
|
| 293 |
+
)}. Please disconnect other devices.`
|
| 294 |
+
);
|
| 295 |
+
}
|
| 296 |
+
} finally {
|
| 297 |
+
rl.close();
|
| 298 |
+
}
|
| 299 |
+
}
|
packages/node/src/index.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* @lerobot/node - Node.js-based robotics control using SerialPort API
|
| 3 |
+
*
|
| 4 |
+
* Control robotics hardware directly from Node.js applications, CLI tools, and desktop software.
|
| 5 |
+
*/
|
| 6 |
+
|
| 7 |
+
// Core functions
|
| 8 |
+
export { calibrate } from "./calibrate.js";
|
| 9 |
+
export { teleoperate } from "./teleoperate.js";
|
| 10 |
+
export { findPort, connectPort } from "./find_port.js";
|
| 11 |
+
export { releaseMotors } from "./release_motors.js";
|
| 12 |
+
|
| 13 |
+
// Types
|
| 14 |
+
export type {
|
| 15 |
+
RobotConnection,
|
| 16 |
+
RobotConfig,
|
| 17 |
+
SerialPort,
|
| 18 |
+
SerialPortInfo,
|
| 19 |
+
SerialOptions,
|
| 20 |
+
} from "./types/robot-connection.js";
|
| 21 |
+
|
| 22 |
+
export type {
|
| 23 |
+
FindPortConfig,
|
| 24 |
+
FindPortProcess,
|
| 25 |
+
DiscoveredPort,
|
| 26 |
+
} from "./types/port-discovery.js";
|
| 27 |
+
|
| 28 |
+
export type {
|
| 29 |
+
CalibrateConfig,
|
| 30 |
+
CalibrationResults,
|
| 31 |
+
LiveCalibrationData,
|
| 32 |
+
CalibrationProcess,
|
| 33 |
+
} from "./types/calibration.js";
|
| 34 |
+
|
| 35 |
+
export type {
|
| 36 |
+
MotorConfig,
|
| 37 |
+
TeleoperationState,
|
| 38 |
+
TeleoperationProcess,
|
| 39 |
+
TeleoperateConfig,
|
| 40 |
+
TeleoperatorConfig,
|
| 41 |
+
DirectTeleoperatorConfig,
|
| 42 |
+
} from "./types/teleoperation.js";
|
| 43 |
+
|
| 44 |
+
export type {
|
| 45 |
+
RobotHardwareConfig,
|
| 46 |
+
KeyboardControl,
|
| 47 |
+
} from "./types/robot-config.js";
|
| 48 |
+
|
| 49 |
+
// Utilities (advanced users)
|
| 50 |
+
export { NodeSerialPortWrapper } from "./utils/serial-port-wrapper.js";
|
| 51 |
+
export {
|
| 52 |
+
readAllMotorPositions,
|
| 53 |
+
readMotorPosition,
|
| 54 |
+
} from "./utils/motor-communication.js";
|
| 55 |
+
export {
|
| 56 |
+
createSO100Config,
|
| 57 |
+
SO100_KEYBOARD_CONTROLS,
|
| 58 |
+
} from "./robots/so100_config.js";
|
| 59 |
+
export { KEYBOARD_TELEOPERATOR_DEFAULTS } from "./teleoperators/index.js";
|
| 60 |
+
export {
|
| 61 |
+
getHfHome,
|
| 62 |
+
getHfLerobotHome,
|
| 63 |
+
getCalibrationDir,
|
| 64 |
+
} from "./utils/constants.js";
|
packages/node/src/release_motors.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* User-facing motor release functionality for Node.js
|
| 3 |
+
* Simple API - pass in robotConnection, motors get released
|
| 4 |
+
*
|
| 5 |
+
* Handles robot configuration and port management internally
|
| 6 |
+
*/
|
| 7 |
+
|
| 8 |
+
import { NodeSerialPortWrapper } from "./utils/serial-port-wrapper.js";
|
| 9 |
+
import { createSO100Config } from "./robots/so100_config.js";
|
| 10 |
+
import { releaseMotors as releaseMotorsLowLevel } from "./utils/motor-communication.js";
|
| 11 |
+
import type { RobotConnection } from "./types/robot-connection.js";
|
| 12 |
+
|
| 13 |
+
/**
|
| 14 |
+
* Release robot motors (allows free movement by hand)
|
| 15 |
+
* Perfect for calibration setup or manual positioning
|
| 16 |
+
*
|
| 17 |
+
* @param robotConnection - Connected robot with configured type
|
| 18 |
+
* @param motorIds - Optional specific motor IDs to release (defaults to all motors for robot type)
|
| 19 |
+
* @throws Error if robot type not configured or motorIds invalid
|
| 20 |
+
*/
|
| 21 |
+
export async function releaseMotors(
|
| 22 |
+
robotConnection: RobotConnection,
|
| 23 |
+
motorIds?: number[]
|
| 24 |
+
): Promise<void> {
|
| 25 |
+
// Validate robot type is configured
|
| 26 |
+
if (!robotConnection.robotType) {
|
| 27 |
+
throw new Error(
|
| 28 |
+
"Robot type is required to release motors. Please configure the robot first."
|
| 29 |
+
);
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
// Validate robot connection
|
| 33 |
+
if (!robotConnection.isConnected || !robotConnection.port) {
|
| 34 |
+
throw new Error(
|
| 35 |
+
"Robot is not connected. Please use findPort() to connect first."
|
| 36 |
+
);
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
// Use the EXISTING port connection (don't create new one!)
|
| 40 |
+
const port = robotConnection.port;
|
| 41 |
+
|
| 42 |
+
// Get robot-specific configuration
|
| 43 |
+
let robotConfig;
|
| 44 |
+
if (robotConnection.robotType.startsWith("so100")) {
|
| 45 |
+
robotConfig = createSO100Config(robotConnection.robotType);
|
| 46 |
+
} else {
|
| 47 |
+
throw new Error(`Unsupported robot type: ${robotConnection.robotType}`);
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
// Determine which motors to release
|
| 51 |
+
const motorsToRelease = motorIds || robotConfig.motorIds;
|
| 52 |
+
|
| 53 |
+
// Validate motorIds are valid for this robot type
|
| 54 |
+
if (motorIds) {
|
| 55 |
+
const invalidMotors = motorIds.filter(
|
| 56 |
+
(id) => !robotConfig.motorIds.includes(id)
|
| 57 |
+
);
|
| 58 |
+
if (invalidMotors.length > 0) {
|
| 59 |
+
throw new Error(
|
| 60 |
+
`Invalid motor IDs [${invalidMotors.join(", ")}] for ${
|
| 61 |
+
robotConnection.robotType
|
| 62 |
+
}. Valid IDs: [${robotConfig.motorIds.join(", ")}]`
|
| 63 |
+
);
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
// Release the motors using low-level function
|
| 68 |
+
await releaseMotorsLowLevel(port, motorsToRelease);
|
| 69 |
+
// Note: Don't close the port - it belongs to the robot connection
|
| 70 |
+
}
|
packages/node/src/robots/so100_config.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* SO-100 specific hardware configuration
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
import type { RobotHardwareConfig } from "../types/robot-config.js";
|
| 6 |
+
|
| 7 |
+
/**
|
| 8 |
+
* STS3215 Protocol Configuration for SO-100 devices
|
| 9 |
+
*/
|
| 10 |
+
export const NODE_STS3215_PROTOCOL = {
|
| 11 |
+
resolution: 4096, // 12-bit resolution (0-4095)
|
| 12 |
+
homingOffsetAddress: 31, // Address for Homing_Offset register
|
| 13 |
+
homingOffsetLength: 2, // 2 bytes for Homing_Offset
|
| 14 |
+
presentPositionAddress: 56, // Address for Present_Position register
|
| 15 |
+
presentPositionLength: 2, // 2 bytes for Present_Position
|
| 16 |
+
minPositionLimitAddress: 9, // Address for Min_Position_Limit register
|
| 17 |
+
minPositionLimitLength: 2, // 2 bytes for Min_Position_Limit
|
| 18 |
+
maxPositionLimitAddress: 11, // Address for Max_Position_Limit register
|
| 19 |
+
maxPositionLimitLength: 2, // 2 bytes for Max_Position_Limit
|
| 20 |
+
signMagnitudeBit: 11, // Bit 11 is sign bit for Homing_Offset encoding
|
| 21 |
+
} as const;
|
| 22 |
+
|
| 23 |
+
/**
|
| 24 |
+
* SO-100 Device Configuration
|
| 25 |
+
* Motor names, IDs, and drive modes for both follower and leader
|
| 26 |
+
*/
|
| 27 |
+
export const SO100_CONFIG = {
|
| 28 |
+
motorNames: [
|
| 29 |
+
"shoulder_pan",
|
| 30 |
+
"shoulder_lift",
|
| 31 |
+
"elbow_flex",
|
| 32 |
+
"wrist_flex",
|
| 33 |
+
"wrist_roll",
|
| 34 |
+
"gripper",
|
| 35 |
+
],
|
| 36 |
+
motorIds: [1, 2, 3, 4, 5, 6],
|
| 37 |
+
// All SO-100 motors use drive_mode=0
|
| 38 |
+
driveModes: [0, 0, 0, 0, 0, 0],
|
| 39 |
+
};
|
| 40 |
+
|
| 41 |
+
/**
|
| 42 |
+
* SO-100 Keyboard Controls for Teleoperation
|
| 43 |
+
* Robot-specific mapping optimized for SO-100 joint layout
|
| 44 |
+
*/
|
| 45 |
+
export const SO100_KEYBOARD_CONTROLS = {
|
| 46 |
+
// Shoulder controls
|
| 47 |
+
ArrowUp: { motor: "shoulder_lift", direction: 1, description: "Shoulder up" },
|
| 48 |
+
ArrowDown: {
|
| 49 |
+
motor: "shoulder_lift",
|
| 50 |
+
direction: -1,
|
| 51 |
+
description: "Shoulder down",
|
| 52 |
+
},
|
| 53 |
+
ArrowLeft: {
|
| 54 |
+
motor: "shoulder_pan",
|
| 55 |
+
direction: -1,
|
| 56 |
+
description: "Shoulder left",
|
| 57 |
+
},
|
| 58 |
+
ArrowRight: {
|
| 59 |
+
motor: "shoulder_pan",
|
| 60 |
+
direction: 1,
|
| 61 |
+
description: "Shoulder right",
|
| 62 |
+
},
|
| 63 |
+
|
| 64 |
+
// WASD controls
|
| 65 |
+
w: { motor: "elbow_flex", direction: 1, description: "Elbow flex" },
|
| 66 |
+
s: { motor: "elbow_flex", direction: -1, description: "Elbow extend" },
|
| 67 |
+
a: { motor: "wrist_flex", direction: -1, description: "Wrist down" },
|
| 68 |
+
d: { motor: "wrist_flex", direction: 1, description: "Wrist up" },
|
| 69 |
+
|
| 70 |
+
// Wrist roll and gripper
|
| 71 |
+
q: { motor: "wrist_roll", direction: -1, description: "Wrist roll left" },
|
| 72 |
+
e: { motor: "wrist_roll", direction: 1, description: "Wrist roll right" },
|
| 73 |
+
o: { motor: "gripper", direction: 1, description: "Gripper open" },
|
| 74 |
+
c: { motor: "gripper", direction: -1, description: "Gripper close" },
|
| 75 |
+
|
| 76 |
+
// Emergency stop
|
| 77 |
+
Escape: {
|
| 78 |
+
motor: "emergency_stop",
|
| 79 |
+
direction: 0,
|
| 80 |
+
description: "Emergency stop",
|
| 81 |
+
},
|
| 82 |
+
} as const;
|
| 83 |
+
|
| 84 |
+
/**
|
| 85 |
+
* Create SO-100 hardware configuration
|
| 86 |
+
*/
|
| 87 |
+
export function createSO100Config(
|
| 88 |
+
deviceType: "so100_follower" | "so100_leader"
|
| 89 |
+
): RobotHardwareConfig {
|
| 90 |
+
return {
|
| 91 |
+
deviceType,
|
| 92 |
+
motorNames: SO100_CONFIG.motorNames,
|
| 93 |
+
motorIds: SO100_CONFIG.motorIds,
|
| 94 |
+
driveModes: SO100_CONFIG.driveModes,
|
| 95 |
+
keyboardControls: SO100_KEYBOARD_CONTROLS,
|
| 96 |
+
protocol: NODE_STS3215_PROTOCOL,
|
| 97 |
+
};
|
| 98 |
+
}
|
packages/node/src/teleoperate.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Node.js teleoperation functionality using serialport API
|
| 3 |
+
* Provides both Python lerobot compatible CLI behavior and programmatic usage
|
| 4 |
+
* Uses proven teleoperator classes with web-compatible API
|
| 5 |
+
*/
|
| 6 |
+
|
| 7 |
+
import { NodeSerialPortWrapper } from "./utils/serial-port-wrapper.js";
|
| 8 |
+
import { createSO100Config } from "./robots/so100_config.js";
|
| 9 |
+
import { readAllMotorPositions } from "./utils/motor-communication.js";
|
| 10 |
+
import {
|
| 11 |
+
KeyboardTeleoperator,
|
| 12 |
+
DirectTeleoperator,
|
| 13 |
+
} from "./teleoperators/index.js";
|
| 14 |
+
import { readFile } from "fs/promises";
|
| 15 |
+
import { join } from "path";
|
| 16 |
+
import { homedir } from "os";
|
| 17 |
+
import { existsSync } from "fs";
|
| 18 |
+
import type {
|
| 19 |
+
TeleoperateConfig,
|
| 20 |
+
TeleoperationProcess,
|
| 21 |
+
MotorConfig,
|
| 22 |
+
TeleoperationState,
|
| 23 |
+
} from "./types/teleoperation.js";
|
| 24 |
+
import type { RobotConnection } from "./types/robot-connection.js";
|
| 25 |
+
import type { CalibrationResults } from "./types/calibration.js";
|
| 26 |
+
|
| 27 |
+
/**
|
| 28 |
+
* Get calibration file path (matches Python lerobot location)
|
| 29 |
+
*/
|
| 30 |
+
function getCalibrationFilePath(robotType: string, robotId: string): string {
|
| 31 |
+
const HF_HOME =
|
| 32 |
+
process.env.HF_HOME || join(homedir(), ".cache", "huggingface");
|
| 33 |
+
const calibrationDir = join(
|
| 34 |
+
HF_HOME,
|
| 35 |
+
"lerobot",
|
| 36 |
+
"calibration",
|
| 37 |
+
"robots",
|
| 38 |
+
robotType
|
| 39 |
+
);
|
| 40 |
+
return join(calibrationDir, `${robotId}.json`);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
/**
|
| 44 |
+
* Load calibration data from file system
|
| 45 |
+
*/
|
| 46 |
+
async function loadCalibrationData(
|
| 47 |
+
robotType: string,
|
| 48 |
+
robotId: string
|
| 49 |
+
): Promise<CalibrationResults | null> {
|
| 50 |
+
const calibrationPath = getCalibrationFilePath(robotType, robotId);
|
| 51 |
+
|
| 52 |
+
if (!existsSync(calibrationPath)) {
|
| 53 |
+
return null;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
try {
|
| 57 |
+
const calibrationJson = await readFile(calibrationPath, "utf-8");
|
| 58 |
+
return JSON.parse(calibrationJson) as CalibrationResults;
|
| 59 |
+
} catch (error) {
|
| 60 |
+
console.warn(
|
| 61 |
+
`Failed to load calibration data from ${calibrationPath}:`,
|
| 62 |
+
error
|
| 63 |
+
);
|
| 64 |
+
return null;
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
/**
|
| 69 |
+
* Build motor configurations from robot config and calibration data
|
| 70 |
+
*/
|
| 71 |
+
function buildMotorConfigs(
|
| 72 |
+
robotConfig: any,
|
| 73 |
+
calibrationData?: CalibrationResults | null
|
| 74 |
+
): MotorConfig[] {
|
| 75 |
+
const motorConfigs: MotorConfig[] = [];
|
| 76 |
+
|
| 77 |
+
for (let i = 0; i < robotConfig.motorNames.length; i++) {
|
| 78 |
+
const motorName = robotConfig.motorNames[i];
|
| 79 |
+
const motorId = robotConfig.motorIds[i];
|
| 80 |
+
|
| 81 |
+
let minPosition = 0;
|
| 82 |
+
let maxPosition = robotConfig.protocol.resolution - 1; // Default full range
|
| 83 |
+
|
| 84 |
+
// Use calibration data if available
|
| 85 |
+
if (calibrationData && calibrationData[motorName]) {
|
| 86 |
+
minPosition = calibrationData[motorName].range_min;
|
| 87 |
+
maxPosition = calibrationData[motorName].range_max;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
motorConfigs.push({
|
| 91 |
+
id: motorId,
|
| 92 |
+
name: motorName,
|
| 93 |
+
currentPosition: Math.floor((minPosition + maxPosition) / 2), // Start at center
|
| 94 |
+
minPosition,
|
| 95 |
+
maxPosition,
|
| 96 |
+
});
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
return motorConfigs;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
/**
|
| 103 |
+
* Main teleoperate function with web-compatible API
|
| 104 |
+
*/
|
| 105 |
+
export async function teleoperate(
|
| 106 |
+
config: TeleoperateConfig
|
| 107 |
+
): Promise<TeleoperationProcess> {
|
| 108 |
+
const { robot, teleop, calibrationData, onStateUpdate } = config;
|
| 109 |
+
|
| 110 |
+
// Validate robot configuration
|
| 111 |
+
if (!robot.robotType) {
|
| 112 |
+
throw new Error(
|
| 113 |
+
"Robot type is required for teleoperation. Please configure the robot first."
|
| 114 |
+
);
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
if (!robot.isConnected || !robot.port) {
|
| 118 |
+
throw new Error(
|
| 119 |
+
"Robot is not connected. Please use findPort() to connect first."
|
| 120 |
+
);
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
// Use the EXISTING port connection (don't create new one!)
|
| 124 |
+
const port = robot.port;
|
| 125 |
+
|
| 126 |
+
// Get robot-specific configuration
|
| 127 |
+
let robotConfig;
|
| 128 |
+
if (robot.robotType.startsWith("so100")) {
|
| 129 |
+
robotConfig = createSO100Config(robot.robotType);
|
| 130 |
+
} else {
|
| 131 |
+
throw new Error(`Unsupported robot type: ${robot.robotType}`);
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
// Load or use provided calibration data
|
| 135 |
+
let effectiveCalibrationData = calibrationData;
|
| 136 |
+
if (!effectiveCalibrationData && robot.robotId) {
|
| 137 |
+
effectiveCalibrationData = await loadCalibrationData(
|
| 138 |
+
robot.robotType,
|
| 139 |
+
robot.robotId
|
| 140 |
+
);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
if (!effectiveCalibrationData) {
|
| 144 |
+
console.warn(
|
| 145 |
+
"No calibration data found. Using default motor ranges. Consider running calibration first."
|
| 146 |
+
);
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
// Build motor configurations
|
| 150 |
+
const motorConfigs = buildMotorConfigs(robotConfig, effectiveCalibrationData);
|
| 151 |
+
|
| 152 |
+
// Read current motor positions
|
| 153 |
+
try {
|
| 154 |
+
const currentPositions = await readAllMotorPositions(
|
| 155 |
+
port,
|
| 156 |
+
robotConfig.motorIds
|
| 157 |
+
);
|
| 158 |
+
for (let i = 0; i < motorConfigs.length; i++) {
|
| 159 |
+
motorConfigs[i].currentPosition = currentPositions[i];
|
| 160 |
+
}
|
| 161 |
+
} catch (error) {
|
| 162 |
+
console.warn("Failed to read initial motor positions:", error);
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
// Create appropriate teleoperator based on configuration
|
| 166 |
+
let teleoperator;
|
| 167 |
+
switch (teleop.type) {
|
| 168 |
+
case "keyboard":
|
| 169 |
+
teleoperator = new KeyboardTeleoperator(
|
| 170 |
+
teleop,
|
| 171 |
+
port,
|
| 172 |
+
motorConfigs,
|
| 173 |
+
robotConfig.keyboardControls,
|
| 174 |
+
onStateUpdate
|
| 175 |
+
);
|
| 176 |
+
break;
|
| 177 |
+
|
| 178 |
+
case "direct":
|
| 179 |
+
teleoperator = new DirectTeleoperator(teleop, port, motorConfigs);
|
| 180 |
+
break;
|
| 181 |
+
|
| 182 |
+
default:
|
| 183 |
+
throw new Error(`Unsupported teleoperator type: ${(teleop as any).type}`);
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
// Initialize teleoperator
|
| 187 |
+
await teleoperator.initialize();
|
| 188 |
+
|
| 189 |
+
// Build process object
|
| 190 |
+
const process: TeleoperationProcess = {
|
| 191 |
+
start(): void {
|
| 192 |
+
teleoperator.start();
|
| 193 |
+
},
|
| 194 |
+
|
| 195 |
+
stop(): void {
|
| 196 |
+
teleoperator.stop();
|
| 197 |
+
},
|
| 198 |
+
|
| 199 |
+
updateKeyState(key: string, pressed: boolean): void {
|
| 200 |
+
if ("updateKeyState" in teleoperator) {
|
| 201 |
+
(teleoperator as any).updateKeyState(key, pressed);
|
| 202 |
+
}
|
| 203 |
+
},
|
| 204 |
+
|
| 205 |
+
getState(): TeleoperationState {
|
| 206 |
+
const teleoperatorSpecificState = teleoperator.getState();
|
| 207 |
+
return {
|
| 208 |
+
isActive: teleoperator.isActiveTeleoperator,
|
| 209 |
+
motorConfigs: [...teleoperator.motorConfigs],
|
| 210 |
+
lastUpdate: Date.now(),
|
| 211 |
+
...teleoperatorSpecificState,
|
| 212 |
+
};
|
| 213 |
+
},
|
| 214 |
+
|
| 215 |
+
teleoperator: teleoperator,
|
| 216 |
+
|
| 217 |
+
async disconnect(): Promise<void> {
|
| 218 |
+
await teleoperator.disconnect();
|
| 219 |
+
},
|
| 220 |
+
};
|
| 221 |
+
|
| 222 |
+
return process;
|
| 223 |
+
}
|
packages/node/src/teleoperators/base-teleoperator.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Base teleoperator interface and abstract class for Node.js platform
|
| 3 |
+
* Defines the contract that all teleoperators must implement
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import type { MotorConfig } from "../types/teleoperation.js";
|
| 7 |
+
import type { MotorCommunicationPort } from "../utils/motor-communication.js";
|
| 8 |
+
|
| 9 |
+
/**
|
| 10 |
+
* Base interface that all Node.js teleoperators must implement
|
| 11 |
+
*/
|
| 12 |
+
export interface NodeTeleoperator {
|
| 13 |
+
initialize(): Promise<void>;
|
| 14 |
+
start(): void;
|
| 15 |
+
stop(): void;
|
| 16 |
+
disconnect(): Promise<void>;
|
| 17 |
+
getState(): TeleoperatorSpecificState;
|
| 18 |
+
onMotorConfigsUpdate(motorConfigs: MotorConfig[]): void;
|
| 19 |
+
motorConfigs: MotorConfig[];
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
/**
|
| 23 |
+
* Teleoperator-specific state (union type for different teleoperator types)
|
| 24 |
+
*/
|
| 25 |
+
export type TeleoperatorSpecificState = {
|
| 26 |
+
keyStates?: { [key: string]: { pressed: boolean; timestamp: number } }; // keyboard
|
| 27 |
+
leaderPositions?: { [motor: string]: number }; // leader arm
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
/**
|
| 31 |
+
* Base abstract class with common functionality for all teleoperators
|
| 32 |
+
*/
|
| 33 |
+
export abstract class BaseNodeTeleoperator implements NodeTeleoperator {
|
| 34 |
+
protected port: MotorCommunicationPort;
|
| 35 |
+
public motorConfigs: MotorConfig[] = [];
|
| 36 |
+
protected isActive: boolean = false;
|
| 37 |
+
|
| 38 |
+
constructor(port: MotorCommunicationPort, motorConfigs: MotorConfig[]) {
|
| 39 |
+
this.port = port;
|
| 40 |
+
this.motorConfigs = motorConfigs;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
abstract initialize(): Promise<void>;
|
| 44 |
+
abstract start(): void;
|
| 45 |
+
abstract stop(): void;
|
| 46 |
+
abstract getState(): TeleoperatorSpecificState;
|
| 47 |
+
|
| 48 |
+
async disconnect(): Promise<void> {
|
| 49 |
+
this.stop();
|
| 50 |
+
if (this.port && "close" in this.port) {
|
| 51 |
+
await (this.port as any).close();
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
onMotorConfigsUpdate(motorConfigs: MotorConfig[]): void {
|
| 56 |
+
this.motorConfigs = motorConfigs;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
get isActiveTeleoperator(): boolean {
|
| 60 |
+
return this.isActive;
|
| 61 |
+
}
|
| 62 |
+
}
|
packages/node/src/teleoperators/direct-teleoperator.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Direct teleoperator for Node.js platform
|
| 3 |
+
* Provides programmatic control without user interface
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import {
|
| 7 |
+
BaseNodeTeleoperator,
|
| 8 |
+
type TeleoperatorSpecificState,
|
| 9 |
+
} from "./base-teleoperator.js";
|
| 10 |
+
import type {
|
| 11 |
+
DirectTeleoperatorConfig,
|
| 12 |
+
MotorConfig,
|
| 13 |
+
} from "../types/teleoperation.js";
|
| 14 |
+
import type { MotorCommunicationPort } from "../utils/motor-communication.js";
|
| 15 |
+
import {
|
| 16 |
+
readMotorPosition,
|
| 17 |
+
writeMotorPosition,
|
| 18 |
+
} from "../utils/motor-communication.js";
|
| 19 |
+
|
| 20 |
+
/**
|
| 21 |
+
* Direct teleoperator provides programmatic motor control
|
| 22 |
+
* Use this when you want to control motors directly from code
|
| 23 |
+
*/
|
| 24 |
+
export class DirectTeleoperator extends BaseNodeTeleoperator {
|
| 25 |
+
constructor(
|
| 26 |
+
config: DirectTeleoperatorConfig,
|
| 27 |
+
port: MotorCommunicationPort,
|
| 28 |
+
motorConfigs: MotorConfig[]
|
| 29 |
+
) {
|
| 30 |
+
super(port, motorConfigs);
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
async initialize(): Promise<void> {
|
| 34 |
+
// Read current motor positions
|
| 35 |
+
for (const config of this.motorConfigs) {
|
| 36 |
+
const position = await readMotorPosition(this.port, config.id);
|
| 37 |
+
if (position !== null) {
|
| 38 |
+
config.currentPosition = position;
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
start(): void {
|
| 44 |
+
this.isActive = true;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
stop(): void {
|
| 48 |
+
this.isActive = false;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
getState(): TeleoperatorSpecificState {
|
| 52 |
+
return {};
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
/**
|
| 56 |
+
* Move motor to exact position (programmatic control)
|
| 57 |
+
*/
|
| 58 |
+
async moveMotor(motorName: string, targetPosition: number): Promise<boolean> {
|
| 59 |
+
if (!this.isActive) return false;
|
| 60 |
+
|
| 61 |
+
const motorConfig = this.motorConfigs.find((m) => m.name === motorName);
|
| 62 |
+
if (!motorConfig) return false;
|
| 63 |
+
|
| 64 |
+
const clampedPosition = Math.max(
|
| 65 |
+
motorConfig.minPosition,
|
| 66 |
+
Math.min(motorConfig.maxPosition, targetPosition)
|
| 67 |
+
);
|
| 68 |
+
|
| 69 |
+
try {
|
| 70 |
+
await writeMotorPosition(
|
| 71 |
+
this.port,
|
| 72 |
+
motorConfig.id,
|
| 73 |
+
Math.round(clampedPosition)
|
| 74 |
+
);
|
| 75 |
+
motorConfig.currentPosition = clampedPosition;
|
| 76 |
+
return true;
|
| 77 |
+
} catch (error) {
|
| 78 |
+
console.warn(`Failed to move motor ${motorName}:`, error);
|
| 79 |
+
return false;
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
/**
|
| 84 |
+
* Move multiple motors simultaneously
|
| 85 |
+
*/
|
| 86 |
+
async moveMotors(
|
| 87 |
+
positions: { [motorName: string]: number }
|
| 88 |
+
): Promise<{ [motorName: string]: boolean }> {
|
| 89 |
+
const results: { [motorName: string]: boolean } = {};
|
| 90 |
+
|
| 91 |
+
const promises = Object.entries(positions).map(
|
| 92 |
+
async ([motorName, position]) => {
|
| 93 |
+
const success = await this.moveMotor(motorName, position);
|
| 94 |
+
results[motorName] = success;
|
| 95 |
+
}
|
| 96 |
+
);
|
| 97 |
+
|
| 98 |
+
await Promise.all(promises);
|
| 99 |
+
return results;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
/**
|
| 103 |
+
* Get current motor positions
|
| 104 |
+
*/
|
| 105 |
+
getCurrentPositions(): { [motorName: string]: number } {
|
| 106 |
+
const positions: { [motorName: string]: number } = {};
|
| 107 |
+
for (const config of this.motorConfigs) {
|
| 108 |
+
positions[config.name] = config.currentPosition;
|
| 109 |
+
}
|
| 110 |
+
return positions;
|
| 111 |
+
}
|
| 112 |
+
}
|
packages/node/src/teleoperators/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Teleoperators barrel exports for Node.js
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
export {
|
| 6 |
+
BaseNodeTeleoperator,
|
| 7 |
+
type NodeTeleoperator,
|
| 8 |
+
type TeleoperatorSpecificState,
|
| 9 |
+
} from "./base-teleoperator.js";
|
| 10 |
+
export {
|
| 11 |
+
KeyboardTeleoperator,
|
| 12 |
+
KEYBOARD_TELEOPERATOR_DEFAULTS,
|
| 13 |
+
} from "./keyboard-teleoperator.js";
|
| 14 |
+
export { DirectTeleoperator } from "./direct-teleoperator.js";
|
packages/node/src/teleoperators/keyboard-teleoperator.ts
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Keyboard teleoperator for Node.js platform using stdin
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
import {
|
| 6 |
+
BaseNodeTeleoperator,
|
| 7 |
+
type TeleoperatorSpecificState,
|
| 8 |
+
} from "./base-teleoperator.js";
|
| 9 |
+
import type { KeyboardControl } from "../types/robot-config.js";
|
| 10 |
+
import type {
|
| 11 |
+
KeyboardTeleoperatorConfig,
|
| 12 |
+
MotorConfig,
|
| 13 |
+
TeleoperationState,
|
| 14 |
+
} from "../types/teleoperation.js";
|
| 15 |
+
import type { MotorCommunicationPort } from "../utils/motor-communication.js";
|
| 16 |
+
import {
|
| 17 |
+
readMotorPosition,
|
| 18 |
+
writeMotorPosition,
|
| 19 |
+
} from "../utils/motor-communication.js";
|
| 20 |
+
|
| 21 |
+
/**
|
| 22 |
+
* Default configuration values for keyboard teleoperator
|
| 23 |
+
*/
|
| 24 |
+
export const KEYBOARD_TELEOPERATOR_DEFAULTS = {
|
| 25 |
+
stepSize: 8, // Keep browser demo step size
|
| 26 |
+
updateRate: 120, // Higher frequency for smoother movement (120 Hz)
|
| 27 |
+
keyTimeout: 150, // Shorter for better single taps, accept some gap on hold
|
| 28 |
+
} as const;
|
| 29 |
+
|
| 30 |
+
export class KeyboardTeleoperator extends BaseNodeTeleoperator {
|
| 31 |
+
private keyboardControls: { [key: string]: KeyboardControl } = {};
|
| 32 |
+
private updateInterval: NodeJS.Timeout | null = null;
|
| 33 |
+
private keyStates: {
|
| 34 |
+
[key: string]: { pressed: boolean; timestamp: number };
|
| 35 |
+
} = {};
|
| 36 |
+
private onStateUpdate?: (state: TeleoperationState) => void;
|
| 37 |
+
|
| 38 |
+
// Configuration values
|
| 39 |
+
private readonly stepSize: number;
|
| 40 |
+
private readonly updateRate: number;
|
| 41 |
+
private readonly keyTimeout: number;
|
| 42 |
+
|
| 43 |
+
constructor(
|
| 44 |
+
config: KeyboardTeleoperatorConfig,
|
| 45 |
+
port: MotorCommunicationPort,
|
| 46 |
+
motorConfigs: MotorConfig[],
|
| 47 |
+
keyboardControls: { [key: string]: KeyboardControl },
|
| 48 |
+
onStateUpdate?: (state: TeleoperationState) => void
|
| 49 |
+
) {
|
| 50 |
+
super(port, motorConfigs);
|
| 51 |
+
this.keyboardControls = keyboardControls;
|
| 52 |
+
this.onStateUpdate = onStateUpdate;
|
| 53 |
+
|
| 54 |
+
// Set configuration values
|
| 55 |
+
this.stepSize = config.stepSize ?? KEYBOARD_TELEOPERATOR_DEFAULTS.stepSize;
|
| 56 |
+
this.updateRate =
|
| 57 |
+
config.updateRate ?? KEYBOARD_TELEOPERATOR_DEFAULTS.updateRate;
|
| 58 |
+
this.keyTimeout =
|
| 59 |
+
config.keyTimeout ?? KEYBOARD_TELEOPERATOR_DEFAULTS.keyTimeout;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
async initialize(): Promise<void> {
|
| 63 |
+
// Set up stdin for raw keyboard input
|
| 64 |
+
if (process.stdin.setRawMode) {
|
| 65 |
+
process.stdin.setRawMode(true);
|
| 66 |
+
}
|
| 67 |
+
process.stdin.resume();
|
| 68 |
+
process.stdin.setEncoding("utf8");
|
| 69 |
+
|
| 70 |
+
// Set up keyboard input handler
|
| 71 |
+
process.stdin.on("data", this.handleKeyboardInput.bind(this));
|
| 72 |
+
|
| 73 |
+
// Read current motor positions
|
| 74 |
+
for (const config of this.motorConfigs) {
|
| 75 |
+
const position = await readMotorPosition(this.port, config.id);
|
| 76 |
+
if (position !== null) {
|
| 77 |
+
config.currentPosition = position;
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
start(): void {
|
| 83 |
+
if (this.isActive) return;
|
| 84 |
+
|
| 85 |
+
this.isActive = true;
|
| 86 |
+
this.updateInterval = setInterval(() => {
|
| 87 |
+
this.updateMotorPositions();
|
| 88 |
+
}, 1000 / this.updateRate);
|
| 89 |
+
|
| 90 |
+
// Display keyboard controls
|
| 91 |
+
this.displayControls();
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
stop(): void {
|
| 95 |
+
if (!this.isActive) return;
|
| 96 |
+
|
| 97 |
+
this.isActive = false;
|
| 98 |
+
|
| 99 |
+
if (this.updateInterval) {
|
| 100 |
+
clearInterval(this.updateInterval);
|
| 101 |
+
this.updateInterval = null;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
// Clear all key states
|
| 105 |
+
this.keyStates = {};
|
| 106 |
+
|
| 107 |
+
// Notify of state change
|
| 108 |
+
if (this.onStateUpdate) {
|
| 109 |
+
this.onStateUpdate(this.buildTeleoperationState());
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
getState(): TeleoperatorSpecificState {
|
| 114 |
+
return {
|
| 115 |
+
keyStates: { ...this.keyStates },
|
| 116 |
+
};
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
updateKeyState(key: string, pressed: boolean): void {
|
| 120 |
+
this.keyStates[key] = {
|
| 121 |
+
pressed,
|
| 122 |
+
timestamp: Date.now(),
|
| 123 |
+
};
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
private handleKeyboardInput(key: string): void {
|
| 127 |
+
if (!this.isActive) return;
|
| 128 |
+
|
| 129 |
+
// Handle special keys
|
| 130 |
+
if (key === "\u0003") {
|
| 131 |
+
// Ctrl+C
|
| 132 |
+
process.exit(0);
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
if (key === "\u001b") {
|
| 136 |
+
// Escape
|
| 137 |
+
this.stop();
|
| 138 |
+
return;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
// Handle regular keys - START IMMEDIATE CONTINUOUS MOVEMENT
|
| 142 |
+
const keyName = this.mapKeyToName(key);
|
| 143 |
+
if (keyName && this.keyboardControls[keyName]) {
|
| 144 |
+
// If key is already active, just refresh timestamp
|
| 145 |
+
if (this.keyStates[keyName]) {
|
| 146 |
+
this.keyStates[keyName].timestamp = Date.now();
|
| 147 |
+
} else {
|
| 148 |
+
// New key press - start continuous movement immediately
|
| 149 |
+
this.updateKeyState(keyName, true);
|
| 150 |
+
|
| 151 |
+
// Move immediately on first press (don't wait for interval)
|
| 152 |
+
this.moveMotorForKey(keyName);
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
private moveMotorForKey(keyName: string): void {
|
| 158 |
+
const control = this.keyboardControls[keyName];
|
| 159 |
+
if (!control || control.motor === "emergency_stop") return;
|
| 160 |
+
|
| 161 |
+
const motorConfig = this.motorConfigs.find((m) => m.name === control.motor);
|
| 162 |
+
if (!motorConfig) return;
|
| 163 |
+
|
| 164 |
+
// Calculate new position
|
| 165 |
+
const newPosition =
|
| 166 |
+
motorConfig.currentPosition + control.direction * this.stepSize;
|
| 167 |
+
|
| 168 |
+
// Apply limits
|
| 169 |
+
const clampedPosition = Math.max(
|
| 170 |
+
motorConfig.minPosition,
|
| 171 |
+
Math.min(motorConfig.maxPosition, newPosition)
|
| 172 |
+
);
|
| 173 |
+
|
| 174 |
+
// Send motor command immediately
|
| 175 |
+
writeMotorPosition(this.port, motorConfig.id, Math.round(clampedPosition))
|
| 176 |
+
.then(() => {
|
| 177 |
+
motorConfig.currentPosition = clampedPosition;
|
| 178 |
+
})
|
| 179 |
+
.catch((error) => {
|
| 180 |
+
console.warn(`Failed to move motor ${motorConfig.id}:`, error);
|
| 181 |
+
});
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
private updateMotorPositions(): void {
|
| 185 |
+
const now = Date.now();
|
| 186 |
+
|
| 187 |
+
// Clear timed-out keys
|
| 188 |
+
Object.keys(this.keyStates).forEach((key) => {
|
| 189 |
+
if (now - this.keyStates[key].timestamp > this.keyTimeout) {
|
| 190 |
+
delete this.keyStates[key];
|
| 191 |
+
}
|
| 192 |
+
});
|
| 193 |
+
|
| 194 |
+
// Process active keys
|
| 195 |
+
const activeKeys = Object.keys(this.keyStates).filter(
|
| 196 |
+
(key) =>
|
| 197 |
+
this.keyStates[key].pressed &&
|
| 198 |
+
now - this.keyStates[key].timestamp <= this.keyTimeout
|
| 199 |
+
);
|
| 200 |
+
|
| 201 |
+
// Emergency stop check
|
| 202 |
+
if (activeKeys.includes("Escape")) {
|
| 203 |
+
this.stop();
|
| 204 |
+
return;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
// Calculate target positions based on active keys
|
| 208 |
+
const targetPositions: { [motorName: string]: number } = {};
|
| 209 |
+
|
| 210 |
+
for (const key of activeKeys) {
|
| 211 |
+
const control = this.keyboardControls[key];
|
| 212 |
+
if (!control || control.motor === "emergency_stop") continue;
|
| 213 |
+
|
| 214 |
+
const motorConfig = this.motorConfigs.find(
|
| 215 |
+
(m) => m.name === control.motor
|
| 216 |
+
);
|
| 217 |
+
if (!motorConfig) continue;
|
| 218 |
+
|
| 219 |
+
// Calculate new position
|
| 220 |
+
const currentTarget =
|
| 221 |
+
targetPositions[motorConfig.name] ?? motorConfig.currentPosition;
|
| 222 |
+
const newPosition = currentTarget + control.direction * this.stepSize;
|
| 223 |
+
|
| 224 |
+
// Apply limits
|
| 225 |
+
targetPositions[motorConfig.name] = Math.max(
|
| 226 |
+
motorConfig.minPosition,
|
| 227 |
+
Math.min(motorConfig.maxPosition, newPosition)
|
| 228 |
+
);
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
// Send motor commands and update positions
|
| 232 |
+
Object.entries(targetPositions).forEach(([motorName, targetPosition]) => {
|
| 233 |
+
const motorConfig = this.motorConfigs.find((m) => m.name === motorName);
|
| 234 |
+
if (motorConfig && targetPosition !== motorConfig.currentPosition) {
|
| 235 |
+
writeMotorPosition(
|
| 236 |
+
this.port,
|
| 237 |
+
motorConfig.id,
|
| 238 |
+
Math.round(targetPosition)
|
| 239 |
+
)
|
| 240 |
+
.then(() => {
|
| 241 |
+
motorConfig.currentPosition = targetPosition;
|
| 242 |
+
})
|
| 243 |
+
.catch((error) => {
|
| 244 |
+
console.warn(
|
| 245 |
+
`Failed to write motor ${motorConfig.id} position:`,
|
| 246 |
+
error
|
| 247 |
+
);
|
| 248 |
+
});
|
| 249 |
+
}
|
| 250 |
+
});
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
private mapKeyToName(key: string): string | null {
|
| 254 |
+
// Map stdin input to key names
|
| 255 |
+
const keyMap: { [key: string]: string } = {
|
| 256 |
+
"\u001b[A": "ArrowUp",
|
| 257 |
+
"\u001b[B": "ArrowDown",
|
| 258 |
+
"\u001b[C": "ArrowRight",
|
| 259 |
+
"\u001b[D": "ArrowLeft",
|
| 260 |
+
w: "w",
|
| 261 |
+
s: "s",
|
| 262 |
+
a: "a",
|
| 263 |
+
d: "d",
|
| 264 |
+
q: "q",
|
| 265 |
+
e: "e",
|
| 266 |
+
o: "o",
|
| 267 |
+
c: "c",
|
| 268 |
+
};
|
| 269 |
+
|
| 270 |
+
return keyMap[key] || null;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
private displayControls(): void {
|
| 274 |
+
console.log("\n=== Robot Teleoperation Controls ===");
|
| 275 |
+
console.log("Arrow Keys: Shoulder pan/lift");
|
| 276 |
+
console.log("WASD: Elbow flex / Wrist flex");
|
| 277 |
+
console.log("Q/E: Wrist roll");
|
| 278 |
+
console.log("O/C: Gripper open/close");
|
| 279 |
+
console.log("ESC: Emergency stop");
|
| 280 |
+
console.log("Ctrl+C: Exit");
|
| 281 |
+
console.log("=====================================\n");
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
private buildTeleoperationState(): TeleoperationState {
|
| 285 |
+
return {
|
| 286 |
+
isActive: this.isActive,
|
| 287 |
+
motorConfigs: [...this.motorConfigs],
|
| 288 |
+
lastUpdate: Date.now(),
|
| 289 |
+
keyStates: { ...this.keyStates },
|
| 290 |
+
};
|
| 291 |
+
}
|
| 292 |
+
}
|
packages/node/src/types/calibration.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Calibration-related types for Node.js implementation
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
import type { RobotConnection } from "./robot-connection.js";
|
| 6 |
+
|
| 7 |
+
/**
|
| 8 |
+
* Live calibration data with current positions and ranges
|
| 9 |
+
*/
|
| 10 |
+
export interface LiveCalibrationData {
|
| 11 |
+
[motorName: string]: {
|
| 12 |
+
current: number;
|
| 13 |
+
min: number;
|
| 14 |
+
max: number;
|
| 15 |
+
range: number;
|
| 16 |
+
};
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
/**
|
| 20 |
+
* Config for calibrate function
|
| 21 |
+
*/
|
| 22 |
+
export interface CalibrateConfig {
|
| 23 |
+
robot: RobotConnection;
|
| 24 |
+
onLiveUpdate?: (data: LiveCalibrationData) => void;
|
| 25 |
+
onProgress?: (message: string) => void;
|
| 26 |
+
outputPath?: string; // Node.js specific: custom output path for calibration file
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
/**
|
| 30 |
+
* Calibration results structure - Python lerobot compatible format
|
| 31 |
+
*/
|
| 32 |
+
export interface CalibrationResults {
|
| 33 |
+
[motorName: string]: {
|
| 34 |
+
id: number;
|
| 35 |
+
drive_mode: number;
|
| 36 |
+
homing_offset: number;
|
| 37 |
+
range_min: number;
|
| 38 |
+
range_max: number;
|
| 39 |
+
};
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
/**
|
| 43 |
+
* Calibration process control object
|
| 44 |
+
*/
|
| 45 |
+
export interface CalibrationProcess {
|
| 46 |
+
stop(): void;
|
| 47 |
+
result: Promise<CalibrationResults>;
|
| 48 |
+
}
|
packages/node/src/types/port-discovery.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Port discovery types for Node.js implementation using serialport
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
// Import types needed in this file
|
| 6 |
+
import type {
|
| 7 |
+
RobotConnection,
|
| 8 |
+
RobotConfig,
|
| 9 |
+
SerialPort,
|
| 10 |
+
SerialPortInfo,
|
| 11 |
+
} from "./robot-connection.js";
|
| 12 |
+
|
| 13 |
+
/**
|
| 14 |
+
* Config for findPort function
|
| 15 |
+
*/
|
| 16 |
+
export interface FindPortConfig {
|
| 17 |
+
// Interactive mode: shows Python lerobot compatible prompts
|
| 18 |
+
interactive?: boolean;
|
| 19 |
+
|
| 20 |
+
// Auto-connect mode: provide robot configs to connect to
|
| 21 |
+
robotConfigs?: RobotConfig[];
|
| 22 |
+
|
| 23 |
+
// Callbacks
|
| 24 |
+
onMessage?: (message: string) => void;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
/**
|
| 28 |
+
* Discovered port information (Node.js discovery-only mode)
|
| 29 |
+
*/
|
| 30 |
+
export interface DiscoveredPort {
|
| 31 |
+
path: string; // Serial port path (e.g., "/dev/ttyUSB0", "COM4")
|
| 32 |
+
robotType: "so100_follower" | "so100_leader";
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
/**
|
| 36 |
+
* Process object returned by findPort
|
| 37 |
+
*/
|
| 38 |
+
export interface FindPortProcess {
|
| 39 |
+
// Result promise - Node.js returns discovered ports, user calls connectPort() separately
|
| 40 |
+
result: Promise<DiscoveredPort[]>;
|
| 41 |
+
|
| 42 |
+
// Control
|
| 43 |
+
stop: () => void;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
// Re-export commonly used types for convenience
|
| 47 |
+
export type { RobotConnection, RobotConfig, SerialPort, SerialPortInfo };
|
packages/node/src/types/robot-config.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Shared robot hardware configuration types
|
| 3 |
+
* Used across calibration, teleoperation, and other robot operations
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
/**
|
| 7 |
+
* Keyboard control mapping for teleoperation
|
| 8 |
+
*/
|
| 9 |
+
export interface KeyboardControl {
|
| 10 |
+
motor: string;
|
| 11 |
+
direction: number;
|
| 12 |
+
description: string;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
* Robot hardware configuration interface
|
| 17 |
+
* Defines the contract that all robot configurations must implement
|
| 18 |
+
*/
|
| 19 |
+
export interface RobotHardwareConfig {
|
| 20 |
+
deviceType: string;
|
| 21 |
+
motorNames: string[];
|
| 22 |
+
motorIds: number[];
|
| 23 |
+
driveModes: number[];
|
| 24 |
+
|
| 25 |
+
// Keyboard controls for teleoperation (robot-specific)
|
| 26 |
+
keyboardControls: { [key: string]: KeyboardControl };
|
| 27 |
+
|
| 28 |
+
protocol: {
|
| 29 |
+
resolution: number;
|
| 30 |
+
homingOffsetAddress: number;
|
| 31 |
+
homingOffsetLength: number;
|
| 32 |
+
presentPositionAddress: number;
|
| 33 |
+
presentPositionLength: number;
|
| 34 |
+
minPositionLimitAddress: number;
|
| 35 |
+
minPositionLimitLength: number;
|
| 36 |
+
maxPositionLimitAddress: number;
|
| 37 |
+
maxPositionLimitLength: number;
|
| 38 |
+
signMagnitudeBit: number;
|
| 39 |
+
};
|
| 40 |
+
}
|
packages/node/src/types/robot-connection.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Core robot connection types used across the lerobot.js Node.js library
|
| 3 |
+
* These types are shared between findPort, calibrate, teleoperate, and other modules
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import type { RobotHardwareConfig } from "./robot-config.js";
|
| 7 |
+
|
| 8 |
+
/**
|
| 9 |
+
* Type definitions for Node.js serialport API
|
| 10 |
+
*/
|
| 11 |
+
export interface SerialPort {
|
| 12 |
+
path: string;
|
| 13 |
+
write(buffer: Buffer): Promise<void>;
|
| 14 |
+
read(): Promise<Buffer | null>;
|
| 15 |
+
open(): Promise<void>;
|
| 16 |
+
close(): Promise<void>;
|
| 17 |
+
isOpen: boolean;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
export interface SerialPortInfo {
|
| 21 |
+
path: string;
|
| 22 |
+
manufacturer?: string;
|
| 23 |
+
serialNumber?: string;
|
| 24 |
+
pnpId?: string;
|
| 25 |
+
locationId?: string;
|
| 26 |
+
productId?: string;
|
| 27 |
+
vendorId?: string;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
export interface SerialOptions {
|
| 31 |
+
baudRate: number;
|
| 32 |
+
dataBits?: number;
|
| 33 |
+
stopBits?: number;
|
| 34 |
+
parity?: "none" | "even" | "odd";
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
/**
|
| 38 |
+
* Unified robot connection interface used across all functions
|
| 39 |
+
* This same object works for findPort, calibrate, teleoperate, etc.
|
| 40 |
+
* Includes all fields needed by CLI and other applications
|
| 41 |
+
*/
|
| 42 |
+
export interface RobotConnection {
|
| 43 |
+
port: SerialPort;
|
| 44 |
+
name: string; // Display name for CLI
|
| 45 |
+
isConnected: boolean; // Connection status
|
| 46 |
+
robotType?: "so100_follower" | "so100_leader"; // Optional until user configures
|
| 47 |
+
robotId?: string; // Optional until user configures
|
| 48 |
+
serialNumber: string; // Always required for identification
|
| 49 |
+
error?: string; // Error message if connection failed
|
| 50 |
+
config?: RobotHardwareConfig; // Robot configuration (motorIds, controls, etc.) - set when robotType is configured
|
| 51 |
+
portInfo?: SerialPortInfo; // Node.js serial port information
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
/**
|
| 55 |
+
* Minimal robot config for finding/connecting to specific robots
|
| 56 |
+
*/
|
| 57 |
+
export interface RobotConfig {
|
| 58 |
+
robotType: "so100_follower" | "so100_leader";
|
| 59 |
+
robotId: string;
|
| 60 |
+
serialNumber: string;
|
| 61 |
+
}
|
packages/node/src/types/teleoperation.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Teleoperation-related types for Node.js implementation
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
import type { RobotConnection } from "./robot-connection.js";
|
| 6 |
+
import type { NodeTeleoperator } from "../teleoperators/index.js";
|
| 7 |
+
|
| 8 |
+
/**
|
| 9 |
+
* Motor position and limits for teleoperation
|
| 10 |
+
*/
|
| 11 |
+
export interface MotorConfig {
|
| 12 |
+
id: number;
|
| 13 |
+
name: string;
|
| 14 |
+
currentPosition: number;
|
| 15 |
+
minPosition: number;
|
| 16 |
+
maxPosition: number;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
/**
|
| 20 |
+
* Teleoperation state
|
| 21 |
+
*/
|
| 22 |
+
export interface TeleoperationState {
|
| 23 |
+
isActive: boolean;
|
| 24 |
+
motorConfigs: MotorConfig[];
|
| 25 |
+
lastUpdate: number;
|
| 26 |
+
|
| 27 |
+
// Teleoperator-specific state (optional fields for different types)
|
| 28 |
+
keyStates?: { [key: string]: { pressed: boolean; timestamp: number } }; // keyboard
|
| 29 |
+
leaderPositions?: { [motor: string]: number }; // leader arm
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
/**
|
| 33 |
+
* Teleoperation process control object
|
| 34 |
+
*/
|
| 35 |
+
export interface TeleoperationProcess {
|
| 36 |
+
start(): void;
|
| 37 |
+
stop(): void;
|
| 38 |
+
updateKeyState(key: string, pressed: boolean): void;
|
| 39 |
+
getState(): TeleoperationState;
|
| 40 |
+
teleoperator: NodeTeleoperator;
|
| 41 |
+
disconnect(): Promise<void>;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
/**
|
| 45 |
+
* Base interface for all teleoperator configurations
|
| 46 |
+
*/
|
| 47 |
+
export interface BaseTeleoperatorConfig {
|
| 48 |
+
type: string;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
/**
|
| 52 |
+
* Keyboard teleoperator configuration
|
| 53 |
+
*/
|
| 54 |
+
export interface KeyboardTeleoperatorConfig extends BaseTeleoperatorConfig {
|
| 55 |
+
type: "keyboard";
|
| 56 |
+
stepSize?: number; // Default: KEYBOARD_TELEOPERATOR_DEFAULTS.stepSize
|
| 57 |
+
updateRate?: number; // Default: KEYBOARD_TELEOPERATOR_DEFAULTS.updateRate
|
| 58 |
+
keyTimeout?: number; // Default: KEYBOARD_TELEOPERATOR_DEFAULTS.keyTimeout
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
/**
|
| 62 |
+
* Leader arm teleoperator configuration (future)
|
| 63 |
+
*/
|
| 64 |
+
export interface LeaderArmTeleoperatorConfig extends BaseTeleoperatorConfig {
|
| 65 |
+
type: "so100_leader";
|
| 66 |
+
port: string;
|
| 67 |
+
calibrationData?: any;
|
| 68 |
+
positionSmoothing?: boolean;
|
| 69 |
+
scaleFactor?: number;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
/**
|
| 73 |
+
* Direct teleoperator configuration
|
| 74 |
+
*/
|
| 75 |
+
export interface DirectTeleoperatorConfig extends BaseTeleoperatorConfig {
|
| 76 |
+
type: "direct";
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
/**
|
| 80 |
+
* Union type for all teleoperator configurations
|
| 81 |
+
*/
|
| 82 |
+
export type TeleoperatorConfig =
|
| 83 |
+
| KeyboardTeleoperatorConfig
|
| 84 |
+
| LeaderArmTeleoperatorConfig
|
| 85 |
+
| DirectTeleoperatorConfig;
|
| 86 |
+
|
| 87 |
+
/**
|
| 88 |
+
* Main teleoperation configuration
|
| 89 |
+
*/
|
| 90 |
+
export interface TeleoperateConfig {
|
| 91 |
+
robot: RobotConnection;
|
| 92 |
+
teleop: TeleoperatorConfig;
|
| 93 |
+
calibrationData?: { [motorName: string]: any };
|
| 94 |
+
onStateUpdate?: (state: TeleoperationState) => void;
|
| 95 |
+
}
|
{src/lerobot/node → packages/node/src}/utils/constants.ts
RENAMED
|
@@ -1,15 +1,11 @@
|
|
| 1 |
/**
|
| 2 |
-
* Constants for lerobot
|
| 3 |
-
* Mirrors Python lerobot/common/constants.py
|
| 4 |
*/
|
| 5 |
|
| 6 |
import { homedir } from "os";
|
| 7 |
import { join } from "path";
|
| 8 |
|
| 9 |
-
// Device types
|
| 10 |
-
export const ROBOTS = "robots";
|
| 11 |
-
export const TELEOPERATORS = "teleoperators";
|
| 12 |
-
|
| 13 |
/**
|
| 14 |
* Get HF Home directory
|
| 15 |
* Equivalent to Python's huggingface_hub.constants.HF_HOME
|
|
|
|
| 1 |
/**
|
| 2 |
+
* Constants for @lerobot/node
|
| 3 |
+
* Mirrors Python lerobot/common/constants.py for directory structure compatibility
|
| 4 |
*/
|
| 5 |
|
| 6 |
import { homedir } from "os";
|
| 7 |
import { join } from "path";
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
/**
|
| 10 |
* Get HF Home directory
|
| 11 |
* Equivalent to Python's huggingface_hub.constants.HF_HOME
|
packages/node/src/utils/motor-calibration.ts
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Motor Calibration Utilities
|
| 3 |
+
* Specialized functions for motor calibration procedures
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import { STS3215_PROTOCOL } from "./sts3215-protocol.js";
|
| 7 |
+
import { encodeSignMagnitude } from "./sign-magnitude.js";
|
| 8 |
+
import {
|
| 9 |
+
readAllMotorPositions,
|
| 10 |
+
writeMotorRegister,
|
| 11 |
+
type MotorCommunicationPort,
|
| 12 |
+
} from "./motor-communication.js";
|
| 13 |
+
|
| 14 |
+
/**
|
| 15 |
+
* Reset homing offsets to 0 for all motors
|
| 16 |
+
*/
|
| 17 |
+
export async function resetHomingOffsets(
|
| 18 |
+
port: MotorCommunicationPort,
|
| 19 |
+
motorIds: number[]
|
| 20 |
+
): Promise<void> {
|
| 21 |
+
for (let i = 0; i < motorIds.length; i++) {
|
| 22 |
+
const motorId = motorIds[i];
|
| 23 |
+
|
| 24 |
+
try {
|
| 25 |
+
const packet = new Uint8Array([
|
| 26 |
+
0xff,
|
| 27 |
+
0xff,
|
| 28 |
+
motorId,
|
| 29 |
+
0x05,
|
| 30 |
+
0x03,
|
| 31 |
+
STS3215_PROTOCOL.HOMING_OFFSET_ADDRESS,
|
| 32 |
+
0x00, // Low byte of 0
|
| 33 |
+
0x00, // High byte of 0
|
| 34 |
+
0x00, // Checksum
|
| 35 |
+
]);
|
| 36 |
+
|
| 37 |
+
const checksum =
|
| 38 |
+
~(
|
| 39 |
+
motorId +
|
| 40 |
+
0x05 +
|
| 41 |
+
0x03 +
|
| 42 |
+
STS3215_PROTOCOL.HOMING_OFFSET_ADDRESS +
|
| 43 |
+
0x00 +
|
| 44 |
+
0x00
|
| 45 |
+
) & 0xff;
|
| 46 |
+
packet[8] = checksum;
|
| 47 |
+
|
| 48 |
+
await port.write(packet);
|
| 49 |
+
|
| 50 |
+
try {
|
| 51 |
+
await port.read(200);
|
| 52 |
+
} catch (error) {
|
| 53 |
+
// Silent - response not required
|
| 54 |
+
}
|
| 55 |
+
} catch (error) {
|
| 56 |
+
throw new Error(`Failed to reset homing offset for motor ${motorId}`);
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
/**
|
| 62 |
+
* Write homing offsets to motor registers immediately
|
| 63 |
+
*/
|
| 64 |
+
export async function writeHomingOffsetsToMotors(
|
| 65 |
+
port: MotorCommunicationPort,
|
| 66 |
+
motorIds: number[],
|
| 67 |
+
motorNames: string[],
|
| 68 |
+
homingOffsets: { [motor: string]: number }
|
| 69 |
+
): Promise<void> {
|
| 70 |
+
for (let i = 0; i < motorIds.length; i++) {
|
| 71 |
+
const motorId = motorIds[i];
|
| 72 |
+
const motorName = motorNames[i];
|
| 73 |
+
const homingOffset = homingOffsets[motorName];
|
| 74 |
+
|
| 75 |
+
try {
|
| 76 |
+
const encodedOffset = encodeSignMagnitude(
|
| 77 |
+
homingOffset,
|
| 78 |
+
STS3215_PROTOCOL.SIGN_MAGNITUDE_BIT
|
| 79 |
+
);
|
| 80 |
+
|
| 81 |
+
const packet = new Uint8Array([
|
| 82 |
+
0xff,
|
| 83 |
+
0xff,
|
| 84 |
+
motorId,
|
| 85 |
+
0x05,
|
| 86 |
+
0x03,
|
| 87 |
+
STS3215_PROTOCOL.HOMING_OFFSET_ADDRESS,
|
| 88 |
+
encodedOffset & 0xff,
|
| 89 |
+
(encodedOffset >> 8) & 0xff,
|
| 90 |
+
0x00,
|
| 91 |
+
]);
|
| 92 |
+
|
| 93 |
+
const checksum =
|
| 94 |
+
~(
|
| 95 |
+
motorId +
|
| 96 |
+
0x05 +
|
| 97 |
+
0x03 +
|
| 98 |
+
STS3215_PROTOCOL.HOMING_OFFSET_ADDRESS +
|
| 99 |
+
(encodedOffset & 0xff) +
|
| 100 |
+
((encodedOffset >> 8) & 0xff)
|
| 101 |
+
) & 0xff;
|
| 102 |
+
packet[8] = checksum;
|
| 103 |
+
|
| 104 |
+
await port.write(packet);
|
| 105 |
+
|
| 106 |
+
try {
|
| 107 |
+
await port.read(200);
|
| 108 |
+
} catch (error) {
|
| 109 |
+
// Silent - response not required
|
| 110 |
+
}
|
| 111 |
+
} catch (error) {
|
| 112 |
+
throw new Error(`Failed to write homing offset for ${motorName}`);
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
/**
|
| 118 |
+
* Set homing offsets with immediate writing
|
| 119 |
+
*/
|
| 120 |
+
export async function setHomingOffsets(
|
| 121 |
+
port: MotorCommunicationPort,
|
| 122 |
+
motorIds: number[],
|
| 123 |
+
motorNames: string[]
|
| 124 |
+
): Promise<{ [motor: string]: number }> {
|
| 125 |
+
// Reset existing homing offsets to 0 first
|
| 126 |
+
await resetHomingOffsets(port, motorIds);
|
| 127 |
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second instead of 100ms
|
| 128 |
+
|
| 129 |
+
// Flush any cached position readings first
|
| 130 |
+
await readAllMotorPositions(port, motorIds); // Dummy read to flush cache
|
| 131 |
+
await new Promise((resolve) => setTimeout(resolve, 200)); // Small additional wait
|
| 132 |
+
|
| 133 |
+
// Read positions (which should now be true physical positions)
|
| 134 |
+
const currentPositions = await readAllMotorPositions(port, motorIds);
|
| 135 |
+
|
| 136 |
+
const homingOffsets: { [motor: string]: number } = {};
|
| 137 |
+
const halfTurn = Math.floor((STS3215_PROTOCOL.RESOLUTION - 1) / 2);
|
| 138 |
+
|
| 139 |
+
for (let i = 0; i < motorNames.length; i++) {
|
| 140 |
+
const motorName = motorNames[i];
|
| 141 |
+
const position = currentPositions[i];
|
| 142 |
+
const calculatedOffset = position - halfTurn;
|
| 143 |
+
homingOffsets[motorName] = calculatedOffset;
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
// Write homing offsets to motors immediately
|
| 147 |
+
await writeHomingOffsetsToMotors(port, motorIds, motorNames, homingOffsets);
|
| 148 |
+
|
| 149 |
+
return homingOffsets;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
/**
|
| 153 |
+
* Write hardware position limits to motors
|
| 154 |
+
*/
|
| 155 |
+
export async function writeHardwarePositionLimits(
|
| 156 |
+
port: MotorCommunicationPort,
|
| 157 |
+
motorIds: number[],
|
| 158 |
+
motorNames: string[],
|
| 159 |
+
rangeMins: { [motor: string]: number },
|
| 160 |
+
rangeMaxes: { [motor: string]: number }
|
| 161 |
+
): Promise<void> {
|
| 162 |
+
for (let i = 0; i < motorIds.length; i++) {
|
| 163 |
+
const motorId = motorIds[i];
|
| 164 |
+
const motorName = motorNames[i];
|
| 165 |
+
const minLimit = rangeMins[motorName];
|
| 166 |
+
const maxLimit = rangeMaxes[motorName];
|
| 167 |
+
|
| 168 |
+
try {
|
| 169 |
+
// Write Min_Position_Limit register
|
| 170 |
+
await writeMotorRegister(
|
| 171 |
+
port,
|
| 172 |
+
motorId,
|
| 173 |
+
STS3215_PROTOCOL.MIN_POSITION_LIMIT_ADDRESS,
|
| 174 |
+
minLimit
|
| 175 |
+
);
|
| 176 |
+
|
| 177 |
+
// Write Max_Position_Limit register
|
| 178 |
+
await writeMotorRegister(
|
| 179 |
+
port,
|
| 180 |
+
motorId,
|
| 181 |
+
STS3215_PROTOCOL.MAX_POSITION_LIMIT_ADDRESS,
|
| 182 |
+
maxLimit
|
| 183 |
+
);
|
| 184 |
+
} catch (error) {
|
| 185 |
+
throw new Error(`Failed to write position limits for ${motorName}`);
|
| 186 |
+
}
|
| 187 |
+
}
|
| 188 |
+
}
|