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 |
+
}
|