replx 1.6__tar.gz → 1.6.1__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.6/replx.egg-info → replx-1.6.1}/PKG-INFO +1 -1
- {replx-1.6 → replx-1.6.1}/replx/__init__.py +1 -1
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/client/core.py +41 -8
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/server/connection_manager.py +0 -5
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/server/handlers/exec.py +27 -3
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/server/handlers/session.py +0 -6
- {replx-1.6 → replx-1.6.1}/replx/cli/app.py +2 -2
- {replx-1.6 → replx-1.6.1}/replx/cli/commands/device.py +0 -35
- {replx-1.6 → replx-1.6.1}/replx/cli/commands/exec.py +205 -60
- {replx-1.6 → replx-1.6.1}/replx/cli/commands/file.py +16 -209
- {replx-1.6 → replx-1.6.1}/replx/cli/commands/firmware.py +3 -35
- {replx-1.6 → replx-1.6.1}/replx/cli/commands/package.py +1 -98
- {replx-1.6 → replx-1.6.1}/replx/cli/commands/utility.py +45 -98
- {replx-1.6 → replx-1.6.1}/replx/cli/config.py +0 -1
- {replx-1.6 → replx-1.6.1}/replx/cli/helpers/compiler.py +0 -1
- {replx-1.6 → replx-1.6.1}/replx/cli/helpers/output.py +1 -2
- {replx-1.6 → replx-1.6.1}/replx/cli/helpers/updater.py +0 -1
- {replx-1.6 → replx-1.6.1}/replx/protocol/__init__.py +0 -1
- {replx-1.6 → replx-1.6.1}/replx/protocol/repl.py +7 -24
- {replx-1.6 → replx-1.6.1}/replx/protocol/storage.py +1 -45
- {replx-1.6 → replx-1.6.1}/replx/terminal.py +161 -7
- {replx-1.6 → replx-1.6.1/replx.egg-info}/PKG-INFO +1 -1
- {replx-1.6 → replx-1.6.1}/replx.egg-info/SOURCES.txt +1 -16
- replx-1.6.1/test/test_line_mode_terminal.py +428 -0
- replx-1.6.1/test/test_termio.py +53 -0
- replx-1.6/replx/tests/__init__.py +0 -0
- replx-1.6/replx/tests/test_agent_asyncio.py +0 -319
- replx-1.6/replx/tests/test_agent_port_canonicalization.py +0 -43
- replx-1.6/replx/tests/test_agent_thread_pool.py +0 -216
- replx-1.6/replx/tests/test_compiler_arch.py +0 -17
- replx-1.6/replx/tests/test_connection_info_lookup.py +0 -30
- replx-1.6/replx/tests/test_device_info_esp_multi_core.py +0 -56
- replx-1.6/replx/tests/test_disconnect_cleanup.py +0 -57
- replx-1.6/replx/tests/test_lock_cleanup.py +0 -194
- replx-1.6/replx/tests/test_pkg_local_version.py +0 -44
- replx-1.6/replx/tests/test_pkg_search_scope_filter.py +0 -82
- replx-1.6/replx/tests/test_repl_reader_task.py +0 -313
- replx-1.6/replx/tests/test_session_disconnect_releases_shared_port.py +0 -47
- replx-1.6/replx/tests/test_session_id_fallback.py +0 -32
- replx-1.6/replx/tests/test_shutdown_status_message.py +0 -56
- replx-1.6/replx/tests/test_windows_com_port_normalization.py +0 -69
- replx-1.6/test/test_termio.py +0 -70
- {replx-1.6 → replx-1.6.1}/LICENSE +0 -0
- {replx-1.6 → replx-1.6.1}/README.md +0 -0
- {replx-1.6 → replx-1.6.1}/pyproject.toml +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/__init__.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/__init__.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/client/__init__.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/client/session.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/protocol.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/server/__init__.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/server/__main__.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/server/command_dispatcher.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/server/core.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/server/handlers/__init__.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/server/handlers/filesystem.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/server/handlers/repl.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/server/handlers/transfer.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/agent/server/session_manager.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/commands/__init__.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/connection.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/helpers/__init__.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/helpers/environment.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/helpers/registry.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/helpers/scanner.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/cli/helpers/store.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/commands.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/transport/__init__.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/transport/base.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/transport/serial.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/_thread.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/aioble/__init__.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/array.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/asyncio/__init__.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/binascii.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/bluetooth.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/builtins.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/cmath.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/collections.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/cryptolib.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/deflate.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/errno.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/framebuf.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/gc.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/hashlib.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/heapq.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/io.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/json.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/lwip.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/machine.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/math.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/micropython.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/mip/__init__.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/network.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/ntptime.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/os.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/platform.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/random.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/re.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/requests/__init__.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/select.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/socket.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/ssl.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/struct.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/sys.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/time.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/tls.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/uasyncio.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/uctypes.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/urequests.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm/vfs.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/binascii.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/errno.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/hashlib.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/io.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/json.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/machine.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/math.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/micropython.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/network.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/os.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/select.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/socket.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/ssl.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/struct.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/sys.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/time.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/ubinascii.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/ucryptolib.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/uerrno.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/uhashlib.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/uio.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/ujson.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/umachine.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/uos.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/uselect.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/usocket.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/ussl.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/ustruct.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/comm_separate/EFR32MG/utime.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/core/ESP32/aioespnow.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/core/ESP32/esp.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/core/ESP32/esp32.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/core/ESP32/espnow.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/core/MIMXRT1062DVJ6A/mimxrt.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/typehints/core/RP2350/rp2.pyi +0 -0
- {replx-1.6 → replx-1.6.1}/replx/utils/__init__.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/utils/constants.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/utils/device_info.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx/utils/exceptions.py +0 -0
- {replx-1.6 → replx-1.6.1}/replx.egg-info/dependency_links.txt +0 -0
- {replx-1.6 → replx-1.6.1}/replx.egg-info/entry_points.txt +0 -0
- {replx-1.6 → replx-1.6.1}/replx.egg-info/requires.txt +0 -0
- {replx-1.6 → replx-1.6.1}/replx.egg-info/top_level.txt +0 -0
- {replx-1.6 → replx-1.6.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: replx
|
|
3
|
-
Version: 1.6
|
|
3
|
+
Version: 1.6.1
|
|
4
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
5
|
Author-email: "chanmin.park" <devcamp@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -2,6 +2,7 @@ import os
|
|
|
2
2
|
import sys
|
|
3
3
|
import socket
|
|
4
4
|
import time
|
|
5
|
+
import tempfile
|
|
5
6
|
from typing import Dict, Any, Optional, Callable
|
|
6
7
|
|
|
7
8
|
from replx.utils.constants import DEFAULT_AGENT_PORT, AGENT_HOST, MAX_UDP_SIZE
|
|
@@ -327,7 +328,18 @@ class AgentClient:
|
|
|
327
328
|
cmd.append(str(port))
|
|
328
329
|
|
|
329
330
|
proc = None
|
|
331
|
+
stderr_file = None
|
|
332
|
+
stderr_path = None
|
|
333
|
+
|
|
330
334
|
if background:
|
|
335
|
+
try:
|
|
336
|
+
fd, stderr_path = tempfile.mkstemp(prefix='replx_agent_', suffix='.err')
|
|
337
|
+
os.close(fd)
|
|
338
|
+
stderr_file = open(stderr_path, 'w', encoding='utf-8')
|
|
339
|
+
except Exception:
|
|
340
|
+
stderr_file = None
|
|
341
|
+
stderr_path = None
|
|
342
|
+
|
|
331
343
|
if sys.platform == 'win32':
|
|
332
344
|
startupinfo = subprocess.STARTUPINFO()
|
|
333
345
|
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
@@ -337,27 +349,48 @@ class AgentClient:
|
|
|
337
349
|
cmd,
|
|
338
350
|
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS,
|
|
339
351
|
stdout=subprocess.DEVNULL,
|
|
340
|
-
stderr=subprocess.DEVNULL,
|
|
352
|
+
stderr=stderr_file if stderr_file else subprocess.DEVNULL,
|
|
341
353
|
startupinfo=startupinfo
|
|
342
354
|
)
|
|
343
355
|
else:
|
|
344
356
|
proc = subprocess.Popen(
|
|
345
357
|
cmd,
|
|
346
358
|
stdout=subprocess.DEVNULL,
|
|
347
|
-
stderr=subprocess.DEVNULL,
|
|
359
|
+
stderr=stderr_file if stderr_file else subprocess.DEVNULL,
|
|
348
360
|
stdin=subprocess.DEVNULL,
|
|
349
361
|
start_new_session=True,
|
|
350
362
|
close_fds=True
|
|
351
363
|
)
|
|
364
|
+
|
|
365
|
+
if stderr_file:
|
|
366
|
+
stderr_file.close()
|
|
367
|
+
stderr_file = None
|
|
352
368
|
else:
|
|
353
369
|
proc = subprocess.Popen(cmd)
|
|
354
370
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
371
|
+
try:
|
|
372
|
+
for _ in range(100):
|
|
373
|
+
time.sleep(0.1)
|
|
374
|
+
if proc is not None and proc.poll() is not None:
|
|
375
|
+
detail = ''
|
|
376
|
+
if stderr_path:
|
|
377
|
+
try:
|
|
378
|
+
with open(stderr_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
379
|
+
detail = f.read().strip()
|
|
380
|
+
except Exception:
|
|
381
|
+
pass
|
|
382
|
+
msg = f"Failed to start agent (process exited with code {proc.returncode})"
|
|
383
|
+
if detail:
|
|
384
|
+
msg += f"\n{detail}"
|
|
385
|
+
raise RuntimeError(msg)
|
|
386
|
+
if AgentClient.is_agent_running(port=port):
|
|
387
|
+
return True
|
|
388
|
+
finally:
|
|
389
|
+
if stderr_path:
|
|
390
|
+
try:
|
|
391
|
+
os.unlink(stderr_path)
|
|
392
|
+
except Exception:
|
|
393
|
+
pass
|
|
361
394
|
|
|
362
395
|
raise RuntimeError("Failed to start agent (timeout)")
|
|
363
396
|
|
|
@@ -455,9 +455,6 @@ class ConnectionManager:
|
|
|
455
455
|
|
|
456
456
|
conn = self._connections.pop(key)
|
|
457
457
|
|
|
458
|
-
# Remember whether a detached (user) script was running before clearing the flag.
|
|
459
|
-
# If so, the device is executing paste-mode code on its UART, NOT in raw REPL.
|
|
460
|
-
# Sending CTRL_B in that state would inject \x02 into the running script's stdin.
|
|
461
458
|
was_detached = conn.is_detached()
|
|
462
459
|
conn.stop_detached()
|
|
463
460
|
|
|
@@ -474,8 +471,6 @@ class ConnectionManager:
|
|
|
474
471
|
if transport:
|
|
475
472
|
try:
|
|
476
473
|
if not was_detached:
|
|
477
|
-
# Only send CTRL_B to leave raw REPL when no user script
|
|
478
|
-
# was running; otherwise the byte contaminates the script stdin.
|
|
479
474
|
transport.write(CTRL_B)
|
|
480
475
|
time.sleep(0.1 if sys.platform == 'win32' else 0.05)
|
|
481
476
|
except Exception:
|
|
@@ -204,11 +204,35 @@ class ExecCommandsMixin:
|
|
|
204
204
|
if not conn or not conn.repl_protocol:
|
|
205
205
|
send_error("Not connected")
|
|
206
206
|
return
|
|
207
|
-
|
|
207
|
+
|
|
208
208
|
with conn.interactive.lock:
|
|
209
209
|
if conn.interactive.active:
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
thread = conn.interactive.thread
|
|
211
|
+
if thread is not None and thread.is_alive():
|
|
212
|
+
conn.interactive.stop_requested = True
|
|
213
|
+
try:
|
|
214
|
+
conn.repl_protocol.interrupt()
|
|
215
|
+
except Exception:
|
|
216
|
+
pass
|
|
217
|
+
else:
|
|
218
|
+
conn.interactive.active = False
|
|
219
|
+
conn.interactive.thread = None
|
|
220
|
+
|
|
221
|
+
for _ in range(20):
|
|
222
|
+
with conn.interactive.lock:
|
|
223
|
+
if not conn.interactive.active:
|
|
224
|
+
break
|
|
225
|
+
time.sleep(0.05)
|
|
226
|
+
|
|
227
|
+
with conn.interactive.lock:
|
|
228
|
+
if conn.interactive.active:
|
|
229
|
+
thread = conn.interactive.thread
|
|
230
|
+
if thread is None or not thread.is_alive():
|
|
231
|
+
conn.interactive.active = False
|
|
232
|
+
conn.interactive.thread = None
|
|
233
|
+
else:
|
|
234
|
+
send_error("Interactive session already active on this connection")
|
|
235
|
+
return
|
|
212
236
|
|
|
213
237
|
repl = conn.repl_protocol
|
|
214
238
|
if conn.is_detached():
|
|
@@ -71,7 +71,6 @@ class SessionCommandsMixin:
|
|
|
71
71
|
|
|
72
72
|
self._add_connection(conn_key, new_conn)
|
|
73
73
|
|
|
74
|
-
# Pass original port to session
|
|
75
74
|
self.session_manager.add_connection_to_session(
|
|
76
75
|
ppid, port,
|
|
77
76
|
as_foreground=as_foreground,
|
|
@@ -113,7 +112,6 @@ class SessionCommandsMixin:
|
|
|
113
112
|
ports_to_close = list(session.get_all_connections())
|
|
114
113
|
freed_port = "all"
|
|
115
114
|
elif port:
|
|
116
|
-
# Find port in session
|
|
117
115
|
found = None
|
|
118
116
|
for conn in session.get_all_connections():
|
|
119
117
|
if conn == port:
|
|
@@ -141,8 +139,6 @@ class SessionCommandsMixin:
|
|
|
141
139
|
if not self.session_manager.has_sessions():
|
|
142
140
|
def delayed_shutdown():
|
|
143
141
|
time.sleep(0.3)
|
|
144
|
-
# cleanup() properly sets _stop_event to stop the asyncio loop
|
|
145
|
-
# and releases the UDP port so a new agent can bind it immediately.
|
|
146
142
|
self.cleanup()
|
|
147
143
|
|
|
148
144
|
shutdown_thread = threading.Thread(target=delayed_shutdown, daemon=True)
|
|
@@ -178,7 +174,6 @@ class SessionCommandsMixin:
|
|
|
178
174
|
|
|
179
175
|
old_fg = session.foreground
|
|
180
176
|
|
|
181
|
-
# Pass original port, not normalized
|
|
182
177
|
session.add_connection(port, as_foreground=True)
|
|
183
178
|
|
|
184
179
|
return {
|
|
@@ -189,7 +184,6 @@ class SessionCommandsMixin:
|
|
|
189
184
|
}
|
|
190
185
|
|
|
191
186
|
def _cmd_set_default(self, ctx: CommandContext, port: str = None, update_session: bool = False) -> dict:
|
|
192
|
-
"""Set default port. If update_session=True, also update calling session's default_port."""
|
|
193
187
|
port = port or ctx.explicit_port
|
|
194
188
|
if port:
|
|
195
189
|
self._set_default_port(port)
|
|
@@ -513,7 +513,7 @@ def main():
|
|
|
513
513
|
break
|
|
514
514
|
first_nonopt = sys.argv[first_nonopt_idx] if first_nonopt_idx is not None else None
|
|
515
515
|
|
|
516
|
-
run_opts = {'-n', '--non-interactive', '-e', '--echo', '-d', '--device'}
|
|
516
|
+
run_opts = {'-n', '--non-interactive', '-e', '--echo', '-d', '--device', '--line', '--hex'}
|
|
517
517
|
has_device_opt = bool(run_opts & {'-d', '--device'} & set(args))
|
|
518
518
|
|
|
519
519
|
script_files = []
|
|
@@ -538,7 +538,7 @@ def main():
|
|
|
538
538
|
|
|
539
539
|
if should_inject_run:
|
|
540
540
|
opt_idx = next((i for i, a in enumerate(sys.argv[1:], 1) if a in run_opts), None)
|
|
541
|
-
insert_at = opt_idx if opt_idx is not None else script_arg_idx
|
|
541
|
+
insert_at = min(opt_idx, script_arg_idx) if opt_idx is not None else script_arg_idx
|
|
542
542
|
sys.argv.insert(insert_at, 'run')
|
|
543
543
|
|
|
544
544
|
first_nonopt_idx = next((i for i, a in enumerate(sys.argv[1:], 1) if not a.startswith('-')), None)
|
|
@@ -35,11 +35,6 @@ from ..app import app
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def _normalize_path_for_comparison(path: str) -> str:
|
|
38
|
-
"""Normalize file path for comparison.
|
|
39
|
-
|
|
40
|
-
Windows: case-insensitive
|
|
41
|
-
Linux/macOS: case-sensitive
|
|
42
|
-
"""
|
|
43
38
|
path = os.path.normpath(path)
|
|
44
39
|
if sys.platform.startswith("win"):
|
|
45
40
|
return path.lower()
|
|
@@ -48,12 +43,6 @@ def _normalize_path_for_comparison(path: str) -> str:
|
|
|
48
43
|
|
|
49
44
|
|
|
50
45
|
def _serial_port_cmp_key(port: str) -> str:
|
|
51
|
-
"""Comparison key for serial ports.
|
|
52
|
-
|
|
53
|
-
Policy:
|
|
54
|
-
- Display: keep OS-provided casing.
|
|
55
|
-
- Compare: on Windows, treat COM ports case-insensitively.
|
|
56
|
-
"""
|
|
57
46
|
if port is None:
|
|
58
47
|
return ""
|
|
59
48
|
p = str(port).strip()
|
|
@@ -61,10 +50,6 @@ def _serial_port_cmp_key(port: str) -> str:
|
|
|
61
50
|
|
|
62
51
|
|
|
63
52
|
def _serial_port_display(port: str) -> str:
|
|
64
|
-
"""Format a serial port name for display.
|
|
65
|
-
|
|
66
|
-
Requirement: on Windows, always show port names in uppercase.
|
|
67
|
-
"""
|
|
68
53
|
if port is None:
|
|
69
54
|
return ""
|
|
70
55
|
p = str(port).strip()
|
|
@@ -72,12 +57,6 @@ def _serial_port_display(port: str) -> str:
|
|
|
72
57
|
|
|
73
58
|
|
|
74
59
|
def _resolve_os_serial_port_name(port: str) -> str:
|
|
75
|
-
"""Resolve port name to the OS-enumerated spelling (best-effort).
|
|
76
|
-
|
|
77
|
-
This is mainly for Windows where users might type `com1` but the OS reports
|
|
78
|
-
`COM1`. We keep display as OS-provided and use case-insensitive comparison
|
|
79
|
-
separately.
|
|
80
|
-
"""
|
|
81
60
|
if not port:
|
|
82
61
|
return port
|
|
83
62
|
p = str(port).strip()
|
|
@@ -158,7 +137,6 @@ def _create_vscode_files_and_typehints(vscode_dir: str, core: str, device: str,
|
|
|
158
137
|
|
|
159
138
|
extra_paths = []
|
|
160
139
|
|
|
161
|
-
# 1. MicroPython standard library typehints (included in replx tool)
|
|
162
140
|
from replx.utils.device_info import is_std_micropython
|
|
163
141
|
if is_std_micropython(core):
|
|
164
142
|
comm_path = StoreManager.comm_typehints_path()
|
|
@@ -167,27 +145,22 @@ def _create_vscode_files_and_typehints(vscode_dir: str, core: str, device: str,
|
|
|
167
145
|
if comm_path and os.path.isdir(comm_path):
|
|
168
146
|
extra_paths.append(comm_path)
|
|
169
147
|
|
|
170
|
-
# 2. Core builtin typehints (included in replx tool)
|
|
171
148
|
if core:
|
|
172
149
|
core_builtin = StoreManager.core_typehints_path(core)
|
|
173
150
|
if core_builtin and os.path.isdir(core_builtin):
|
|
174
151
|
extra_paths.append(core_builtin)
|
|
175
152
|
|
|
176
|
-
# 3. Core library typehints (downloaded via 'replx pkg download')
|
|
177
153
|
if core:
|
|
178
154
|
core_lib_typehints = os.path.join(StoreManager.pkg_root(), "core", core, "typehints")
|
|
179
155
|
if os.path.isdir(core_lib_typehints):
|
|
180
156
|
extra_paths.append(core_lib_typehints)
|
|
181
157
|
|
|
182
|
-
# 4. Device builtin typehints (included in replx tool)
|
|
183
158
|
if device:
|
|
184
159
|
device_path = device_name_to_path(device)
|
|
185
160
|
device_builtin = StoreManager.device_typehints_path(device_path)
|
|
186
161
|
if device_builtin and os.path.isdir(device_builtin):
|
|
187
162
|
extra_paths.append(device_builtin)
|
|
188
163
|
|
|
189
|
-
# 5. Device library typehints (downloaded via 'replx pkg download')
|
|
190
|
-
# Note: device name like 'ticle-lite' becomes 'ticle_lite' in filesystem
|
|
191
164
|
if device:
|
|
192
165
|
device_path = device_name_to_path(device)
|
|
193
166
|
device_typehints = os.path.join(StoreManager.pkg_root(), "device", device_path, "typehints")
|
|
@@ -290,7 +263,6 @@ Run this once per project folder to set up your workspace.
|
|
|
290
263
|
port = global_opts.get('port')
|
|
291
264
|
agent_port = global_opts.get('agent_port')
|
|
292
265
|
|
|
293
|
-
# Store/use the OS-enumerated port spelling when possible.
|
|
294
266
|
port = _resolve_os_serial_port_name(port)
|
|
295
267
|
|
|
296
268
|
if not port:
|
|
@@ -306,7 +278,6 @@ Run this once per project folder to set up your workspace.
|
|
|
306
278
|
|
|
307
279
|
env_path = _find_env_file()
|
|
308
280
|
if agent_port is None:
|
|
309
|
-
# Use original port for connection lookup
|
|
310
281
|
if env_path and port:
|
|
311
282
|
existing_config = _get_connection_config(env_path, port)
|
|
312
283
|
if existing_config and existing_config.get('agent_port'):
|
|
@@ -356,7 +327,6 @@ Run this once per project folder to set up your workspace.
|
|
|
356
327
|
env_path = os.path.join(vscode_dir, ".replx")
|
|
357
328
|
workspace = os.path.dirname(vscode_dir)
|
|
358
329
|
|
|
359
|
-
# Use original port for storing connection
|
|
360
330
|
_update_connection_config(
|
|
361
331
|
env_path, port,
|
|
362
332
|
version=version, core=core, device=device,
|
|
@@ -391,7 +361,6 @@ Run this once per project folder to set up your workspace.
|
|
|
391
361
|
)
|
|
392
362
|
raise typer.Exit()
|
|
393
363
|
else:
|
|
394
|
-
# Use original port for session setup
|
|
395
364
|
try:
|
|
396
365
|
with AgentClient(port=agent_port, device_port=port) as client:
|
|
397
366
|
result = client.send_command(
|
|
@@ -410,7 +379,6 @@ Run this once per project folder to set up your workspace.
|
|
|
410
379
|
vscode_dir = _find_or_create_vscode_dir()
|
|
411
380
|
env_path = os.path.join(vscode_dir, ".replx")
|
|
412
381
|
|
|
413
|
-
# Use original port for storing connection
|
|
414
382
|
_update_connection_config(
|
|
415
383
|
env_path, port,
|
|
416
384
|
version=STATE.version, core=STATE.core, device=STATE.device,
|
|
@@ -478,7 +446,6 @@ Run this once per project folder to set up your workspace.
|
|
|
478
446
|
)
|
|
479
447
|
raise typer.Exit(1)
|
|
480
448
|
|
|
481
|
-
# Use original port for session setup
|
|
482
449
|
try:
|
|
483
450
|
with AgentClient(port=agent_port, device_port=port) as client:
|
|
484
451
|
result = client.send_command('session_setup',
|
|
@@ -542,7 +509,6 @@ Run this once per project folder to set up your workspace.
|
|
|
542
509
|
except Exception:
|
|
543
510
|
pass
|
|
544
511
|
|
|
545
|
-
# Use original port for storing connection
|
|
546
512
|
set_as_default = True
|
|
547
513
|
|
|
548
514
|
if clean_mode:
|
|
@@ -1022,7 +988,6 @@ WIFI_STATUS_MESSAGES = {
|
|
|
1022
988
|
}
|
|
1023
989
|
|
|
1024
990
|
def _wifi_status(client: AgentClient):
|
|
1025
|
-
"""Show WiFi status."""
|
|
1026
991
|
code = '''
|
|
1027
992
|
import network
|
|
1028
993
|
import json
|