replx 1.3__tar.gz → 1.5__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.
- replx-1.5/PKG-INFO +101 -0
- replx-1.5/README.md +75 -0
- {replx-1.3 → replx-1.5}/replx/__init__.py +1 -1
- {replx-1.3 → replx-1.5}/replx/cli/agent/client/core.py +20 -12
- {replx-1.3 → replx-1.5}/replx/cli/agent/server/connection_manager.py +17 -7
- {replx-1.3 → replx-1.5}/replx/cli/agent/server/core.py +249 -132
- {replx-1.3 → replx-1.5}/replx/cli/agent/server/handlers/exec.py +9 -8
- {replx-1.3 → replx-1.5}/replx/cli/agent/server/handlers/repl.py +53 -7
- {replx-1.3 → replx-1.5}/replx/cli/agent/server/handlers/session.py +6 -14
- {replx-1.3 → replx-1.5}/replx/cli/app.py +1 -1
- {replx-1.3 → replx-1.5}/replx/cli/commands/device.py +55 -20
- {replx-1.3 → replx-1.5}/replx/cli/commands/exec.py +42 -12
- {replx-1.3 → replx-1.5}/replx/cli/commands/package.py +124 -163
- {replx-1.3 → replx-1.5}/replx/cli/commands/utility.py +13 -13
- {replx-1.3 → replx-1.5}/replx/cli/helpers/registry.py +25 -7
- {replx-1.3 → replx-1.5}/replx/commands.py +3 -3
- {replx-1.3 → replx-1.5}/replx/protocol/repl.py +46 -5
- {replx-1.3 → replx-1.5}/replx/terminal.py +46 -0
- replx-1.5/replx/tests/test_agent_asyncio.py +319 -0
- replx-1.5/replx/tests/test_agent_port_canonicalization.py +43 -0
- replx-1.5/replx/tests/test_agent_thread_pool.py +216 -0
- replx-1.5/replx/tests/test_disconnect_cleanup.py +57 -0
- replx-1.5/replx/tests/test_lock_cleanup.py +194 -0
- replx-1.5/replx/tests/test_pkg_search_scope_filter.py +82 -0
- replx-1.5/replx/tests/test_repl_reader_task.py +313 -0
- replx-1.5/replx/tests/test_session_disconnect_releases_shared_port.py +47 -0
- replx-1.5/replx/tests/test_shutdown_status_message.py +56 -0
- replx-1.5/replx/tests/test_windows_com_port_normalization.py +69 -0
- {replx-1.3 → replx-1.5}/replx/transport/serial.py +13 -8
- replx-1.5/replx.egg-info/PKG-INFO +101 -0
- {replx-1.3 → replx-1.5}/replx.egg-info/SOURCES.txt +13 -1
- replx-1.5/test/test_termio.py +42 -0
- replx-1.3/PKG-INFO +0 -25
- replx-1.3/replx.egg-info/PKG-INFO +0 -25
- {replx-1.3 → replx-1.5}/LICENSE +0 -0
- {replx-1.3 → replx-1.5}/pyproject.toml +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/__init__.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/agent/__init__.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/agent/client/__init__.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/agent/client/session.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/agent/protocol.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/agent/server/__init__.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/agent/server/__main__.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/agent/server/command_dispatcher.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/agent/server/handlers/__init__.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/agent/server/handlers/filesystem.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/agent/server/handlers/transfer.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/agent/server/session_manager.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/commands/__init__.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/commands/file.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/commands/firmware.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/config.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/connection.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/helpers/__init__.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/helpers/compiler.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/helpers/environment.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/helpers/output.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/helpers/scanner.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/helpers/store.py +0 -0
- {replx-1.3 → replx-1.5}/replx/cli/helpers/updater.py +0 -0
- {replx-1.3 → replx-1.5}/replx/protocol/__init__.py +0 -0
- {replx-1.3 → replx-1.5}/replx/protocol/storage.py +0 -0
- {replx-1.3 → replx-1.5}/replx/tests/__init__.py +0 -0
- {replx-1.3 → replx-1.5}/replx/tests/test_compiler_arch.py +0 -0
- {replx-1.3 → replx-1.5}/replx/tests/test_connection_info_lookup.py +0 -0
- {replx-1.3 → replx-1.5}/replx/tests/test_device_info_esp_multi_core.py +0 -0
- {replx-1.3 → replx-1.5}/replx/tests/test_pkg_local_version.py +0 -0
- {replx-1.3 → replx-1.5}/replx/tests/test_session_id_fallback.py +0 -0
- {replx-1.3 → replx-1.5}/replx/transport/__init__.py +0 -0
- {replx-1.3 → replx-1.5}/replx/transport/base.py +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/_thread.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/aioble/__init__.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/array.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/asyncio/__init__.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/binascii.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/bluetooth.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/builtins.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/cmath.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/collections.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/cryptolib.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/deflate.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/errno.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/framebuf.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/gc.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/hashlib.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/heapq.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/io.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/json.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/lwip.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/machine.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/math.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/micropython.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/mip/__init__.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/network.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/ntptime.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/os.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/platform.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/random.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/re.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/requests/__init__.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/select.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/socket.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/ssl.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/struct.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/sys.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/time.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/tls.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/uasyncio.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/uctypes.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/urequests.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm/vfs.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/binascii.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/errno.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/hashlib.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/io.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/json.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/machine.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/math.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/micropython.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/network.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/os.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/select.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/socket.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/ssl.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/struct.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/sys.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/time.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/ubinascii.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/ucryptolib.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/uerrno.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/uhashlib.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/uio.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/ujson.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/umachine.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/uos.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/uselect.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/usocket.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/ussl.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/ustruct.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/comm_separate/EFR32MG/utime.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/core/ESP32/aioespnow.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/core/ESP32/esp.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/core/ESP32/esp32.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/core/ESP32/espnow.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/core/MIMXRT1062DVJ6A/mimxrt.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/typehints/core/RP2350/rp2.pyi +0 -0
- {replx-1.3 → replx-1.5}/replx/utils/__init__.py +0 -0
- {replx-1.3 → replx-1.5}/replx/utils/constants.py +0 -0
- {replx-1.3 → replx-1.5}/replx/utils/device_info.py +0 -0
- {replx-1.3 → replx-1.5}/replx/utils/exceptions.py +0 -0
- {replx-1.3 → replx-1.5}/replx.egg-info/dependency_links.txt +0 -0
- {replx-1.3 → replx-1.5}/replx.egg-info/entry_points.txt +0 -0
- {replx-1.3 → replx-1.5}/replx.egg-info/requires.txt +0 -0
- {replx-1.3 → replx-1.5}/replx.egg-info/top_level.txt +0 -0
- {replx-1.3 → replx-1.5}/setup.cfg +0 -0
replx-1.5/PKG-INFO
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: replx
|
|
3
|
+
Version: 1.5
|
|
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@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/PlanXLab/replx
|
|
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
|
|
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: typer>=0.12
|
|
21
|
+
Requires-Dist: rich>=13.0
|
|
22
|
+
Requires-Dist: pyserial>=3.5
|
|
23
|
+
Requires-Dist: mpy-cross>=1.26
|
|
24
|
+
Requires-Dist: psutil>=5.9.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# replx
|
|
28
|
+
|
|
29
|
+
[](https://badge.fury.io/py/replx)
|
|
30
|
+
[](https://www.python.org/downloads/)
|
|
31
|
+
[](https://opensource.org/licenses/MIT)
|
|
32
|
+
|
|
33
|
+
`replx` is a CLI tool for MicroPython development. It uses an agent-based architecture to manage multiple CLI sessions and multiple boards in a consistent workflow.
|
|
34
|
+
|
|
35
|
+
## What replx provides
|
|
36
|
+
|
|
37
|
+
- Shared connection management across terminal sessions
|
|
38
|
+
- Foreground and background board handling per session
|
|
39
|
+
- Workspace-level default device configuration
|
|
40
|
+
- File operations on device storage
|
|
41
|
+
- Script execution, REPL access, and utility commands
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
pip install replx
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Command summary
|
|
50
|
+
|
|
51
|
+
### Connection and session
|
|
52
|
+
|
|
53
|
+
- `setup`: Initialize workspace settings and register a default device.
|
|
54
|
+
- `scan`: List available serial devices.
|
|
55
|
+
- `status`: Show session and connection state.
|
|
56
|
+
- `fg`: Change the foreground device for the current session.
|
|
57
|
+
- `whoami`: Show the current foreground device.
|
|
58
|
+
- `disconnect`: Close a device connection.
|
|
59
|
+
- `shutdown`: Stop the agent and clear active sessions.
|
|
60
|
+
|
|
61
|
+
### Execution and interaction
|
|
62
|
+
|
|
63
|
+
- `exec` (`-c`): Execute inline Python code on the device.
|
|
64
|
+
- `run`: Run a local or device-side script.
|
|
65
|
+
- `repl`: Open an interactive REPL session.
|
|
66
|
+
- `shell`: Open a device file-system shell.
|
|
67
|
+
|
|
68
|
+
### File operations
|
|
69
|
+
|
|
70
|
+
- `ls`: List files and directories.
|
|
71
|
+
- `cat`: Print file content.
|
|
72
|
+
- `get`: Download files from device to local.
|
|
73
|
+
- `put`: Upload files from local to device.
|
|
74
|
+
- `cp`: Copy files or directories on device.
|
|
75
|
+
- `mv`: Move or rename files or directories on device.
|
|
76
|
+
- `rm`: Remove files or directories on device.
|
|
77
|
+
- `mkdir`: Create directories on device.
|
|
78
|
+
- `touch`: Create an empty file or update timestamps.
|
|
79
|
+
|
|
80
|
+
### Device management
|
|
81
|
+
|
|
82
|
+
- `usage`: Show device storage usage.
|
|
83
|
+
- `reset`: Perform a soft reset.
|
|
84
|
+
- `format`: Format the device file system.
|
|
85
|
+
- `init`: Run initialization scripts on device.
|
|
86
|
+
- `wifi`: Manage Wi-Fi configuration and status.
|
|
87
|
+
- `firmware`: Check, download, or update firmware.
|
|
88
|
+
|
|
89
|
+
### Package and build
|
|
90
|
+
|
|
91
|
+
- `pkg`: Search, download, and update packages.
|
|
92
|
+
- `mpy`: Compile `.py` files to `.mpy`.
|
|
93
|
+
|
|
94
|
+
## Notes
|
|
95
|
+
|
|
96
|
+
- `scan`, `status`, `whoami`, and `shutdown` are special commands and do not accept `--port`.
|
|
97
|
+
- Most device commands can omit the port when a foreground or workspace default device is available.
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT
|
replx-1.5/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# replx
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/py/replx)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
`replx` is a CLI tool for MicroPython development. It uses an agent-based architecture to manage multiple CLI sessions and multiple boards in a consistent workflow.
|
|
8
|
+
|
|
9
|
+
## What replx provides
|
|
10
|
+
|
|
11
|
+
- Shared connection management across terminal sessions
|
|
12
|
+
- Foreground and background board handling per session
|
|
13
|
+
- Workspace-level default device configuration
|
|
14
|
+
- File operations on device storage
|
|
15
|
+
- Script execution, REPL access, and utility commands
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
pip install replx
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Command summary
|
|
24
|
+
|
|
25
|
+
### Connection and session
|
|
26
|
+
|
|
27
|
+
- `setup`: Initialize workspace settings and register a default device.
|
|
28
|
+
- `scan`: List available serial devices.
|
|
29
|
+
- `status`: Show session and connection state.
|
|
30
|
+
- `fg`: Change the foreground device for the current session.
|
|
31
|
+
- `whoami`: Show the current foreground device.
|
|
32
|
+
- `disconnect`: Close a device connection.
|
|
33
|
+
- `shutdown`: Stop the agent and clear active sessions.
|
|
34
|
+
|
|
35
|
+
### Execution and interaction
|
|
36
|
+
|
|
37
|
+
- `exec` (`-c`): Execute inline Python code on the device.
|
|
38
|
+
- `run`: Run a local or device-side script.
|
|
39
|
+
- `repl`: Open an interactive REPL session.
|
|
40
|
+
- `shell`: Open a device file-system shell.
|
|
41
|
+
|
|
42
|
+
### File operations
|
|
43
|
+
|
|
44
|
+
- `ls`: List files and directories.
|
|
45
|
+
- `cat`: Print file content.
|
|
46
|
+
- `get`: Download files from device to local.
|
|
47
|
+
- `put`: Upload files from local to device.
|
|
48
|
+
- `cp`: Copy files or directories on device.
|
|
49
|
+
- `mv`: Move or rename files or directories on device.
|
|
50
|
+
- `rm`: Remove files or directories on device.
|
|
51
|
+
- `mkdir`: Create directories on device.
|
|
52
|
+
- `touch`: Create an empty file or update timestamps.
|
|
53
|
+
|
|
54
|
+
### Device management
|
|
55
|
+
|
|
56
|
+
- `usage`: Show device storage usage.
|
|
57
|
+
- `reset`: Perform a soft reset.
|
|
58
|
+
- `format`: Format the device file system.
|
|
59
|
+
- `init`: Run initialization scripts on device.
|
|
60
|
+
- `wifi`: Manage Wi-Fi configuration and status.
|
|
61
|
+
- `firmware`: Check, download, or update firmware.
|
|
62
|
+
|
|
63
|
+
### Package and build
|
|
64
|
+
|
|
65
|
+
- `pkg`: Search, download, and update packages.
|
|
66
|
+
- `mpy`: Compile `.py` files to `.mpy`.
|
|
67
|
+
|
|
68
|
+
## Notes
|
|
69
|
+
|
|
70
|
+
- `scan`, `status`, `whoami`, and `shutdown` are special commands and do not accept `--port`.
|
|
71
|
+
- Most device commands can omit the port when a foreground or workspace default device is available.
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
|
@@ -33,7 +33,7 @@ class AgentClient:
|
|
|
33
33
|
self.sock.close()
|
|
34
34
|
self.sock = None
|
|
35
35
|
|
|
36
|
-
def send_command(self, command: str, timeout: float = None, **args) -> Dict[str, Any]:
|
|
36
|
+
def send_command(self, command: str, timeout: float = None, max_retries: int = None, **args) -> Dict[str, Any]:
|
|
37
37
|
if not self.sock:
|
|
38
38
|
self.connect()
|
|
39
39
|
|
|
@@ -58,8 +58,11 @@ class AgentClient:
|
|
|
58
58
|
request_data = AgentProtocol.encode_message(request)
|
|
59
59
|
|
|
60
60
|
response = None
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
|
|
62
|
+
if max_retries is not None:
|
|
63
|
+
max_attempts = max(1, max_retries)
|
|
64
|
+
else:
|
|
65
|
+
max_attempts = 1 if effective_timeout < 1.0 else self.MAX_RETRIES
|
|
63
66
|
|
|
64
67
|
for attempt in range(max_attempts):
|
|
65
68
|
try:
|
|
@@ -154,7 +157,6 @@ class AgentClient:
|
|
|
154
157
|
self.sock.settimeout(0.01)
|
|
155
158
|
input_interval = 0.001
|
|
156
159
|
last_input_time = 0
|
|
157
|
-
error_check_until = time.time() + 0.1
|
|
158
160
|
|
|
159
161
|
try:
|
|
160
162
|
while True:
|
|
@@ -179,15 +181,15 @@ class AgentClient:
|
|
|
179
181
|
except Exception:
|
|
180
182
|
pass
|
|
181
183
|
|
|
184
|
+
_pending_error = None
|
|
182
185
|
try:
|
|
183
186
|
data, addr = self.sock.recvfrom(MAX_UDP_SIZE)
|
|
184
187
|
msg = AgentProtocol.decode_message(data)
|
|
185
188
|
|
|
186
189
|
if msg and msg.get('seq') == seq:
|
|
187
|
-
if
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if msg.get('type') == 'stream':
|
|
190
|
+
if msg.get('type') == 'response' and msg.get('error'):
|
|
191
|
+
_pending_error = msg['error']
|
|
192
|
+
elif msg.get('type') == 'stream':
|
|
191
193
|
output = msg.get('output', '')
|
|
192
194
|
if output and output_callback:
|
|
193
195
|
output_callback(output.encode('utf-8'), 'stdout')
|
|
@@ -203,6 +205,9 @@ class AgentClient:
|
|
|
203
205
|
except Exception:
|
|
204
206
|
pass
|
|
205
207
|
|
|
208
|
+
if _pending_error:
|
|
209
|
+
raise RuntimeError(_pending_error)
|
|
210
|
+
|
|
206
211
|
except KeyboardInterrupt:
|
|
207
212
|
try:
|
|
208
213
|
self.send_command(Cmd.RUN_STOP, timeout=0.5)
|
|
@@ -321,13 +326,14 @@ class AgentClient:
|
|
|
321
326
|
if port:
|
|
322
327
|
cmd.append(str(port))
|
|
323
328
|
|
|
329
|
+
proc = None
|
|
324
330
|
if background:
|
|
325
331
|
if sys.platform == 'win32':
|
|
326
332
|
startupinfo = subprocess.STARTUPINFO()
|
|
327
333
|
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
328
334
|
startupinfo.wShowWindow = 0
|
|
329
335
|
|
|
330
|
-
subprocess.Popen(
|
|
336
|
+
proc = subprocess.Popen(
|
|
331
337
|
cmd,
|
|
332
338
|
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS,
|
|
333
339
|
stdout=subprocess.DEVNULL,
|
|
@@ -335,7 +341,7 @@ class AgentClient:
|
|
|
335
341
|
startupinfo=startupinfo
|
|
336
342
|
)
|
|
337
343
|
else:
|
|
338
|
-
subprocess.Popen(
|
|
344
|
+
proc = subprocess.Popen(
|
|
339
345
|
cmd,
|
|
340
346
|
stdout=subprocess.DEVNULL,
|
|
341
347
|
stderr=subprocess.DEVNULL,
|
|
@@ -344,10 +350,12 @@ class AgentClient:
|
|
|
344
350
|
close_fds=True
|
|
345
351
|
)
|
|
346
352
|
else:
|
|
347
|
-
subprocess.Popen(cmd)
|
|
353
|
+
proc = subprocess.Popen(cmd)
|
|
348
354
|
|
|
349
|
-
for
|
|
355
|
+
for _ in range(100):
|
|
350
356
|
time.sleep(0.1)
|
|
357
|
+
if proc is not None and proc.poll() is not None:
|
|
358
|
+
raise RuntimeError(f"Failed to start agent (process exited with code {proc.returncode})")
|
|
351
359
|
if AgentClient.is_agent_running(port=port):
|
|
352
360
|
return True
|
|
353
361
|
|
|
@@ -2,11 +2,13 @@ import re
|
|
|
2
2
|
import sys
|
|
3
3
|
import time
|
|
4
4
|
import threading
|
|
5
|
+
from concurrent.futures import Future as ConcurrentFuture
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
7
|
from typing import Optional, Dict, Any, List, Tuple
|
|
7
8
|
|
|
8
9
|
from replx.protocol import ReplProtocol, create_storage
|
|
9
10
|
from replx.utils import parse_device_banner
|
|
11
|
+
from replx.utils.constants import CTRL_B, CTRL_C
|
|
10
12
|
from replx.utils.exceptions import TransportError
|
|
11
13
|
|
|
12
14
|
|
|
@@ -16,11 +18,11 @@ def _detect_device_info(transport, core: str, device: str = None) -> Tuple[str,
|
|
|
16
18
|
delay3 = 0.1 if sys.platform != "win32" else 0.2
|
|
17
19
|
|
|
18
20
|
try:
|
|
19
|
-
transport.write(b'\r
|
|
21
|
+
transport.write(b'\r' + CTRL_C)
|
|
20
22
|
time.sleep(delay1)
|
|
21
23
|
transport.reset_input_buffer()
|
|
22
24
|
|
|
23
|
-
transport.write(b'\r
|
|
25
|
+
transport.write(b'\r' + CTRL_B)
|
|
24
26
|
time.sleep(delay2)
|
|
25
27
|
|
|
26
28
|
res = transport.read_available()
|
|
@@ -104,6 +106,7 @@ class InteractiveSessionState:
|
|
|
104
106
|
class ReplSessionState:
|
|
105
107
|
active: bool = False
|
|
106
108
|
ppid: Optional[int] = None
|
|
109
|
+
reader_future: Optional[ConcurrentFuture] = None
|
|
107
110
|
reader_thread: Optional[threading.Thread] = None
|
|
108
111
|
output_buffer: bytes = b""
|
|
109
112
|
buffer_lock: threading.Lock = field(default_factory=threading.Lock)
|
|
@@ -117,6 +120,9 @@ class ReplSessionState:
|
|
|
117
120
|
def stop(self):
|
|
118
121
|
self.active = False
|
|
119
122
|
self.ppid = None
|
|
123
|
+
if self.reader_future is not None:
|
|
124
|
+
self.reader_future.cancel()
|
|
125
|
+
self.reader_future = None
|
|
120
126
|
if self.reader_thread:
|
|
121
127
|
self.reader_thread.join(timeout=1)
|
|
122
128
|
self.reader_thread = None
|
|
@@ -152,7 +158,6 @@ class BoardConnection:
|
|
|
152
158
|
version: str = "?"
|
|
153
159
|
device_root_fs: str = "/"
|
|
154
160
|
|
|
155
|
-
# Unique board ID from machine.unique_id().hex() - lazy evaluated on first command
|
|
156
161
|
board_id: Optional[str] = None
|
|
157
162
|
|
|
158
163
|
busy: bool = False
|
|
@@ -428,10 +433,10 @@ class ConnectionManager:
|
|
|
428
433
|
conn = self.get_connection(port)
|
|
429
434
|
if conn and conn.repl_protocol:
|
|
430
435
|
try:
|
|
431
|
-
transport = conn.repl_protocol.
|
|
432
|
-
transport.write(
|
|
436
|
+
transport = conn.repl_protocol.transport
|
|
437
|
+
transport.write(CTRL_C)
|
|
433
438
|
time.sleep(0.05)
|
|
434
|
-
transport.write(
|
|
439
|
+
transport.write(CTRL_B)
|
|
435
440
|
time.sleep(0.1)
|
|
436
441
|
transport.reset_input_buffer()
|
|
437
442
|
except Exception:
|
|
@@ -461,8 +466,13 @@ class ConnectionManager:
|
|
|
461
466
|
|
|
462
467
|
if conn.repl_protocol:
|
|
463
468
|
try:
|
|
464
|
-
transport = conn.repl_protocol.
|
|
469
|
+
transport = conn.repl_protocol.transport
|
|
465
470
|
if transport:
|
|
471
|
+
try:
|
|
472
|
+
transport.write(CTRL_B)
|
|
473
|
+
time.sleep(0.1 if sys.platform == 'win32' else 0.05)
|
|
474
|
+
except Exception:
|
|
475
|
+
pass
|
|
466
476
|
transport.close()
|
|
467
477
|
except Exception:
|
|
468
478
|
pass
|