NERDDISCO commited on
Commit
540cfa6
·
0 Parent(s):

first commit

Browse files
.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2025 Tim Pietrusky
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
docs/conventions.md ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LeRobot.js Conventions
2
+
3
+ ## Project Overview
4
+
5
+ **lerobot.js** is a TypeScript/JavaScript implementation of Hugging Face's [lerobot](https://github.com/huggingface/lerobot) robotics library. Our goal is to bring state-of-the-art AI for real-world robotics directly to the JavaScript ecosystem, enabling robot control without any Python dependencies.
6
+
7
+ ### Vision Statement
8
+
9
+ > Lower the barrier to entry for robotics by making cutting-edge robotic AI accessible through JavaScript, the world's most widely used programming language.
10
+
11
+ ## Project Goals
12
+
13
+ ### Primary Objectives
14
+
15
+ 1. **Native JavaScript/TypeScript Implementation**: Complete robotics stack running purely in JS/TS
16
+ 2. **Feature Parity**: Implement core functionality from the original Python lerobot
17
+ 3. **Web-First Design**: Enable robotics applications to run in browsers, Edge devices, and Node.js
18
+ 4. **Real-World Robot Control**: Direct hardware interface without Python bridge
19
+ 5. **Hugging Face Integration**: Seamless model and dataset loading from HF Hub
20
+
21
+ ### Core Features to Implement
22
+
23
+ - **Pretrained Models**: Load and run robotics policies (ACT, Diffusion, TDMPC, VQ-BeT)
24
+ - **Dataset Management**: LeRobotDataset format with HF Hub integration
25
+ - **Simulation Environments**: Browser-based robotics simulations
26
+ - **Real Robot Support**: Hardware interfaces for motors, cameras, sensors
27
+ - **Training Infrastructure**: Policy training and evaluation tools
28
+ - **Visualization Tools**: Dataset and robot state visualization
29
+
30
+ ## Technical Foundation
31
+
32
+ ### Core Stack
33
+
34
+ - **Runtime**: Node.js 18+ / Modern Browsers
35
+ - **Language**: TypeScript with strict type checking
36
+ - **Build Tool**: Vite (development and production builds)
37
+ - **Package Manager**: pnpm
38
+ - **Module System**: ES Modules
39
+ - **Target**: ES2020
40
+
41
+ ## Architecture Principles
42
+
43
+ ### 1. Python lerobot Faithfulness (Primary Principle)
44
+
45
+ **lerobot.js must maintain UX/API compatibility with Python lerobot**
46
+
47
+ - **Identical Commands**: `npx lerobot find-port` matches `python -m lerobot.find_port`
48
+ - **Same Terminology**: Use "MotorsBus", not "robot arms" - keep Python's exact wording
49
+ - **Matching Output**: Error messages, prompts, and flow identical to Python version
50
+ - **Familiar Workflows**: Python lerobot users should feel immediately at home
51
+ - **No "Improvements"**: Resist urge to add features/UX that Python version doesn't have
52
+
53
+ > **Why?** Users are already trained on Python lerobot. Our goal is seamless migration to TypeScript, not learning a new tool.
54
+
55
+ ### 2. Modular Design
56
+
57
+ ```
58
+ lerobot/
59
+ ├── common/
60
+ │ ├── datasets/ # Dataset loading and management
61
+ │ ├── envs/ # Simulation environments
62
+ │ ├── policies/ # AI policies and models
63
+ │ ├── devices/ # Hardware device interfaces
64
+ │ └── utils/ # Shared utilities
65
+ ├── core/ # Core robotics primitives
66
+ ├── web/ # Browser-specific implementations
67
+ └── node/ # Node.js-specific implementations
68
+ ```
69
+
70
+ ### 3. Platform Abstraction
71
+
72
+ - **Universal Core**: Platform-agnostic robotics logic
73
+ - **Web Adapters**: Browser-specific implementations (WebGL, WebAssembly, WebUSB)
74
+ - **Node Adapters**: Node.js implementations (native modules, serial ports)
75
+
76
+ ### 4. Progressive Enhancement
77
+
78
+ - **Core Functionality**: Works everywhere (basic policy inference)
79
+ - **Enhanced Features**: Leverage platform capabilities (GPU acceleration, hardware access)
80
+ - **Premium Features**: Advanced capabilities (real-time training, complex simulations)
81
+
82
+ ## Development Standards
83
+
84
+ ### Code Style
85
+
86
+ - **Formatting**: Prettier with default settings
87
+ - **Linting**: ESLint with TypeScript recommended rules
88
+ - **Naming**:
89
+ - camelCase for variables/functions
90
+ - PascalCase for classes/types
91
+ - snake_case for file names (following lerobot convention)
92
+ - **File Structure**: Feature-based organization with index.ts barrels
93
+
94
+ ### TypeScript Standards
95
+
96
+ - **Strict Mode**: All strict compiler options enabled
97
+ - **Type Safety**: Prefer types over interfaces for data structures
98
+ - **Generics**: Use generics for reusable components
99
+ - **Error Handling**: Use Result<T, E> pattern for recoverable errors
100
+
101
+ ### Implementation Philosophy
102
+
103
+ - **Python First**: When in doubt, check how Python lerobot does it
104
+ - **Port, Don't Innovate**: Direct ports are better than clever improvements
105
+ - **User Expectations**: Maintain the exact experience Python users expect
106
+ - **Terminology Consistency**: Use Python lerobot's exact naming and messaging
107
+
108
+ ### Development Process
109
+
110
+ - **Python Reference**: Always check Python lerobot implementation first
111
+ - **UX Matching**: Test that commands, outputs, and workflows match exactly
112
+ - **User Story Validation**: Validate against real Python lerobot users
113
+
114
+ ### Testing Strategy
115
+
116
+ - **Unit Tests**: Vitest for individual functions and classes
117
+ - **Integration Tests**: Test component interactions
118
+ - **E2E Tests**: Playwright for full workflow testing
119
+ - **Hardware Tests**: Mock/stub hardware interfaces for CI
120
+ - **UX Compatibility Tests**: Verify outputs match Python version
121
+
122
+ ## Package Structure
123
+
124
+ ### NPM Package Name
125
+
126
+ - **Public Package**: `lerobot` (on npm)
127
+ - **Development Name**: `lerobot.js` (GitHub repository)
128
+
129
+ ## Dependencies Strategy
130
+
131
+ ### Core Dependencies
132
+
133
+ - **ML Inference**: ONNX.js for model execution (browser + Node.js)
134
+ - **Tensor Operations**: Custom lightweight tensor lib for data manipulation
135
+ - **Math**: Custom math utilities for robotics
136
+ - **Networking**: Fetch API (universal)
137
+ - **File I/O**: Platform-appropriate abstractions
138
+
139
+ ### Optional Enhanced Dependencies
140
+
141
+ - **3D Graphics**: Three.js for simulation and visualization
142
+ - **Hardware**: Platform-specific libraries for device access
143
+ - **Development**: Vitest, ESLint, Prettier
docs/getting_started_with_so100.md ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## install lerobot
2
+
3
+ - clone the repo: git clone https://github.com/huggingface/lerobot.git
4
+ - go into the dir: cd lerobot
5
+ - setup venv with your favorite tool, i use uv: uv venv --python 3.10
6
+ - activate venv: windows: .venv\Scripts\activate
7
+ - install dependencies: uv pip install -e ".[feetech]"
8
+ (we install feetech here as that is needed to talk with the motors that the so100 is using)
9
+
10
+ ## identify USB ports
11
+
12
+ - connect usb / power
13
+ - run `python lerobot/find_port.py`
14
+
15
+ ```
16
+ python lerobot/find_port.py
17
+ Finding all available ports for the MotorsBus.
18
+ Ports before disconnecting: ['COM3', 'COM4']
19
+ Remove the USB cable from your MotorsBus and press Enter when done.
20
+
21
+ The port of this MotorsBus is 'COM3'
22
+ Reconnect the USB cable.
23
+ ```
24
+
25
+ - the port it will show you is the one from the disconnected arm
26
+ - in my case on windows the follower arm is COM4 and the leader arm is COM3 (but this could change, as the port mappings on windows are not fixed)
27
+
28
+ ## check motor ids
29
+
30
+ **Important: Always do this first!** This checks if your robot motors are already configured correctly. If they are, you can skip the tedious setup process entirely.
31
+
32
+ **What are motor IDs?** Each motor needs a unique ID number (1, 2, 3, 4, 5, 6) so the computer can talk to them individually. Brand new motors usually all have the same default ID (1), which doesn't work.
33
+
34
+ ### check follower arm
35
+
36
+ ```
37
+ python -m lerobot.check_motors --robot.port=COM4
38
+ ```
39
+
40
+ ### check leader arm
41
+
42
+ ```
43
+ python -m lerobot.check_motors --teleop.port=COM3
44
+ ```
45
+
46
+ **If you see this - you're lucky! Skip to calibration:**
47
+
48
+ ```
49
+ 🎉 PERFECT! This arm is correctly configured:
50
+ ✅ All 6 motors found: [1, 2, 3, 4, 5, 6]
51
+ ✅ Correct baudrate: 1000000
52
+
53
+ ✅ This arm is ready for calibration!
54
+ ```
55
+
56
+ **If you see this - continue to "setup motors" below:**
57
+
58
+ ```
59
+ ⚠️ This arm needs motor ID setup:
60
+ Expected IDs: [1, 2, 3, 4, 5, 6]
61
+ Found IDs: [1, 1, 1, 1, 1, 1]
62
+ Duplicate IDs: [1] (likely all motors have ID=1)
63
+ ```
64
+
65
+ ## setup motors
66
+
67
+ **⚠️ Only do this section if the motor check above showed your motors need setup!**
68
+
69
+ This is a one-time process where you connect each motor individually to assign unique ID numbers. It's tedious but only needed once - the ID gets permanently stored in each motor's memory.
70
+
71
+ **Why is this needed?** Motors come from the factory with the same default ID (usually 1). The computer can't tell them apart, so we need to give each motor a unique number.
72
+
73
+ **⚠️ Important safety notes:**
74
+
75
+ - Always power down (unplug power + USB) when connecting/disconnecting motors
76
+ - Connect only ONE motor at a time during this process
77
+ - The motor gears should be removed from the leader arm before this step
78
+
79
+ ### follower arm
80
+
81
+ ```
82
+ python -m lerobot.setup_motors --robot.type=so100_follower --robot.port=COM4
83
+ ```
84
+
85
+ The script will guide you through connecting each motor one by one. Follow the prompts carefully.
86
+
87
+ ### leader arm
88
+
89
+ ```
90
+ python -m lerobot.setup_motors --teleop.type=so100_leader --teleop.port=COM3
91
+ ```
92
+
93
+ Same process as the follower arm.
94
+
95
+ **After setup:** Run the motor check commands again to verify everything worked correctly. You should now see the "PERFECT!" message.
96
+
97
+ ## calibrate
98
+
99
+ **What is calibration?** This teaches both robot arms to have the same understanding of joint positions. When both arms are in the same physical pose, they should report the same position values. This is crucial for the leader arm to properly control the follower arm.
100
+
101
+ **Why is this needed?** Even though the arms are identical, small manufacturing differences mean their position sensors might read slightly different values for the same physical position. Calibration fixes this.
102
+
103
+ **Important:** Make sure both arms have all motors properly connected (daisy-chained) before calibrating. The motor setup process has you disconnect motors individually, but for calibration you need the full arm assembled.
104
+
105
+ > **💡 Don't be intimidated by calibration!**
106
+ >
107
+ > **It's really simple:** You just manually move each joint to the correct position - no special technique required! The calibration process is just:
108
+ >
109
+ > 1. Start from the neutral position (standard in robotics)
110
+ > 2. Manually move each motor/joint to where it needs to be
111
+ > 3. Move them one at a time at whatever pace feels comfortable
112
+ > 4. The robot learns from the positions you set
113
+ >
114
+ > **That's it!** You're just physically positioning the joints - the software handles all the complex stuff automatically.
115
+
116
+ ### calibrate follower arm
117
+
118
+ **Step 1:** Start the calibration script:
119
+
120
+ ```
121
+ python -m lerobot.calibrate --robot.type=so100_follower --robot.port=COM4 --robot.id=my_follower_arm
122
+ ```
123
+
124
+ **Step 2:** Follow the script's prompts:
125
+
126
+ 1. The script will ask you to **move the arm to the middle position**. This should be a **neutral pose** where:
127
+
128
+ - **Shoulder pan (base, joint 1):** 0° - Point the arm straight forward along the +X axis (not twisted left or right)
129
+ - **Shoulder lift (joint 2):** ~45° - Lift the upper arm segment upward at roughly 45 degrees from horizontal
130
+ - **Elbow (joint 3):** ~90° - Bend the elbow to form an "L" shape, with the forearm perpendicular to the upper arm
131
+ - **Wrist flex (joint 4):** 0° - Keep the wrist straight, aligned with the forearm
132
+ - **Wrist roll (joint 5):** 0° - Keep the gripper pointing straight (not twisted around the forearm axis)
133
+ - **Gripper (joint 6):** Fully closed position (mechanical hard stop)
134
+
135
+ **Technical reference:** This is similar to a "home position" or "zero configuration" used in robot calibration - a repeatable, neutral pose that avoids joint limits and singularities. The arm should form clear geometric references (forward direction, L-shaped elbow) that are easy to reproduce.
136
+
137
+ **💡 Tip:** Once you get the arm in the correct position, consider taking a photo for future reference!
138
+
139
+ 2. The script will ask you to **move joints through their full range** - gently move each joint from its furthest position in one direction to its furthest position in the other direction (the joint will stop when it hits its physical limit). Do this for all joints while the script records, then press Enter when done
140
+ 3. The script will **save the calibration automatically**
141
+
142
+ ### calibrate leader arm
143
+
144
+ **Step 1:** Start the calibration script:
145
+
146
+ ```
147
+ python -m lerobot.calibrate --teleop.type=so100_leader --teleop.port=COM3 --teleop.id=my_leader_arm
148
+ ```
149
+
150
+ **Step 2:** Follow the script's prompts:
151
+
152
+ 1. The script will ask you to **move the arm to the middle position**. This should be a **neutral pose** where:
153
+
154
+ - **Shoulder pan (base, joint 1):** 0° - Point the arm straight forward along the +X axis (not twisted left or right)
155
+ - **Shoulder lift (joint 2):** ~45° - Lift the upper arm segment upward at roughly 45 degrees from horizontal
156
+ - **Elbow (joint 3):** ~90° - Bend the elbow to form an "L" shape, with the forearm perpendicular to the upper arm
157
+ - **Wrist flex (joint 4):** 0° - Keep the wrist straight, aligned with the forearm
158
+ - **Wrist roll (joint 5):** 0° - Keep the gripper pointing straight (not twisted around the forearm axis)
159
+ - **Gripper (joint 6):** Fully closed position (mechanical hard stop)
160
+
161
+ **Technical reference:** This is similar to a "home position" or "zero configuration" used in robot calibration - a repeatable, neutral pose that avoids joint limits and singularities.
162
+
163
+ 2. The script will ask you to **move joints through their full range** - gently move each joint from its furthest position in one direction to its furthest position in the other direction (the joint will stop when it hits its physical limit). Do this for all joints while the script records, then press Enter when done
164
+ 3. The script will **save the calibration automatically**
165
+
166
+ **✅ After both arms are calibrated, you're ready for teleoperation!**
167
+
168
+ ## test teleoperation
169
+
170
+ Now test that your leader arm can control the follower arm:
171
+
172
+ ```bash
173
+ python -m lerobot.teleoperate --robot.type=so100_follower --robot.port=COM4 --robot.id=my_follower_arm --teleop.type=so100_leader --teleop.port=COM3 --teleop.id=my_leader_arm
174
+ ```
175
+
176
+ **What should happen:**
177
+
178
+ - Both arms should connect automatically
179
+ - When you move the leader arm (manually), the follower arm should copy the movements
180
+ - Press `Ctrl+C` to stop teleoperation
181
+
182
+ **If something goes wrong:**
183
+
184
+ - Check your COM ports are correct
185
+ - Make sure both arms are powered on
186
+ - Verify the cables are properly connected
187
+
188
+ ## record demonstrations (optional)
189
+
190
+ Once teleoperation works, you can record demonstrations for training a robot learning policy.
191
+
192
+ **For example, to record 10 episodes of a simple task:**
193
+
194
+ ```bash
195
+ python -m lerobot.record --robot.type=so100_follower --robot.port=COM4 --robot.id=my_follower_arm --teleop.type=so100_leader --teleop.port=COM3 --teleop.id=my_leader_arm --dataset-name=my_first_dataset --num-episodes=10 --task="Pick up the red block and place it in the box"
196
+ ```
197
+
198
+ **What this does:**
199
+
200
+ - Records your demonstrations using teleoperation
201
+ - Saves data to train a neural network later
202
+ - Each "episode" is one complete demonstration of the task
203
+
204
+ ## next steps
205
+
206
+ ✅ **You now have working SO-100 robot arms!**
207
+
208
+ **To continue with robot learning:**
209
+
210
+ - Follow the [Getting Started with Real-World Robots](https://huggingface.co/docs/lerobot/getting_started_real_world_robot) tutorial
211
+ - Learn how to train policies and run them autonomously
212
+ - Join the [Discord community](https://discord.com/invite/s3KuuzsPFb) for help and discussions
213
+
214
+ **Common next steps:**
215
+
216
+ 1. **Add cameras** for vision-based tasks
217
+ 2. **Record more complex demonstrations**
218
+ 3. **Train neural network policies**
219
+ 4. **Run policies autonomously**
docs/planning/001_find_usb_ports.md ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # User Story 001: Find USB Ports
2
+
3
+ ## Story
4
+
5
+ **As a** robotics developer setting up SO-100 robot arms
6
+ **I want** to identify which USB ports my robot arms are connected to
7
+ **So that** I can configure my robot arms without manually testing each port
8
+
9
+ ## Background
10
+
11
+ When setting up SO-100 robot arms (or any robotics hardware), users need to identify which USB/serial ports correspond to their robot controllers. The Python lerobot provides a `find_port.py` script that:
12
+
13
+ 1. Lists all available serial ports
14
+ 2. Asks the user to disconnect a specific device
15
+ 3. Detects which port disappeared
16
+ 4. Identifies that port as belonging to the disconnected device
17
+
18
+ We need to replicate this functionality in Node.js for lerobot.js.
19
+
20
+ ## Acceptance Criteria
21
+
22
+ ### Core Functionality
23
+
24
+ - [ ] **Port Discovery**: List all available serial/USB ports on the system
25
+ - [ ] **Interactive Detection**: Guide user through disconnect/reconnect process
26
+ - [ ] **Port Identification**: Detect which port corresponds to which robot arm
27
+ - [ ] **Cross-Platform**: Work on Windows, macOS, and Linux
28
+ - [ ] **CLI Interface**: Provide `npx lerobot find-port` command identical to Python version
29
+
30
+ ### User Experience
31
+
32
+ - [ ] **Clear Instructions**: Provide step-by-step guidance to user
33
+ - [ ] **Error Handling**: Handle cases where no ports are found or detection fails
34
+ - [ ] **Progress Feedback**: Show current status during detection process
35
+ - [ ] **Results Display**: Clearly show which port belongs to which device
36
+
37
+ ### Technical Requirements
38
+
39
+ - [ ] **Node.js Native**: Use Node.js serial port libraries (no Python dependencies)
40
+ - [ ] **TypeScript**: Fully typed implementation following our conventions
41
+ - [ ] **CLI Tool**: Executable via `npx lerobot find-port` (matching Python version)
42
+ - [ ] **snake_case**: File naming follows lerobot conventions
43
+
44
+ ## Expected User Flow
45
+
46
+ ```bash
47
+ $ npx lerobot find-port
48
+
49
+ Finding all available ports for the MotorsBus.
50
+ Ports before disconnecting: ['COM3', 'COM4']
51
+ Remove the USB cable from your MotorsBus and press Enter when done.
52
+
53
+ The port of this MotorsBus is 'COM3'
54
+ Reconnect the USB cable.
55
+ ```
56
+
57
+ **For multiple arms, run separately:**
58
+
59
+ ```bash
60
+ # First arm
61
+ $ npx lerobot find-port
62
+ # Follow prompts...
63
+
64
+ # Second arm
65
+ $ npx lerobot find-port
66
+ # Follow prompts...
67
+ ```
68
+
69
+ ## Implementation Details
70
+
71
+ ### File Structure
72
+
73
+ ```
74
+ src/lerobot/
75
+ └── find_port.ts # Direct port of Python script
76
+
77
+ src/cli/
78
+ └── index.ts # Main CLI entry point with find-port command
79
+ ```
80
+
81
+ ### Key Dependencies
82
+
83
+ - **serialport**: For cross-platform serial port detection and management
84
+ - **readline**: Node.js built-in for simple input() equivalent
85
+
86
+ ### Core Functions to Implement
87
+
88
+ ```typescript
89
+ // find_port.ts (matching Python naming)
90
+ async function findAvailablePorts(): Promise<string[]>;
91
+ async function findPort(): Promise<void>; // Main interactive function
92
+ ```
93
+
94
+ ### Technical Considerations
95
+
96
+ #### Cross-Platform Serial Ports
97
+
98
+ - **Windows**: COM ports (COM1, COM2, etc.)
99
+ - **macOS**: /dev/cu._ or /dev/tty._
100
+ - **Linux**: /dev/ttyUSB*, /dev/ttyACM*
101
+
102
+ #### Error Scenarios to Handle
103
+
104
+ - No serial ports detected
105
+ - Multiple ports disconnected simultaneously
106
+ - Port permissions issues
107
+ - User cancels detection process
108
+ - Same port reconnected to different device
109
+
110
+ #### Performance & UX
111
+
112
+ - Fast port scanning (< 1 second)
113
+ - Clear progress indicators
114
+ - Timeout handling for user input
115
+ - Graceful interrupt handling (Ctrl+C)
116
+
117
+ ## Definition of Done
118
+
119
+ - [ ] **Functional**: Successfully identifies robot arm ports on Windows/macOS/Linux
120
+ - [ ] **Tested**: Unit tests for core logic, integration tests with mock serial ports
121
+ - [ ] **Documented**: README section explaining usage
122
+ - [ ] **CLI Ready**: Installable and runnable as CLI tool
123
+ - [ ] **Type Safe**: Full TypeScript coverage with strict mode
124
+ - [ ] **Follows Conventions**: snake_case files, proper architecture
index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Vite + TS</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
package.json ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "lerobot",
3
+ "version": "0.0.0",
4
+ "description": "State-of-the-art AI for real-world robotics in JS",
5
+ "type": "module",
6
+ "bin": {
7
+ "lerobot": "./dist/cli/index.js"
8
+ },
9
+ "main": "./dist/lerobot/find_port.js",
10
+ "files": [
11
+ "dist/**/*",
12
+ "README.md"
13
+ ],
14
+ "keywords": [
15
+ "robotics",
16
+ "ai",
17
+ "machine-learning",
18
+ "typescript",
19
+ "lerobot"
20
+ ],
21
+ "scripts": {
22
+ "dev": "vite",
23
+ "build": "pnpm run build:cli",
24
+ "build:cli": "tsc --project tsconfig.cli.json",
25
+ "build:web": "tsc && vite build",
26
+ "preview": "vite preview",
27
+ "cli:find-port": "tsx src/cli/index.ts find-port",
28
+ "prepublishOnly": "pnpm run build"
29
+ },
30
+ "dependencies": {
31
+ "serialport": "^12.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "typescript": "~5.8.3",
35
+ "vite": "^6.3.5",
36
+ "tsx": "^4.19.2",
37
+ "@types/node": "^22.10.5"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/timpietrusky/lerobot.js"
42
+ },
43
+ "license": "Apache-2.0",
44
+ "author": "Tim Pietrusky",
45
+ "engines": {
46
+ "node": ">=18.0.0"
47
+ }
48
+ }
pnpm-lock.yaml ADDED
@@ -0,0 +1,813 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: ^22.10.5
17
+ version: 22.15.31
18
+ tsx:
19
+ specifier: ^4.19.2
20
+ version: 4.20.3
21
+ typescript:
22
+ specifier: ~5.8.3
23
+ version: 5.8.3
24
+ vite:
25
+ specifier: ^6.3.5
26
+ version: 6.3.5(@types/[email protected])([email protected])
27
+
28
+ packages:
29
+
30
+ '@esbuild/[email protected]':
31
+ resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
32
+ engines: {node: '>=18'}
33
+ cpu: [ppc64]
34
+ os: [aix]
35
+
36
+ '@esbuild/[email protected]':
37
+ resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==}
38
+ engines: {node: '>=18'}
39
+ cpu: [arm64]
40
+ os: [android]
41
+
42
+ '@esbuild/[email protected]':
43
+ resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==}
44
+ engines: {node: '>=18'}
45
+ cpu: [arm]
46
+ os: [android]
47
+
48
+ '@esbuild/[email protected]':
49
+ resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==}
50
+ engines: {node: '>=18'}
51
+ cpu: [x64]
52
+ os: [android]
53
+
54
+ '@esbuild/[email protected]':
55
+ resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==}
56
+ engines: {node: '>=18'}
57
+ cpu: [arm64]
58
+ os: [darwin]
59
+
60
+ '@esbuild/[email protected]':
61
+ resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==}
62
+ engines: {node: '>=18'}
63
+ cpu: [x64]
64
+ os: [darwin]
65
+
66
+ '@esbuild/[email protected]':
67
+ resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==}
68
+ engines: {node: '>=18'}
69
+ cpu: [arm64]
70
+ os: [freebsd]
71
+
72
+ '@esbuild/[email protected]':
73
+ resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==}
74
+ engines: {node: '>=18'}
75
+ cpu: [x64]
76
+ os: [freebsd]
77
+
78
+ '@esbuild/[email protected]':
79
+ resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==}
80
+ engines: {node: '>=18'}
81
+ cpu: [arm64]
82
+ os: [linux]
83
+
84
+ '@esbuild/[email protected]':
85
+ resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==}
86
+ engines: {node: '>=18'}
87
+ cpu: [arm]
88
+ os: [linux]
89
+
90
+ '@esbuild/[email protected]':
91
+ resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==}
92
+ engines: {node: '>=18'}
93
+ cpu: [ia32]
94
+ os: [linux]
95
+
96
+ '@esbuild/[email protected]':
97
+ resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==}
98
+ engines: {node: '>=18'}
99
+ cpu: [loong64]
100
+ os: [linux]
101
+
102
+ '@esbuild/[email protected]':
103
+ resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==}
104
+ engines: {node: '>=18'}
105
+ cpu: [mips64el]
106
+ os: [linux]
107
+
108
+ '@esbuild/[email protected]':
109
+ resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==}
110
+ engines: {node: '>=18'}
111
+ cpu: [ppc64]
112
+ os: [linux]
113
+
114
+ '@esbuild/[email protected]':
115
+ resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==}
116
+ engines: {node: '>=18'}
117
+ cpu: [riscv64]
118
+ os: [linux]
119
+
120
+ '@esbuild/[email protected]':
121
+ resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==}
122
+ engines: {node: '>=18'}
123
+ cpu: [s390x]
124
+ os: [linux]
125
+
126
+ '@esbuild/[email protected]':
127
+ resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==}
128
+ engines: {node: '>=18'}
129
+ cpu: [x64]
130
+ os: [linux]
131
+
132
+ '@esbuild/[email protected]':
133
+ resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==}
134
+ engines: {node: '>=18'}
135
+ cpu: [arm64]
136
+ os: [netbsd]
137
+
138
+ '@esbuild/[email protected]':
139
+ resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==}
140
+ engines: {node: '>=18'}
141
+ cpu: [x64]
142
+ os: [netbsd]
143
+
144
+ '@esbuild/[email protected]':
145
+ resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==}
146
+ engines: {node: '>=18'}
147
+ cpu: [arm64]
148
+ os: [openbsd]
149
+
150
+ '@esbuild/[email protected]':
151
+ resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==}
152
+ engines: {node: '>=18'}
153
+ cpu: [x64]
154
+ os: [openbsd]
155
+
156
+ '@esbuild/[email protected]':
157
+ resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==}
158
+ engines: {node: '>=18'}
159
+ cpu: [x64]
160
+ os: [sunos]
161
+
162
+ '@esbuild/[email protected]':
163
+ resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==}
164
+ engines: {node: '>=18'}
165
+ cpu: [arm64]
166
+ os: [win32]
167
+
168
+ '@esbuild/[email protected]':
169
+ resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==}
170
+ engines: {node: '>=18'}
171
+ cpu: [ia32]
172
+ os: [win32]
173
+
174
+ '@esbuild/[email protected]':
175
+ resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==}
176
+ engines: {node: '>=18'}
177
+ cpu: [x64]
178
+ os: [win32]
179
+
180
+ '@rollup/[email protected]':
181
+ resolution: {integrity: sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==}
182
+ cpu: [arm]
183
+ os: [android]
184
+
185
+ '@rollup/[email protected]':
186
+ resolution: {integrity: sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==}
187
+ cpu: [arm64]
188
+ os: [android]
189
+
190
+ '@rollup/[email protected]':
191
+ resolution: {integrity: sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==}
192
+ cpu: [arm64]
193
+ os: [darwin]
194
+
195
+ '@rollup/[email protected]':
196
+ resolution: {integrity: sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==}
197
+ cpu: [x64]
198
+ os: [darwin]
199
+
200
+ '@rollup/[email protected]':
201
+ resolution: {integrity: sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==}
202
+ cpu: [arm64]
203
+ os: [freebsd]
204
+
205
+ '@rollup/[email protected]':
206
+ resolution: {integrity: sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==}
207
+ cpu: [x64]
208
+ os: [freebsd]
209
+
210
+ '@rollup/[email protected]':
211
+ resolution: {integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==}
212
+ cpu: [arm]
213
+ os: [linux]
214
+
215
+ '@rollup/[email protected]':
216
+ resolution: {integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==}
217
+ cpu: [arm]
218
+ os: [linux]
219
+
220
+ '@rollup/[email protected]':
221
+ resolution: {integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==}
222
+ cpu: [arm64]
223
+ os: [linux]
224
+
225
+ '@rollup/[email protected]':
226
+ resolution: {integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==}
227
+ cpu: [arm64]
228
+ os: [linux]
229
+
230
+ '@rollup/[email protected]':
231
+ resolution: {integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==}
232
+ cpu: [loong64]
233
+ os: [linux]
234
+
235
+ '@rollup/[email protected]':
236
+ resolution: {integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==}
237
+ cpu: [ppc64]
238
+ os: [linux]
239
+
240
+ '@rollup/[email protected]':
241
+ resolution: {integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==}
242
+ cpu: [riscv64]
243
+ os: [linux]
244
+
245
+ '@rollup/[email protected]':
246
+ resolution: {integrity: sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==}
247
+ cpu: [riscv64]
248
+ os: [linux]
249
+
250
+ '@rollup/[email protected]':
251
+ resolution: {integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==}
252
+ cpu: [s390x]
253
+ os: [linux]
254
+
255
+ '@rollup/[email protected]':
256
+ resolution: {integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==}
257
+ cpu: [x64]
258
+ os: [linux]
259
+
260
+ '@rollup/[email protected]':
261
+ resolution: {integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==}
262
+ cpu: [x64]
263
+ os: [linux]
264
+
265
+ '@rollup/[email protected]':
266
+ resolution: {integrity: sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==}
267
+ cpu: [arm64]
268
+ os: [win32]
269
+
270
+ '@rollup/[email protected]':
271
+ resolution: {integrity: sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==}
272
+ cpu: [ia32]
273
+ os: [win32]
274
+
275
+ '@rollup/[email protected]':
276
+ resolution: {integrity: sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==}
277
+ cpu: [x64]
278
+ os: [win32]
279
+
280
+ '@serialport/[email protected]':
281
+ resolution: {integrity: sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==}
282
+ engines: {node: '>=12.0.0'}
283
+
284
+ '@serialport/[email protected]':
285
+ resolution: {integrity: sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==}
286
+ engines: {node: '>=16.0.0'}
287
+
288
+ '@serialport/[email protected]':
289
+ resolution: {integrity: sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==}
290
+ engines: {node: ^12.22 || ^14.13 || >=16}
291
+
292
+ '@serialport/[email protected]':
293
+ resolution: {integrity: sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==}
294
+ engines: {node: '>=12.0.0'}
295
+
296
+ '@serialport/[email protected]':
297
+ resolution: {integrity: sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==}
298
+ engines: {node: '>=12.0.0'}
299
+
300
+ '@serialport/[email protected]':
301
+ resolution: {integrity: sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==}
302
+ engines: {node: '>=12.0.0'}
303
+
304
+ '@serialport/[email protected]':
305
+ resolution: {integrity: sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==}
306
+ engines: {node: '>=12.0.0'}
307
+
308
+ '@serialport/[email protected]':
309
+ resolution: {integrity: sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==}
310
+ engines: {node: '>=12.0.0'}
311
+
312
+ '@serialport/[email protected]':
313
+ resolution: {integrity: sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==}
314
+ engines: {node: '>=8.6.0'}
315
+
316
+ '@serialport/[email protected]':
317
+ resolution: {integrity: sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==}
318
+ engines: {node: '>=12.0.0'}
319
+
320
+ '@serialport/[email protected]':
321
+ resolution: {integrity: sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==}
322
+ engines: {node: '>=12.0.0'}
323
+
324
+ '@serialport/[email protected]':
325
+ resolution: {integrity: sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==}
326
+ engines: {node: '>=12.0.0'}
327
+
328
+ '@serialport/[email protected]':
329
+ resolution: {integrity: sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==}
330
+ engines: {node: '>=12.0.0'}
331
+
332
+ '@serialport/[email protected]':
333
+ resolution: {integrity: sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==}
334
+ engines: {node: '>=12.0.0'}
335
+
336
+ '@serialport/[email protected]':
337
+ resolution: {integrity: sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==}
338
+ engines: {node: '>=12.0.0'}
339
+
340
+ '@serialport/[email protected]':
341
+ resolution: {integrity: sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==}
342
+ engines: {node: '>=12.0.0'}
343
+
344
+ '@types/[email protected]':
345
+ resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
346
+
347
+ '@types/[email protected]':
348
+ resolution: {integrity: sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==}
349
+
350
351
+ resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
352
+ engines: {node: '>=6.0'}
353
+ peerDependencies:
354
+ supports-color: '*'
355
+ peerDependenciesMeta:
356
+ supports-color:
357
+ optional: true
358
+
359
360
+ resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==}
361
+ engines: {node: '>=18'}
362
+ hasBin: true
363
+
364
365
+ resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
366
+ peerDependencies:
367
+ picomatch: ^3 || ^4
368
+ peerDependenciesMeta:
369
+ picomatch:
370
+ optional: true
371
+
372
373
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
374
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
375
+ os: [darwin]
376
+
377
378
+ resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
379
+
380
381
+ resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
382
+
383
384
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
385
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
386
+ hasBin: true
387
+
388
389
+ resolution: {integrity: sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==}
390
+
391
392
+ resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
393
+ hasBin: true
394
+
395
396
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
397
+
398
399
+ resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
400
+ engines: {node: '>=12'}
401
+
402
403
+ resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==}
404
+ engines: {node: ^10 || ^12 || >=14}
405
+
406
407
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
408
+
409
410
+ resolution: {integrity: sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==}
411
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
412
+ hasBin: true
413
+
414
415
+ resolution: {integrity: sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==}
416
+ engines: {node: '>=16.0.0'}
417
+
418
419
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
420
+ engines: {node: '>=0.10.0'}
421
+
422
423
+ resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
424
+ engines: {node: '>=12.0.0'}
425
+
426
427
+ resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==}
428
+ engines: {node: '>=18.0.0'}
429
+ hasBin: true
430
+
431
432
+ resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
433
+ engines: {node: '>=14.17'}
434
+ hasBin: true
435
+
436
437
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
438
+
439
440
+ resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==}
441
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
442
+ hasBin: true
443
+ peerDependencies:
444
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
445
+ jiti: '>=1.21.0'
446
+ less: '*'
447
+ lightningcss: ^1.21.0
448
+ sass: '*'
449
+ sass-embedded: '*'
450
+ stylus: '*'
451
+ sugarss: '*'
452
+ terser: ^5.16.0
453
+ tsx: ^4.8.1
454
+ yaml: ^2.4.2
455
+ peerDependenciesMeta:
456
+ '@types/node':
457
+ optional: true
458
+ jiti:
459
+ optional: true
460
+ less:
461
+ optional: true
462
+ lightningcss:
463
+ optional: true
464
+ sass:
465
+ optional: true
466
+ sass-embedded:
467
+ optional: true
468
+ stylus:
469
+ optional: true
470
+ sugarss:
471
+ optional: true
472
+ terser:
473
+ optional: true
474
+ tsx:
475
+ optional: true
476
+ yaml:
477
+ optional: true
478
+
479
+ snapshots:
480
+
481
+ '@esbuild/[email protected]':
482
+ optional: true
483
+
484
+ '@esbuild/[email protected]':
485
+ optional: true
486
+
487
+ '@esbuild/[email protected]':
488
+ optional: true
489
+
490
+ '@esbuild/[email protected]':
491
+ optional: true
492
+
493
+ '@esbuild/[email protected]':
494
+ optional: true
495
+
496
+ '@esbuild/[email protected]':
497
+ optional: true
498
+
499
+ '@esbuild/[email protected]':
500
+ optional: true
501
+
502
+ '@esbuild/[email protected]':
503
+ optional: true
504
+
505
+ '@esbuild/[email protected]':
506
+ optional: true
507
+
508
+ '@esbuild/[email protected]':
509
+ optional: true
510
+
511
+ '@esbuild/[email protected]':
512
+ optional: true
513
+
514
+ '@esbuild/[email protected]':
515
+ optional: true
516
+
517
+ '@esbuild/[email protected]':
518
+ optional: true
519
+
520
+ '@esbuild/[email protected]':
521
+ optional: true
522
+
523
+ '@esbuild/[email protected]':
524
+ optional: true
525
+
526
+ '@esbuild/[email protected]':
527
+ optional: true
528
+
529
+ '@esbuild/[email protected]':
530
+ optional: true
531
+
532
+ '@esbuild/[email protected]':
533
+ optional: true
534
+
535
+ '@esbuild/[email protected]':
536
+ optional: true
537
+
538
+ '@esbuild/[email protected]':
539
+ optional: true
540
+
541
+ '@esbuild/[email protected]':
542
+ optional: true
543
+
544
+ '@esbuild/[email protected]':
545
+ optional: true
546
+
547
+ '@esbuild/[email protected]':
548
+ optional: true
549
+
550
+ '@esbuild/[email protected]':
551
+ optional: true
552
+
553
+ '@esbuild/[email protected]':
554
+ optional: true
555
+
556
+ '@rollup/[email protected]':
557
+ optional: true
558
+
559
+ '@rollup/[email protected]':
560
+ optional: true
561
+
562
+ '@rollup/[email protected]':
563
+ optional: true
564
+
565
+ '@rollup/[email protected]':
566
+ optional: true
567
+
568
+ '@rollup/[email protected]':
569
+ optional: true
570
+
571
+ '@rollup/[email protected]':
572
+ optional: true
573
+
574
+ '@rollup/[email protected]':
575
+ optional: true
576
+
577
+ '@rollup/[email protected]':
578
+ optional: true
579
+
580
+ '@rollup/[email protected]':
581
+ optional: true
582
+
583
+ '@rollup/[email protected]':
584
+ optional: true
585
+
586
+ '@rollup/[email protected]':
587
+ optional: true
588
+
589
+ '@rollup/[email protected]':
590
+ optional: true
591
+
592
+ '@rollup/[email protected]':
593
+ optional: true
594
+
595
+ '@rollup/[email protected]':
596
+ optional: true
597
+
598
+ '@rollup/[email protected]':
599
+ optional: true
600
+
601
+ '@rollup/[email protected]':
602
+ optional: true
603
+
604
+ '@rollup/[email protected]':
605
+ optional: true
606
+
607
+ '@rollup/[email protected]':
608
+ optional: true
609
+
610
+ '@rollup/[email protected]':
611
+ optional: true
612
+
613
+ '@rollup/[email protected]':
614
+ optional: true
615
+
616
+ '@serialport/[email protected]':
617
+ dependencies:
618
+ '@serialport/bindings-interface': 1.2.2
619
+ debug: 4.3.4
620
+ transitivePeerDependencies:
621
+ - supports-color
622
+
623
+ '@serialport/[email protected]':
624
+ dependencies:
625
+ '@serialport/bindings-interface': 1.2.2
626
+ '@serialport/parser-readline': 11.0.0
627
+ debug: 4.3.4
628
+ node-addon-api: 7.0.0
629
+ node-gyp-build: 4.6.0
630
+ transitivePeerDependencies:
631
+ - supports-color
632
+
633
+ '@serialport/[email protected]': {}
634
+
635
+ '@serialport/[email protected]': {}
636
+
637
+ '@serialport/[email protected]': {}
638
+
639
+ '@serialport/[email protected]': {}
640
+
641
+ '@serialport/[email protected]': {}
642
+
643
+ '@serialport/[email protected]': {}
644
+
645
+ '@serialport/[email protected]': {}
646
+
647
+ '@serialport/[email protected]':
648
+ dependencies:
649
+ '@serialport/parser-delimiter': 11.0.0
650
+
651
+ '@serialport/[email protected]':
652
+ dependencies:
653
+ '@serialport/parser-delimiter': 12.0.0
654
+
655
+ '@serialport/[email protected]': {}
656
+
657
+ '@serialport/[email protected]': {}
658
+
659
+ '@serialport/[email protected]': {}
660
+
661
+ '@serialport/[email protected]': {}
662
+
663
+ '@serialport/[email protected]':
664
+ dependencies:
665
+ '@serialport/bindings-interface': 1.2.2
666
+ debug: 4.3.4
667
+ transitivePeerDependencies:
668
+ - supports-color
669
+
670
+ '@types/[email protected]': {}
671
+
672
+ '@types/[email protected]':
673
+ dependencies:
674
+ undici-types: 6.21.0
675
+
676
677
+ dependencies:
678
+ ms: 2.1.2
679
+
680
681
+ optionalDependencies:
682
+ '@esbuild/aix-ppc64': 0.25.5
683
+ '@esbuild/android-arm': 0.25.5
684
+ '@esbuild/android-arm64': 0.25.5
685
+ '@esbuild/android-x64': 0.25.5
686
+ '@esbuild/darwin-arm64': 0.25.5
687
+ '@esbuild/darwin-x64': 0.25.5
688
+ '@esbuild/freebsd-arm64': 0.25.5
689
+ '@esbuild/freebsd-x64': 0.25.5
690
+ '@esbuild/linux-arm': 0.25.5
691
+ '@esbuild/linux-arm64': 0.25.5
692
+ '@esbuild/linux-ia32': 0.25.5
693
+ '@esbuild/linux-loong64': 0.25.5
694
+ '@esbuild/linux-mips64el': 0.25.5
695
+ '@esbuild/linux-ppc64': 0.25.5
696
+ '@esbuild/linux-riscv64': 0.25.5
697
+ '@esbuild/linux-s390x': 0.25.5
698
+ '@esbuild/linux-x64': 0.25.5
699
+ '@esbuild/netbsd-arm64': 0.25.5
700
+ '@esbuild/netbsd-x64': 0.25.5
701
+ '@esbuild/openbsd-arm64': 0.25.5
702
+ '@esbuild/openbsd-x64': 0.25.5
703
+ '@esbuild/sunos-x64': 0.25.5
704
+ '@esbuild/win32-arm64': 0.25.5
705
+ '@esbuild/win32-ia32': 0.25.5
706
+ '@esbuild/win32-x64': 0.25.5
707
+
708
709
+ optionalDependencies:
710
+ picomatch: 4.0.2
711
+
712
713
+ optional: true
714
+
715
716
+ dependencies:
717
+ resolve-pkg-maps: 1.0.0
718
+
719
720
+
721
722
+
723
724
+
725
726
+
727
728
+
729
730
+
731
732
+ dependencies:
733
+ nanoid: 3.3.11
734
+ picocolors: 1.1.1
735
+ source-map-js: 1.2.1
736
+
737
738
+
739
740
+ dependencies:
741
+ '@types/estree': 1.0.7
742
+ optionalDependencies:
743
+ '@rollup/rollup-android-arm-eabi': 4.43.0
744
+ '@rollup/rollup-android-arm64': 4.43.0
745
+ '@rollup/rollup-darwin-arm64': 4.43.0
746
+ '@rollup/rollup-darwin-x64': 4.43.0
747
+ '@rollup/rollup-freebsd-arm64': 4.43.0
748
+ '@rollup/rollup-freebsd-x64': 4.43.0
749
+ '@rollup/rollup-linux-arm-gnueabihf': 4.43.0
750
+ '@rollup/rollup-linux-arm-musleabihf': 4.43.0
751
+ '@rollup/rollup-linux-arm64-gnu': 4.43.0
752
+ '@rollup/rollup-linux-arm64-musl': 4.43.0
753
+ '@rollup/rollup-linux-loongarch64-gnu': 4.43.0
754
+ '@rollup/rollup-linux-powerpc64le-gnu': 4.43.0
755
+ '@rollup/rollup-linux-riscv64-gnu': 4.43.0
756
+ '@rollup/rollup-linux-riscv64-musl': 4.43.0
757
+ '@rollup/rollup-linux-s390x-gnu': 4.43.0
758
+ '@rollup/rollup-linux-x64-gnu': 4.43.0
759
+ '@rollup/rollup-linux-x64-musl': 4.43.0
760
+ '@rollup/rollup-win32-arm64-msvc': 4.43.0
761
+ '@rollup/rollup-win32-ia32-msvc': 4.43.0
762
+ '@rollup/rollup-win32-x64-msvc': 4.43.0
763
+ fsevents: 2.3.3
764
+
765
766
+ dependencies:
767
+ '@serialport/binding-mock': 10.2.2
768
+ '@serialport/bindings-cpp': 12.0.1
769
+ '@serialport/parser-byte-length': 12.0.0
770
+ '@serialport/parser-cctalk': 12.0.0
771
+ '@serialport/parser-delimiter': 12.0.0
772
+ '@serialport/parser-inter-byte-timeout': 12.0.0
773
+ '@serialport/parser-packet-length': 12.0.0
774
+ '@serialport/parser-readline': 12.0.0
775
+ '@serialport/parser-ready': 12.0.0
776
+ '@serialport/parser-regex': 12.0.0
777
+ '@serialport/parser-slip-encoder': 12.0.0
778
+ '@serialport/parser-spacepacket': 12.0.0
779
+ '@serialport/stream': 12.0.0
780
+ debug: 4.3.4
781
+ transitivePeerDependencies:
782
+ - supports-color
783
+
784
785
+
786
787
+ dependencies:
788
+ fdir: 6.4.6([email protected])
789
+ picomatch: 4.0.2
790
+
791
792
+ dependencies:
793
+ esbuild: 0.25.5
794
+ get-tsconfig: 4.10.1
795
+ optionalDependencies:
796
+ fsevents: 2.3.3
797
+
798
799
+
800
801
+
802
803
+ dependencies:
804
+ esbuild: 0.25.5
805
+ fdir: 6.4.6([email protected])
806
+ picomatch: 4.0.2
807
+ postcss: 8.5.5
808
+ rollup: 4.43.0
809
+ tinyglobby: 0.2.14
810
+ optionalDependencies:
811
+ '@types/node': 22.15.31
812
+ fsevents: 2.3.3
813
+ tsx: 4.20.3
public/vite.svg ADDED
src/cli/index.ts ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * lerobot.js CLI
5
+ *
6
+ * Provides command-line interface for lerobot functionality
7
+ * Maintains compatibility with Python lerobot command structure
8
+ */
9
+
10
+ import { findPort } from "../lerobot/find_port.js";
11
+
12
+ /**
13
+ * Show usage information
14
+ */
15
+ function showUsage() {
16
+ console.log("Usage: lerobot <command>");
17
+ console.log("");
18
+ console.log("Commands:");
19
+ console.log(
20
+ " find-port Find the USB port associated with your MotorsBus"
21
+ );
22
+ console.log("");
23
+ console.log("Examples:");
24
+ console.log(" lerobot find-port");
25
+ console.log("");
26
+ }
27
+
28
+ /**
29
+ * Main CLI function
30
+ */
31
+ async function main() {
32
+ const args = process.argv.slice(2);
33
+
34
+ if (args.length === 0) {
35
+ showUsage();
36
+ process.exit(1);
37
+ }
38
+
39
+ const command = args[0];
40
+
41
+ try {
42
+ switch (command) {
43
+ case "find-port":
44
+ await findPort();
45
+ break;
46
+
47
+ case "help":
48
+ case "--help":
49
+ case "-h":
50
+ showUsage();
51
+ break;
52
+
53
+ default:
54
+ console.error(`Unknown command: ${command}`);
55
+ showUsage();
56
+ process.exit(1);
57
+ }
58
+ } catch (error) {
59
+ console.error("Error:", error instanceof Error ? error.message : error);
60
+ process.exit(1);
61
+ }
62
+ }
63
+
64
+ // Run the CLI
65
+ main();
src/counter.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ export function setupCounter(element: HTMLButtonElement) {
2
+ let counter = 0
3
+ const setCounter = (count: number) => {
4
+ counter = count
5
+ element.innerHTML = `count is ${counter}`
6
+ }
7
+ element.addEventListener('click', () => setCounter(counter + 1))
8
+ setCounter(0)
9
+ }
src/lerobot/find_port.ts ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Helper to find the USB port associated with your MotorsBus.
3
+ *
4
+ * Direct port of Python lerobot find_port.py
5
+ *
6
+ * Example:
7
+ * ```
8
+ * npx lerobot find-port
9
+ * ```
10
+ */
11
+
12
+ import { SerialPort } from "serialport";
13
+ import { createInterface } from "readline";
14
+ import { platform } from "os";
15
+ import { readdir } from "fs/promises";
16
+ import { join } from "path";
17
+
18
+ /**
19
+ * Find all available serial ports on the system
20
+ * Mirrors Python's find_available_ports() function
21
+ */
22
+ async function findAvailablePorts(): Promise<string[]> {
23
+ if (platform() === "win32") {
24
+ // List COM ports using serialport library (equivalent to pyserial)
25
+ const ports = await SerialPort.list();
26
+ return ports.map((port) => port.path);
27
+ } else {
28
+ // List /dev/tty* ports for Unix-based systems (Linux/macOS)
29
+ try {
30
+ const devFiles = await readdir("/dev");
31
+ const ttyPorts = devFiles
32
+ .filter((file) => file.startsWith("tty"))
33
+ .map((file) => join("/dev", file));
34
+ return ttyPorts;
35
+ } catch (error) {
36
+ // Fallback to serialport library if /dev reading fails
37
+ const ports = await SerialPort.list();
38
+ return ports.map((port) => port.path);
39
+ }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Create readline interface for user input
45
+ * Equivalent to Python's input() function
46
+ */
47
+ function createReadlineInterface() {
48
+ return createInterface({
49
+ input: process.stdin,
50
+ output: process.stdout,
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Prompt user for input and wait for response
56
+ * Equivalent to Python's input() function
57
+ */
58
+ function waitForInput(prompt: string = ""): Promise<string> {
59
+ const rl = createReadlineInterface();
60
+ return new Promise((resolve) => {
61
+ if (prompt) {
62
+ process.stdout.write(prompt);
63
+ }
64
+ rl.on("line", (answer) => {
65
+ rl.close();
66
+ resolve(answer);
67
+ });
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Sleep for specified milliseconds
73
+ * Equivalent to Python's time.sleep()
74
+ */
75
+ function sleep(ms: number): Promise<void> {
76
+ return new Promise((resolve) => setTimeout(resolve, ms));
77
+ }
78
+
79
+ /**
80
+ * Main find port function - direct port of Python find_port()
81
+ * Maintains identical UX and messaging
82
+ */
83
+ export async function findPort(): Promise<void> {
84
+ console.log("Finding all available ports for the MotorsBus.");
85
+
86
+ const portsBefore = await findAvailablePorts();
87
+ console.log("Ports before disconnecting:", portsBefore);
88
+
89
+ console.log(
90
+ "Remove the USB cable from your MotorsBus and press Enter when done."
91
+ );
92
+ await waitForInput();
93
+
94
+ // Allow some time for port to be released (equivalent to Python's time.sleep(0.5))
95
+ await sleep(500);
96
+
97
+ const portsAfter = await findAvailablePorts();
98
+ const portsDiff = portsBefore.filter((port) => !portsAfter.includes(port));
99
+
100
+ if (portsDiff.length === 1) {
101
+ const port = portsDiff[0];
102
+ console.log(`The port of this MotorsBus is '${port}'`);
103
+ console.log("Reconnect the USB cable.");
104
+ } else if (portsDiff.length === 0) {
105
+ throw new Error(
106
+ `Could not detect the port. No difference was found (${JSON.stringify(
107
+ portsDiff
108
+ )}).`
109
+ );
110
+ } else {
111
+ throw new Error(
112
+ `Could not detect the port. More than one port was found (${JSON.stringify(
113
+ portsDiff
114
+ )}).`
115
+ );
116
+ }
117
+ }
118
+
119
+ /**
120
+ * CLI entry point when called directly
121
+ */
122
+ if (import.meta.url === `file://${process.argv[1]}`) {
123
+ findPort().catch((error) => {
124
+ console.error(error.message);
125
+ process.exit(1);
126
+ });
127
+ }
src/main.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import './style.css'
2
+ import typescriptLogo from './typescript.svg'
3
+ import viteLogo from '/vite.svg'
4
+ import { setupCounter } from './counter.ts'
5
+
6
+ document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
7
+ <div>
8
+ <a href="https://vite.dev" target="_blank">
9
+ <img src="${viteLogo}" class="logo" alt="Vite logo" />
10
+ </a>
11
+ <a href="https://www.typescriptlang.org/" target="_blank">
12
+ <img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" />
13
+ </a>
14
+ <h1>Vite + TypeScript</h1>
15
+ <div class="card">
16
+ <button id="counter" type="button"></button>
17
+ </div>
18
+ <p class="read-the-docs">
19
+ Click on the Vite and TypeScript logos to learn more
20
+ </p>
21
+ </div>
22
+ `
23
+
24
+ setupCounter(document.querySelector<HTMLButtonElement>('#counter')!)
src/style.css ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3
+ line-height: 1.5;
4
+ font-weight: 400;
5
+
6
+ color-scheme: light dark;
7
+ color: rgba(255, 255, 255, 0.87);
8
+ background-color: #242424;
9
+
10
+ font-synthesis: none;
11
+ text-rendering: optimizeLegibility;
12
+ -webkit-font-smoothing: antialiased;
13
+ -moz-osx-font-smoothing: grayscale;
14
+ }
15
+
16
+ a {
17
+ font-weight: 500;
18
+ color: #646cff;
19
+ text-decoration: inherit;
20
+ }
21
+ a:hover {
22
+ color: #535bf2;
23
+ }
24
+
25
+ body {
26
+ margin: 0;
27
+ display: flex;
28
+ place-items: center;
29
+ min-width: 320px;
30
+ min-height: 100vh;
31
+ }
32
+
33
+ h1 {
34
+ font-size: 3.2em;
35
+ line-height: 1.1;
36
+ }
37
+
38
+ #app {
39
+ max-width: 1280px;
40
+ margin: 0 auto;
41
+ padding: 2rem;
42
+ text-align: center;
43
+ }
44
+
45
+ .logo {
46
+ height: 6em;
47
+ padding: 1.5em;
48
+ will-change: filter;
49
+ transition: filter 300ms;
50
+ }
51
+ .logo:hover {
52
+ filter: drop-shadow(0 0 2em #646cffaa);
53
+ }
54
+ .logo.vanilla:hover {
55
+ filter: drop-shadow(0 0 2em #3178c6aa);
56
+ }
57
+
58
+ .card {
59
+ padding: 2em;
60
+ }
61
+
62
+ .read-the-docs {
63
+ color: #888;
64
+ }
65
+
66
+ button {
67
+ border-radius: 8px;
68
+ border: 1px solid transparent;
69
+ padding: 0.6em 1.2em;
70
+ font-size: 1em;
71
+ font-weight: 500;
72
+ font-family: inherit;
73
+ background-color: #1a1a1a;
74
+ cursor: pointer;
75
+ transition: border-color 0.25s;
76
+ }
77
+ button:hover {
78
+ border-color: #646cff;
79
+ }
80
+ button:focus,
81
+ button:focus-visible {
82
+ outline: 4px auto -webkit-focus-ring-color;
83
+ }
84
+
85
+ @media (prefers-color-scheme: light) {
86
+ :root {
87
+ color: #213547;
88
+ background-color: #ffffff;
89
+ }
90
+ a:hover {
91
+ color: #747bff;
92
+ }
93
+ button {
94
+ background-color: #f9f9f9;
95
+ }
96
+ }
src/typescript.svg ADDED
src/vite-env.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ /// <reference types="vite/client" />
tsconfig.cli.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "declaration": true,
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true
13
+ },
14
+ "include": ["src/lerobot/**/*", "src/cli/**/*"],
15
+ "exclude": [
16
+ "src/main.ts",
17
+ "src/counter.ts",
18
+ "src/style.css",
19
+ "src/vite-env.d.ts"
20
+ ]
21
+ }
tsconfig.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "strict": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "erasableSyntaxOnly": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "noUncheckedSideEffectImports": true
23
+ },
24
+ "include": ["src"]
25
+ }