replx 1.22.2__tar.gz → 1.25.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of replx might be problematic. Click here for more details.
- replx-1.25.0/PKG-INFO +73 -0
- replx-1.25.0/README.md +46 -0
- {replx-1.22.2 → replx-1.25.0}/pyproject.toml +1 -1
- {replx-1.22.2 → replx-1.25.0}/replx/__init__.py +1 -1
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/__init__.py +10 -3
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/protocol.py +9 -14
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/connection_manager.py +11 -10
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/core.py +6 -2
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/handlers/exec.py +1 -1
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/udp.py +20 -12
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/exec.py +1 -1
- {replx-1.22.2 → replx-1.25.0}/replx/protocol/repl.py +45 -46
- {replx-1.22.2 → replx-1.25.0}/replx/protocol/storage.py +2 -1
- {replx-1.22.2 → replx-1.25.0}/replx/terminal.py +1 -2
- replx-1.25.0/replx.egg-info/PKG-INFO +73 -0
- replx-1.22.2/PKG-INFO +0 -118
- replx-1.22.2/README.md +0 -91
- replx-1.22.2/replx.egg-info/PKG-INFO +0 -118
- {replx-1.22.2 → replx-1.25.0}/LICENSE +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/__main__.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/__init__.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/client/__init__.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/client/core.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/client/session.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/__init__.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/__main__.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/command_dispatcher.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/handlers/__init__.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/handlers/filesystem.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/handlers/i2c.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/handlers/repl.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/handlers/session.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/handlers/spi.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/handlers/transfer.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/handlers/uart.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/agent/server/session_manager.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/app.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/__init__.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/_common.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/adc.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/ble.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/device.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/file.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/firmware.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/gpio.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/i2c.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/i2s.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/mip.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/package.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/pwm.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/spi.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/uart.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/utility.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/wifi.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/commands/ws2812.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/config.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/connection.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/helpers/__init__.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/helpers/compiler.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/helpers/environment.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/helpers/output.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/helpers/registry.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/helpers/scanner.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/helpers/store.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/cli/helpers/updater.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/commands.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/protocol/__init__.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/transport/__init__.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/transport/serial.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/_thread.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/aioble/__init__.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/aioble/l2cap.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/array.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/asyncio/__init__.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/binascii.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/bluetooth.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/builtins.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/cmath.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/collections.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/cryptolib.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/deflate.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/errno.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/framebuf.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/gc.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/hashlib.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/heapq.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/io.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/json.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/lwip.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/machine.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/math.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/micropython.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/mip/__init__.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/network.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/ntptime.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/os.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/platform.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/random.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/re.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/requests/__init__.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/select.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/socket.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/ssl.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/struct.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/sys.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/time.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/tls.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/uasyncio.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/uctypes.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/ulab.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/urequests.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm/vfs.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/binascii.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/digi/__init__.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/digi/ble.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/errno.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/hashlib.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/io.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/json.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/machine.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/math.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/micropython.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/network.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/os.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/select.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/socket.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/ssl.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/struct.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/sys.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/time.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/ubinascii.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/ucryptolib.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/uerrno.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/uhashlib.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/uio.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/ujson.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/umachine.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/uos.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/uselect.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/usocket.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/ussl.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/ustruct.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/utime.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/xbee/__init__.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/xbee/modem_status.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/comm_separate/EFR32MG/xbee/relay.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/core/ESP32/aioespnow.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/core/ESP32/esp.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/core/ESP32/esp32.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/core/ESP32/espnow.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/core/MIMXRT1062DVJ6A/mimxrt.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/typehints/core/RP2350/rp2.pyi +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/utils/__init__.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/utils/constants.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/utils/device_info.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx/utils/exceptions.py +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx.egg-info/SOURCES.txt +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx.egg-info/dependency_links.txt +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx.egg-info/entry_points.txt +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx.egg-info/requires.txt +0 -0
- {replx-1.22.2 → replx-1.25.0}/replx.egg-info/top_level.txt +0 -0
- {replx-1.22.2 → replx-1.25.0}/setup.cfg +0 -0
- {replx-1.22.2 → replx-1.25.0}/test/test_hardware_commands.py +0 -0
replx-1.25.0/PKG-INFO
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: replx
|
|
3
|
+
Version: 1.25.0
|
|
4
|
+
Summary: replx is a fast, modern MicroPython CLI: turbo REPL, robust file sync (put/get), project install, mpy-cross integration, and smart port discovery.
|
|
5
|
+
Author-email: "chanmin.park" <devcamp@planxlab.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://planxlab.com
|
|
8
|
+
Project-URL: Repository, https://github.com/PlanXLab/replx
|
|
9
|
+
Project-URL: Issues, https://github.com/PlanXLab/replx/issues
|
|
10
|
+
Keywords: micropython,repl,serial,pyserial,typer,mpy-cross,deploy,gpio,i2c,spi,uart,pwm,adc,i2s,ws2812
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
16
|
+
Classifier: Topic :: System :: Hardware :: Universal Serial Bus (USB)
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: click>=8.4.2
|
|
21
|
+
Requires-Dist: typer>=0.27.0
|
|
22
|
+
Requires-Dist: rich>=13.0
|
|
23
|
+
Requires-Dist: pyserial>=3.5
|
|
24
|
+
Requires-Dist: mpy-cross>=1.27.0
|
|
25
|
+
Requires-Dist: psutil>=7.2.2
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# replx
|
|
29
|
+
|
|
30
|
+
[](https://pypi.org/project/replx/)
|
|
31
|
+
[](https://www.python.org/downloads/)
|
|
32
|
+
[](https://opensource.org/licenses/MIT)
|
|
33
|
+
|
|
34
|
+
**A cross-platform command-line toolkit for MicroPython development.** replx runs a shared local agent so multiple terminals can work with multiple boards while reusing connections and session state.
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- Works on Windows, Linux, and macOS.
|
|
39
|
+
- Connect to multiple boards from multiple terminals, with per-session foreground and background devices.
|
|
40
|
+
- Discover boards, configure a workspace default, and set up VS Code MicroPython support.
|
|
41
|
+
- Run scripts and snippets, use the interactive REPL, and sync files and directories.
|
|
42
|
+
- Search and install packages with `mip` or `pkg`; compile `.py` files to `.mpy`.
|
|
43
|
+
- Work with GPIO, PWM, ADC, UART, SPI, I2C, I2S, BLE, and WS2812 devices (availability depends on board and firmware).
|
|
44
|
+
- Use MicroPython raw-paste for code submissions of 4 KiB or larger when supported; batched file uploads keep the standard raw path for throughput.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
python -m pip install replx
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This installs both `replx` and the `rx` command.
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
replx scan
|
|
58
|
+
replx COM4 setup
|
|
59
|
+
replx status
|
|
60
|
+
replx -c "print('Hello from MicroPython')"
|
|
61
|
+
replx run main.py
|
|
62
|
+
replx repl
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Replace `COM4` with the board's serial port, such as `/dev/ttyUSB0` on Linux or `/dev/cu.usbmodem14101` on macOS. After `setup`, the configured board can usually be targeted without repeating the port; specify a port when working with another board.
|
|
66
|
+
|
|
67
|
+
## Documentation
|
|
68
|
+
|
|
69
|
+
This PyPI page is a short overview and quick start. See the [full command reference and user manual on GitHub](https://github.com/PlanXLab/replx) for connection/session behavior, commands, options, examples, and troubleshooting.
|
|
70
|
+
|
|
71
|
+
- [GitHub repository](https://github.com/PlanXLab/replx)
|
|
72
|
+
- [Report an issue](https://github.com/PlanXLab/replx/issues)
|
|
73
|
+
- [Website](https://planxlab.com)
|
replx-1.25.0/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# replx
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/replx/)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
**A cross-platform command-line toolkit for MicroPython development.** replx runs a shared local agent so multiple terminals can work with multiple boards while reusing connections and session state.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Works on Windows, Linux, and macOS.
|
|
12
|
+
- Connect to multiple boards from multiple terminals, with per-session foreground and background devices.
|
|
13
|
+
- Discover boards, configure a workspace default, and set up VS Code MicroPython support.
|
|
14
|
+
- Run scripts and snippets, use the interactive REPL, and sync files and directories.
|
|
15
|
+
- Search and install packages with `mip` or `pkg`; compile `.py` files to `.mpy`.
|
|
16
|
+
- Work with GPIO, PWM, ADC, UART, SPI, I2C, I2S, BLE, and WS2812 devices (availability depends on board and firmware).
|
|
17
|
+
- Use MicroPython raw-paste for code submissions of 4 KiB or larger when supported; batched file uploads keep the standard raw path for throughput.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
python -m pip install replx
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This installs both `replx` and the `rx` command.
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
replx scan
|
|
31
|
+
replx COM4 setup
|
|
32
|
+
replx status
|
|
33
|
+
replx -c "print('Hello from MicroPython')"
|
|
34
|
+
replx run main.py
|
|
35
|
+
replx repl
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Replace `COM4` with the board's serial port, such as `/dev/ttyUSB0` on Linux or `/dev/cu.usbmodem14101` on macOS. After `setup`, the configured board can usually be targeted without repeating the port; specify a port when working with another board.
|
|
39
|
+
|
|
40
|
+
## Documentation
|
|
41
|
+
|
|
42
|
+
This PyPI page is a short overview and quick start. See the [full command reference and user manual on GitHub](https://github.com/PlanXLab/replx) for connection/session behavior, commands, options, examples, and troubleshooting.
|
|
43
|
+
|
|
44
|
+
- [GitHub repository](https://github.com/PlanXLab/replx)
|
|
45
|
+
- [Report an issue](https://github.com/PlanXLab/replx/issues)
|
|
46
|
+
- [Website](https://planxlab.com)
|
|
@@ -34,7 +34,7 @@ dependencies = [
|
|
|
34
34
|
[project.optional-dependencies]
|
|
35
35
|
|
|
36
36
|
[project.urls]
|
|
37
|
-
Homepage = "https://
|
|
37
|
+
Homepage = "https://planxlab.com"
|
|
38
38
|
Repository = "https://github.com/PlanXLab/replx"
|
|
39
39
|
Issues = "https://github.com/PlanXLab/replx/issues"
|
|
40
40
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
|
+
from importlib import import_module
|
|
3
|
+
|
|
2
4
|
from .protocol import AgentProtocol
|
|
3
|
-
from .client import AgentClient, get_session_id, get_cached_session_id, clear_session_cache
|
|
4
5
|
|
|
5
6
|
__all__ = [
|
|
6
7
|
'AgentProtocol',
|
|
@@ -14,8 +15,14 @@ __all__ = [
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def __getattr__(name):
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
if name in {
|
|
19
|
+
'AgentClient',
|
|
20
|
+
'get_session_id',
|
|
21
|
+
'get_cached_session_id',
|
|
22
|
+
'clear_session_cache',
|
|
23
|
+
}:
|
|
24
|
+
return getattr(import_module('.client', __name__), name)
|
|
25
|
+
|
|
19
26
|
if name == 'AgentServer':
|
|
20
27
|
from .server import AgentServer
|
|
21
28
|
|
|
@@ -9,6 +9,7 @@ from replx.utils.constants import MAX_PAYLOAD_SIZE
|
|
|
9
9
|
|
|
10
10
|
_MAGIC = b'RPLX'
|
|
11
11
|
_VERSION = 1
|
|
12
|
+
_HEADER = struct.Struct('!4sBI')
|
|
12
13
|
|
|
13
14
|
_seq_lock = threading.Lock()
|
|
14
15
|
_seq_counter = int(time.time() * 1000000) & 0xFFFFFFFE # even start
|
|
@@ -24,38 +25,32 @@ def _next_seq() -> int:
|
|
|
24
25
|
class AgentProtocol:
|
|
25
26
|
@staticmethod
|
|
26
27
|
def encode_message(msg: Dict[str, Any]) -> bytes:
|
|
27
|
-
payload = json.dumps(msg).encode('utf-8')
|
|
28
|
+
payload = json.dumps(msg, separators=(',', ':')).encode('utf-8')
|
|
28
29
|
|
|
29
30
|
if len(payload) > MAX_PAYLOAD_SIZE:
|
|
30
31
|
raise ValueError(f"Message too large: {len(payload)} bytes")
|
|
31
32
|
|
|
32
|
-
header = (
|
|
33
|
-
_MAGIC +
|
|
34
|
-
struct.pack('!B', _VERSION) +
|
|
35
|
-
struct.pack('!I', len(payload))
|
|
36
|
-
)
|
|
33
|
+
header = _HEADER.pack(_MAGIC, _VERSION, len(payload))
|
|
37
34
|
|
|
38
35
|
return header + payload
|
|
39
36
|
|
|
40
37
|
@staticmethod
|
|
41
38
|
def decode_message(data: bytes) -> Optional[Dict[str, Any]]:
|
|
42
|
-
if len(data) <
|
|
39
|
+
if len(data) < _HEADER.size:
|
|
43
40
|
return None
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
magic, version, length = _HEADER.unpack_from(data)
|
|
43
|
+
if magic != _MAGIC:
|
|
46
44
|
return None
|
|
47
45
|
|
|
48
|
-
version = struct.unpack('!B', data[4:5])[0]
|
|
49
46
|
if version != _VERSION:
|
|
50
47
|
return None
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if len(data) < 9 + length:
|
|
49
|
+
payload_end = _HEADER.size + length
|
|
50
|
+
if len(data) < payload_end:
|
|
55
51
|
return None
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
return json.loads(payload.decode('utf-8'))
|
|
53
|
+
return json.loads(data[_HEADER.size:payload_end])
|
|
59
54
|
|
|
60
55
|
@staticmethod
|
|
61
56
|
def create_request(command: str, seq: int = None, ppid: int = None, port: str = None, **args) -> Dict[str, Any]:
|
|
@@ -2,9 +2,10 @@ import re
|
|
|
2
2
|
import sys
|
|
3
3
|
import time
|
|
4
4
|
import threading
|
|
5
|
+
from collections import deque
|
|
5
6
|
from concurrent.futures import Future as ConcurrentFuture
|
|
6
7
|
from dataclasses import dataclass, field
|
|
7
|
-
from typing import Optional, Dict, Any, List, Tuple
|
|
8
|
+
from typing import Optional, Dict, Any, Deque, List, Tuple
|
|
8
9
|
|
|
9
10
|
from replx.protocol import ReplProtocol, create_storage
|
|
10
11
|
from replx.utils import parse_device_banner, canon_port as _shared_canon_port
|
|
@@ -58,7 +59,7 @@ class InteractiveSessionState:
|
|
|
58
59
|
seq: int = 0
|
|
59
60
|
client_addr: Optional[tuple] = None
|
|
60
61
|
echo: bool = True
|
|
61
|
-
input_queue:
|
|
62
|
+
input_queue: Deque[bytes] = field(default_factory=deque)
|
|
62
63
|
stop_requested: bool = False
|
|
63
64
|
thread: Optional[threading.Thread] = None
|
|
64
65
|
error: Optional[str] = None
|
|
@@ -71,7 +72,7 @@ class InteractiveSessionState:
|
|
|
71
72
|
self.seq = seq
|
|
72
73
|
self.client_addr = client_addr
|
|
73
74
|
self.echo = echo
|
|
74
|
-
self.input_queue
|
|
75
|
+
self.input_queue.clear()
|
|
75
76
|
self.stop_requested = False
|
|
76
77
|
self.error = None
|
|
77
78
|
|
|
@@ -82,7 +83,7 @@ class InteractiveSessionState:
|
|
|
82
83
|
self.ppid = None
|
|
83
84
|
self.seq = 0
|
|
84
85
|
self.client_addr = None
|
|
85
|
-
self.input_queue
|
|
86
|
+
self.input_queue.clear()
|
|
86
87
|
self.stop_requested = False
|
|
87
88
|
thread_to_join = self.thread
|
|
88
89
|
self.thread = None
|
|
@@ -104,14 +105,14 @@ class ReplSessionState:
|
|
|
104
105
|
ppid: Optional[int] = None
|
|
105
106
|
reader_future: Optional[ConcurrentFuture] = None
|
|
106
107
|
reader_thread: Optional[threading.Thread] = None
|
|
107
|
-
output_buffer:
|
|
108
|
+
output_buffer: bytearray = field(default_factory=bytearray)
|
|
108
109
|
buffer_lock: threading.Lock = field(default_factory=threading.Lock)
|
|
109
110
|
|
|
110
111
|
def start(self, ppid: int):
|
|
111
112
|
self.active = True
|
|
112
113
|
self.ppid = ppid
|
|
113
114
|
with self.buffer_lock:
|
|
114
|
-
self.output_buffer
|
|
115
|
+
self.output_buffer.clear()
|
|
115
116
|
|
|
116
117
|
def stop(self):
|
|
117
118
|
self.active = False
|
|
@@ -123,7 +124,7 @@ class ReplSessionState:
|
|
|
123
124
|
self.reader_thread.join(timeout=1)
|
|
124
125
|
self.reader_thread = None
|
|
125
126
|
with self.buffer_lock:
|
|
126
|
-
self.output_buffer
|
|
127
|
+
self.output_buffer.clear()
|
|
127
128
|
|
|
128
129
|
def is_owner(self, ppid: int) -> bool:
|
|
129
130
|
if ppid is None:
|
|
@@ -134,13 +135,13 @@ class ReplSessionState:
|
|
|
134
135
|
|
|
135
136
|
def append_output(self, data: bytes):
|
|
136
137
|
with self.buffer_lock:
|
|
137
|
-
self.output_buffer
|
|
138
|
+
self.output_buffer.extend(data)
|
|
138
139
|
|
|
139
140
|
def read_output(self) -> bytes:
|
|
140
141
|
with self.buffer_lock:
|
|
141
142
|
data = self.output_buffer
|
|
142
|
-
self.output_buffer =
|
|
143
|
-
|
|
143
|
+
self.output_buffer = bytearray()
|
|
144
|
+
return bytes(data)
|
|
144
145
|
|
|
145
146
|
@dataclass
|
|
146
147
|
class BoardConnection:
|
|
@@ -617,10 +617,14 @@ class AgentServer(
|
|
|
617
617
|
self._cache_response(client_addr, seq, encoded)
|
|
618
618
|
self._safe_send(encoded, client_addr)
|
|
619
619
|
|
|
620
|
-
def _handle_request(self,
|
|
620
|
+
def _handle_request(self, request: bytes | dict, client_addr: tuple):
|
|
621
621
|
msg = None
|
|
622
622
|
try:
|
|
623
|
-
msg =
|
|
623
|
+
msg = (
|
|
624
|
+
AgentProtocol.decode_message(request)
|
|
625
|
+
if isinstance(request, bytes)
|
|
626
|
+
else request
|
|
627
|
+
)
|
|
624
628
|
if not msg:
|
|
625
629
|
print(f"Invalid message from {client_addr}", file=sys.stderr)
|
|
626
630
|
return
|
|
@@ -360,7 +360,7 @@ class ExecCommandsMixin:
|
|
|
360
360
|
input_data = None
|
|
361
361
|
with conn.interactive.lock:
|
|
362
362
|
if conn.interactive.input_queue:
|
|
363
|
-
input_data = conn.interactive.input_queue.
|
|
363
|
+
input_data = conn.interactive.input_queue.popleft()
|
|
364
364
|
|
|
365
365
|
if input_data:
|
|
366
366
|
try:
|
|
@@ -17,6 +17,8 @@ FAST_COMMANDS: frozenset[str] = frozenset({
|
|
|
17
17
|
'repl_read',
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
+
_RESPONSE_CACHE_MAX_BYTES = 16 * 1024 * 1024
|
|
21
|
+
|
|
20
22
|
|
|
21
23
|
class AgentDatagramProtocol(asyncio.DatagramProtocol):
|
|
22
24
|
__slots__ = ('_server',)
|
|
@@ -44,7 +46,8 @@ class AgentDatagramProtocol(asyncio.DatagramProtocol):
|
|
|
44
46
|
if command in FAST_COMMANDS
|
|
45
47
|
else server._slow_executor
|
|
46
48
|
)
|
|
47
|
-
|
|
49
|
+
request = msg if isinstance(msg, dict) else data
|
|
50
|
+
executor.submit(server._handle_request, request, addr)
|
|
48
51
|
|
|
49
52
|
def error_received(self, exc: Exception) -> None:
|
|
50
53
|
if self._server.running:
|
|
@@ -59,6 +62,7 @@ class UdpReliabilityMixin:
|
|
|
59
62
|
self,
|
|
60
63
|
*,
|
|
61
64
|
response_cache_max: int = 512,
|
|
65
|
+
response_cache_max_bytes: int = _RESPONSE_CACHE_MAX_BYTES,
|
|
62
66
|
last_seq_max: int = 256,
|
|
63
67
|
) -> None:
|
|
64
68
|
self.last_seq: dict = {}
|
|
@@ -69,9 +73,10 @@ class UdpReliabilityMixin:
|
|
|
69
73
|
self._send_lock = threading.Lock()
|
|
70
74
|
|
|
71
75
|
self._response_cache: dict[tuple, bytes] = {}
|
|
72
|
-
self.
|
|
76
|
+
self._response_cache_bytes = 0
|
|
73
77
|
self._response_cache_lock = threading.Lock()
|
|
74
78
|
self._RESPONSE_CACHE_MAX = response_cache_max
|
|
79
|
+
self._RESPONSE_CACHE_MAX_BYTES = response_cache_max_bytes
|
|
75
80
|
|
|
76
81
|
self._stream_ack_events: dict[tuple, threading.Event] = {}
|
|
77
82
|
self._stream_ack_lock = threading.Lock()
|
|
@@ -92,17 +97,20 @@ class UdpReliabilityMixin:
|
|
|
92
97
|
return
|
|
93
98
|
key = (client_addr, seq)
|
|
94
99
|
with self._response_cache_lock:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
except ValueError:
|
|
99
|
-
pass
|
|
100
|
-
else:
|
|
101
|
-
if len(self._response_cache_order) >= self._RESPONSE_CACHE_MAX:
|
|
102
|
-
oldest = self._response_cache_order.pop(0)
|
|
103
|
-
self._response_cache.pop(oldest, None)
|
|
100
|
+
previous = self._response_cache.pop(key, None)
|
|
101
|
+
if previous is not None:
|
|
102
|
+
self._response_cache_bytes -= len(previous)
|
|
104
103
|
self._response_cache[key] = data
|
|
105
|
-
self.
|
|
104
|
+
self._response_cache_bytes += len(data)
|
|
105
|
+
|
|
106
|
+
# Dict insertion order provides the FIFO index without a second list.
|
|
107
|
+
while (
|
|
108
|
+
len(self._response_cache) > self._RESPONSE_CACHE_MAX
|
|
109
|
+
or self._response_cache_bytes > self._RESPONSE_CACHE_MAX_BYTES
|
|
110
|
+
):
|
|
111
|
+
oldest = next(iter(self._response_cache))
|
|
112
|
+
evicted = self._response_cache.pop(oldest)
|
|
113
|
+
self._response_cache_bytes -= len(evicted)
|
|
106
114
|
|
|
107
115
|
def _lookup_response(self, client_addr: tuple, seq: int) -> Optional[bytes]:
|
|
108
116
|
with self._response_cache_lock:
|
|
@@ -6,8 +6,6 @@ import threading
|
|
|
6
6
|
from contextlib import contextmanager
|
|
7
7
|
from typing import Callable, Optional
|
|
8
8
|
|
|
9
|
-
import typer
|
|
10
|
-
|
|
11
9
|
from replx.utils.exceptions import ProtocolError, TransportError
|
|
12
10
|
from replx.utils.constants import (
|
|
13
11
|
CTRL_A, CTRL_B, CTRL_C, CTRL_D, CTRL_E,
|
|
@@ -27,6 +25,8 @@ from replx.terminal import (
|
|
|
27
25
|
getch, putch, getch_nonblock,
|
|
28
26
|
)
|
|
29
27
|
|
|
28
|
+
_RAW_PASTE_MIN_COMMAND_SIZE = 4 * 1024
|
|
29
|
+
|
|
30
30
|
|
|
31
31
|
class ReplProtocol:
|
|
32
32
|
_active_instance = None
|
|
@@ -133,7 +133,7 @@ class ReplProtocol:
|
|
|
133
133
|
|
|
134
134
|
def _read_ex(self, min_num_bytes:int, ending:bytes, timeout:int=5,
|
|
135
135
|
data_consumer:Optional[Callable[[bytes], None]]=None) -> bytes:
|
|
136
|
-
start = time.
|
|
136
|
+
start = time.monotonic()
|
|
137
137
|
deadline = (start + timeout) if timeout > 0 else None
|
|
138
138
|
|
|
139
139
|
streaming_mode = (timeout == 0 and data_consumer is not None)
|
|
@@ -265,18 +265,18 @@ class ReplProtocol:
|
|
|
265
265
|
self._rx_pushback[:] = tail + self._rx_pushback
|
|
266
266
|
return _get_result()
|
|
267
267
|
|
|
268
|
-
last_activity = time.
|
|
269
|
-
last_keepalive = time.
|
|
268
|
+
last_activity = time.monotonic()
|
|
269
|
+
last_keepalive = time.monotonic()
|
|
270
270
|
keepalive_interval = 30.0
|
|
271
271
|
|
|
272
272
|
while True:
|
|
273
273
|
if self._stop_event.is_set():
|
|
274
274
|
break
|
|
275
275
|
|
|
276
|
-
if deadline is not None and time.
|
|
276
|
+
if deadline is not None and time.monotonic() >= deadline:
|
|
277
277
|
break
|
|
278
278
|
|
|
279
|
-
now = time.
|
|
279
|
+
now = time.monotonic()
|
|
280
280
|
if streaming_mode and (now - last_keepalive) >= keepalive_interval:
|
|
281
281
|
try:
|
|
282
282
|
if hasattr(self.transport, 'keep_alive'):
|
|
@@ -294,7 +294,7 @@ class ReplProtocol:
|
|
|
294
294
|
chunk = _read_some(want)
|
|
295
295
|
if chunk:
|
|
296
296
|
pos = _feed_chunk(chunk)
|
|
297
|
-
last_activity = time.
|
|
297
|
+
last_activity = time.monotonic()
|
|
298
298
|
if pos >= 0:
|
|
299
299
|
tail = chunk[pos:]
|
|
300
300
|
if tail:
|
|
@@ -307,7 +307,7 @@ class ReplProtocol:
|
|
|
307
307
|
|
|
308
308
|
if timeout > 0:
|
|
309
309
|
idle_limit = max(timeout * 2, 10)
|
|
310
|
-
if (time.
|
|
310
|
+
if (time.monotonic() - last_activity) > idle_limit:
|
|
311
311
|
break
|
|
312
312
|
|
|
313
313
|
return _get_result()
|
|
@@ -434,10 +434,10 @@ class ReplProtocol:
|
|
|
434
434
|
try:
|
|
435
435
|
self.transport.write(RAW_PASTE_INIT)
|
|
436
436
|
|
|
437
|
-
start = time.
|
|
437
|
+
start = time.monotonic()
|
|
438
438
|
timeout = 0.5
|
|
439
439
|
response = b''
|
|
440
|
-
while len(response) < 2 and (time.
|
|
440
|
+
while len(response) < 2 and (time.monotonic() - start) < timeout:
|
|
441
441
|
waiting = self.transport.in_waiting()
|
|
442
442
|
if waiting > 0:
|
|
443
443
|
response += self._read(min(2 - len(response), waiting))
|
|
@@ -449,14 +449,16 @@ class ReplProtocol:
|
|
|
449
449
|
return False
|
|
450
450
|
|
|
451
451
|
if response == RAW_PASTE_SUPPORTED:
|
|
452
|
-
|
|
453
|
-
if len(
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
452
|
+
window_bytes = self._read(2)
|
|
453
|
+
if len(window_bytes) == 2:
|
|
454
|
+
window_size = struct.unpack('<H', window_bytes)[0]
|
|
455
|
+
if window_size > 0:
|
|
456
|
+
self._raw_paste_window_size = window_size
|
|
457
|
+
self._raw_paste_supported = True
|
|
458
|
+
return True
|
|
459
|
+
|
|
460
|
+
self._raw_paste_supported = False
|
|
461
|
+
return False
|
|
460
462
|
|
|
461
463
|
elif response == RAW_PASTE_NOT_SUPPORTED:
|
|
462
464
|
self._raw_paste_supported = False
|
|
@@ -513,13 +515,13 @@ class ReplProtocol:
|
|
|
513
515
|
pass
|
|
514
516
|
|
|
515
517
|
def _exec_raw_paste(self, command: bytes, data_consumer: Optional[Callable[[bytes], None]] = None) -> tuple[bytes, bytes]:
|
|
516
|
-
remaining_window = self._raw_paste_window_size
|
|
518
|
+
remaining_window = self._raw_paste_window_size
|
|
517
519
|
bytes_sent = 0
|
|
518
520
|
command_len = len(command)
|
|
519
521
|
aborted = False
|
|
520
522
|
|
|
521
523
|
while bytes_sent < command_len:
|
|
522
|
-
while remaining_window <= 0:
|
|
524
|
+
while remaining_window <= 0 or self.transport.in_waiting() > 0:
|
|
523
525
|
fc_byte = self._read(1)
|
|
524
526
|
if fc_byte == RAW_PASTE_WINDOW_INC:
|
|
525
527
|
remaining_window += self._raw_paste_window_size
|
|
@@ -527,27 +529,19 @@ class ReplProtocol:
|
|
|
527
529
|
self.transport.write(CTRL_D)
|
|
528
530
|
aborted = True
|
|
529
531
|
break
|
|
530
|
-
|
|
532
|
+
elif fc_byte:
|
|
533
|
+
raise ProtocolError(
|
|
534
|
+
f"Unexpected raw-paste flow-control byte: {fc_byte!r}"
|
|
535
|
+
)
|
|
536
|
+
|
|
531
537
|
if aborted:
|
|
532
538
|
break
|
|
533
|
-
|
|
539
|
+
|
|
534
540
|
chunk_size = min(remaining_window, command_len - bytes_sent)
|
|
535
541
|
chunk = command[bytes_sent:bytes_sent + chunk_size]
|
|
536
542
|
self.transport.write(chunk)
|
|
537
543
|
bytes_sent += chunk_size
|
|
538
544
|
remaining_window -= chunk_size
|
|
539
|
-
|
|
540
|
-
while self.transport.in_waiting() > 0:
|
|
541
|
-
fc_byte = self._read(1)
|
|
542
|
-
if fc_byte == RAW_PASTE_WINDOW_INC:
|
|
543
|
-
remaining_window += self._raw_paste_window_size
|
|
544
|
-
elif fc_byte == RAW_PASTE_END_DATA:
|
|
545
|
-
self.transport.write(CTRL_D)
|
|
546
|
-
aborted = True
|
|
547
|
-
break
|
|
548
|
-
|
|
549
|
-
if aborted:
|
|
550
|
-
break
|
|
551
545
|
|
|
552
546
|
if not aborted and bytes_sent == command_len:
|
|
553
547
|
self.transport.write(CTRL_D)
|
|
@@ -570,7 +564,7 @@ class ReplProtocol:
|
|
|
570
564
|
|
|
571
565
|
def _exec(self, command:str|bytes, interactive:bool=False, echo:bool=False, detach:bool=False,
|
|
572
566
|
data_consumer:Optional[Callable[[bytes], None]]=None,
|
|
573
|
-
force_raw_paste:bool=
|
|
567
|
+
force_raw_paste:Optional[bool]=None) -> bytes:
|
|
574
568
|
self._stop_event.clear()
|
|
575
569
|
self._reset_error_filter()
|
|
576
570
|
|
|
@@ -591,7 +585,7 @@ class ReplProtocol:
|
|
|
591
585
|
|
|
592
586
|
def _exec_command(self, command: bytes, interactive: bool, echo: bool, detach: bool,
|
|
593
587
|
data_consumer: Optional[Callable[[bytes], None]],
|
|
594
|
-
force_raw_paste: bool) -> bytes:
|
|
588
|
+
force_raw_paste: Optional[bool]) -> bytes:
|
|
595
589
|
data_err = b''
|
|
596
590
|
if data_consumer is None and interactive:
|
|
597
591
|
data_consumer = self._create_data_consumer()
|
|
@@ -603,7 +597,10 @@ class ReplProtocol:
|
|
|
603
597
|
|
|
604
598
|
command_len = len(command)
|
|
605
599
|
|
|
606
|
-
|
|
600
|
+
prefer_raw_paste = force_raw_paste
|
|
601
|
+
if prefer_raw_paste is None:
|
|
602
|
+
prefer_raw_paste = command_len >= _RAW_PASTE_MIN_COMMAND_SIZE
|
|
603
|
+
use_raw_paste = prefer_raw_paste and self._enter_raw_paste_mode()
|
|
607
604
|
|
|
608
605
|
if use_raw_paste:
|
|
609
606
|
try:
|
|
@@ -630,7 +627,7 @@ class ReplProtocol:
|
|
|
630
627
|
current_buffer_size = self._DEVICE_CHUNK_SIZES
|
|
631
628
|
bytes_sent = 0
|
|
632
629
|
|
|
633
|
-
start_time = time.
|
|
630
|
+
start_time = time.monotonic()
|
|
634
631
|
|
|
635
632
|
while bytes_sent < command_len:
|
|
636
633
|
chunk_end = min(bytes_sent + current_buffer_size, command_len)
|
|
@@ -644,7 +641,7 @@ class ReplProtocol:
|
|
|
644
641
|
|
|
645
642
|
self.transport.write(EOF_MARKER)
|
|
646
643
|
|
|
647
|
-
transfer_time = time.
|
|
644
|
+
transfer_time = time.monotonic() - start_time
|
|
648
645
|
timeout = max(5, int(transfer_time * 2))
|
|
649
646
|
|
|
650
647
|
data = self._read_ex(1, OK_RESPONSE, timeout=timeout)
|
|
@@ -742,8 +739,8 @@ class ReplProtocol:
|
|
|
742
739
|
return data_consumer
|
|
743
740
|
|
|
744
741
|
def _drain_eof(self, max_ms:int=200):
|
|
745
|
-
deadline = time.
|
|
746
|
-
while time.
|
|
742
|
+
deadline = time.monotonic() + max_ms / 1000
|
|
743
|
+
while time.monotonic() < deadline:
|
|
747
744
|
waiting = self.transport.in_waiting()
|
|
748
745
|
if waiting:
|
|
749
746
|
_ = self._read(waiting)
|
|
@@ -826,6 +823,8 @@ class ReplProtocol:
|
|
|
826
823
|
|
|
827
824
|
def run(self, local, interactive:bool=False, echo:bool=False):
|
|
828
825
|
if not interactive and echo:
|
|
826
|
+
import typer
|
|
827
|
+
|
|
829
828
|
raise typer.BadParameter("Option chaining error: -n and -e can only be used once, not multiple times.")
|
|
830
829
|
|
|
831
830
|
with open(local, "rb") as f:
|
|
@@ -900,8 +899,8 @@ class ReplProtocol:
|
|
|
900
899
|
return
|
|
901
900
|
time.sleep(0.1)
|
|
902
901
|
|
|
903
|
-
drain_timeout = time.
|
|
904
|
-
while time.
|
|
902
|
+
drain_timeout = time.monotonic() + 0.5
|
|
903
|
+
while time.monotonic() < drain_timeout:
|
|
905
904
|
if self.transport.in_waiting() > 0:
|
|
906
905
|
self.transport.read(self.transport.in_waiting())
|
|
907
906
|
time.sleep(0.02)
|
|
@@ -917,8 +916,8 @@ class ReplProtocol:
|
|
|
917
916
|
print('')
|
|
918
917
|
return
|
|
919
918
|
|
|
920
|
-
prompt_timeout = time.
|
|
921
|
-
while time.
|
|
919
|
+
prompt_timeout = time.monotonic() + 1.0
|
|
920
|
+
while time.monotonic() < prompt_timeout:
|
|
922
921
|
time.sleep(0.05)
|
|
923
922
|
if self._repl_prompt_detected:
|
|
924
923
|
break
|
|
@@ -750,7 +750,8 @@ if chunk:
|
|
|
750
750
|
return
|
|
751
751
|
|
|
752
752
|
code = ";\n".join(batch_src_lines)
|
|
753
|
-
|
|
753
|
+
# Keep bulk file batches on raw REPL to avoid per-window flow-control overhead.
|
|
754
|
+
self.repl._exec(code, force_raw_paste=False)
|
|
754
755
|
batch_src_lines = []
|
|
755
756
|
batch_bytes = 0
|
|
756
757
|
|
|
@@ -199,7 +199,7 @@ else:
|
|
|
199
199
|
_FD = sys.stdin.fileno()
|
|
200
200
|
|
|
201
201
|
_terminal_state = threading.local()
|
|
202
|
-
_terminal_lock = threading.
|
|
202
|
+
_terminal_lock = threading.RLock()
|
|
203
203
|
|
|
204
204
|
def _get_terminal_state():
|
|
205
205
|
if not hasattr(_terminal_state, 'old_settings'):
|
|
@@ -501,4 +501,3 @@ class LineModeTerminal:
|
|
|
501
501
|
self._scroll_bottom = self._rows - 1
|
|
502
502
|
lmt_set_scroll_region(self._scroll_top, self._scroll_bottom)
|
|
503
503
|
self._draw_input()
|
|
504
|
-
|