replx 1.9__tar.gz → 1.10__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.9/replx.egg-info → replx-1.10}/PKG-INFO +1 -1
- {replx-1.9 → replx-1.10}/replx/__init__.py +1 -1
- replx-1.10/replx/__main__.py +14 -0
- {replx-1.9 → replx-1.10}/replx/cli/__init__.py +8 -1
- replx-1.10/replx/cli/agent/__init__.py +27 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/core.py +1 -1
- {replx-1.9 → replx-1.10}/replx/cli/app.py +110 -41
- {replx-1.9 → replx-1.10}/replx/cli/commands/device.py +35 -11
- {replx-1.9 → replx-1.10}/replx/cli/commands/exec.py +3 -3
- {replx-1.9 → replx-1.10}/replx/cli/commands/file.py +27 -2
- {replx-1.9 → replx-1.10}/replx/cli/commands/utility.py +2 -1
- {replx-1.9 → replx-1.10}/replx/cli/config.py +98 -3
- {replx-1.9 → replx-1.10}/replx/cli/helpers/__init__.py +24 -6
- {replx-1.9 → replx-1.10}/replx/cli/helpers/output.py +23 -7
- {replx-1.9 → replx-1.10}/replx/cli/helpers/scanner.py +33 -5
- {replx-1.9 → replx-1.10/replx.egg-info}/PKG-INFO +1 -1
- {replx-1.9 → replx-1.10}/replx.egg-info/SOURCES.txt +1 -0
- replx-1.9/replx/cli/agent/__init__.py +0 -14
- {replx-1.9 → replx-1.10}/LICENSE +0 -0
- {replx-1.9 → replx-1.10}/README.md +0 -0
- {replx-1.9 → replx-1.10}/pyproject.toml +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/client/__init__.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/client/core.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/client/session.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/protocol.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/__init__.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/__main__.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/command_dispatcher.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/connection_manager.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/handlers/__init__.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/handlers/exec.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/handlers/filesystem.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/handlers/i2c.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/handlers/repl.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/handlers/session.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/handlers/spi.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/handlers/transfer.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/handlers/uart.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/agent/server/session_manager.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/commands/__init__.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/commands/_common.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/commands/adc.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/commands/firmware.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/commands/gpio.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/commands/i2c.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/commands/package.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/commands/pwm.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/commands/spi.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/commands/uart.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/commands/wifi.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/connection.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/helpers/compiler.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/helpers/environment.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/helpers/registry.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/helpers/store.py +0 -0
- {replx-1.9 → replx-1.10}/replx/cli/helpers/updater.py +0 -0
- {replx-1.9 → replx-1.10}/replx/commands.py +0 -0
- {replx-1.9 → replx-1.10}/replx/protocol/__init__.py +0 -0
- {replx-1.9 → replx-1.10}/replx/protocol/repl.py +0 -0
- {replx-1.9 → replx-1.10}/replx/protocol/storage.py +0 -0
- {replx-1.9 → replx-1.10}/replx/terminal.py +0 -0
- {replx-1.9 → replx-1.10}/replx/transport/__init__.py +0 -0
- {replx-1.9 → replx-1.10}/replx/transport/serial.py +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/_thread.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/aioble/__init__.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/array.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/asyncio/__init__.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/binascii.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/bluetooth.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/builtins.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/cmath.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/collections.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/cryptolib.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/deflate.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/errno.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/framebuf.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/gc.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/hashlib.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/heapq.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/io.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/json.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/lwip.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/machine.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/math.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/micropython.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/mip/__init__.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/network.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/ntptime.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/os.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/platform.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/random.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/re.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/requests/__init__.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/select.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/socket.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/ssl.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/struct.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/sys.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/time.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/tls.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/uasyncio.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/uctypes.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/urequests.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm/vfs.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/binascii.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/digi/__init__.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/digi/ble.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/errno.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/hashlib.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/io.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/json.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/machine.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/math.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/micropython.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/network.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/os.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/select.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/socket.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/ssl.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/struct.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/sys.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/time.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/ubinascii.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/ucryptolib.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/uerrno.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/uhashlib.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/uio.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/ujson.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/umachine.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/uos.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/uselect.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/usocket.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/ussl.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/ustruct.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/utime.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/xbee/__init__.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/xbee/modem_status.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/comm_separate/EFR32MG/xbee/relay.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/core/ESP32/aioespnow.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/core/ESP32/esp.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/core/ESP32/esp32.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/core/ESP32/espnow.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/core/MIMXRT1062DVJ6A/mimxrt.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/typehints/core/RP2350/rp2.pyi +0 -0
- {replx-1.9 → replx-1.10}/replx/utils/__init__.py +0 -0
- {replx-1.9 → replx-1.10}/replx/utils/constants.py +0 -0
- {replx-1.9 → replx-1.10}/replx/utils/device_info.py +0 -0
- {replx-1.9 → replx-1.10}/replx/utils/exceptions.py +0 -0
- {replx-1.9 → replx-1.10}/replx.egg-info/dependency_links.txt +0 -0
- {replx-1.9 → replx-1.10}/replx.egg-info/entry_points.txt +0 -0
- {replx-1.9 → replx-1.10}/replx.egg-info/requires.txt +0 -0
- {replx-1.9 → replx-1.10}/replx.egg-info/top_level.txt +0 -0
- {replx-1.9 → replx-1.10}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: replx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10
|
|
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
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Allow ``python -m replx ...`` as a fast-startup alias for the ``replx`` script.
|
|
2
|
+
|
|
3
|
+
On Windows the ``replx.exe`` console-script launcher (installed by pip from
|
|
4
|
+
``[project.scripts]``) goes through a zipapp-style entry that adds ~450ms to
|
|
5
|
+
every invocation. ``python -m replx`` bypasses that launcher and is noticeably
|
|
6
|
+
snappier for users who care about CLI startup latency.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from replx.cli.app import main
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
sys.exit(main())
|
|
@@ -3,7 +3,6 @@ from .config import (
|
|
|
3
3
|
ConfigManager, AgentPortManager, ConnectionResolver,
|
|
4
4
|
)
|
|
5
5
|
from replx.utils.constants import DEFAULT_AGENT_PORT, MIN_AGENT_PORT, MAX_AGENT_PORT
|
|
6
|
-
from .app import app, main
|
|
7
6
|
|
|
8
7
|
__all__ = [
|
|
9
8
|
'RuntimeState', 'STATE', 'GLOBAL_OPTIONS',
|
|
@@ -11,3 +10,11 @@ __all__ = [
|
|
|
11
10
|
'DEFAULT_AGENT_PORT', 'MIN_AGENT_PORT', 'MAX_AGENT_PORT',
|
|
12
11
|
'app', 'main'
|
|
13
12
|
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def __getattr__(name):
|
|
16
|
+
if name in {'app', 'main'}:
|
|
17
|
+
from .app import app, main
|
|
18
|
+
|
|
19
|
+
return {'app': app, 'main': main}[name]
|
|
20
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
from .protocol import AgentProtocol
|
|
3
|
+
from .client import AgentClient, get_session_id, get_cached_session_id, clear_session_cache
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
'AgentProtocol',
|
|
7
|
+
'AgentClient',
|
|
8
|
+
'get_session_id',
|
|
9
|
+
'get_cached_session_id',
|
|
10
|
+
'clear_session_cache',
|
|
11
|
+
'AgentServer',
|
|
12
|
+
'agent_main',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def __getattr__(name):
|
|
17
|
+
# The server stack pulls in ``replx.terminal`` (~150ms) plus all command
|
|
18
|
+
# handlers; CLI clients never need it. Defer until something actually asks.
|
|
19
|
+
if name == 'AgentServer':
|
|
20
|
+
from .server import AgentServer
|
|
21
|
+
|
|
22
|
+
return AgentServer
|
|
23
|
+
if name == 'agent_main':
|
|
24
|
+
from .server import main as agent_main
|
|
25
|
+
|
|
26
|
+
return agent_main
|
|
27
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -334,7 +334,7 @@ class AgentServer(
|
|
|
334
334
|
)
|
|
335
335
|
self._datagram_transport = transport
|
|
336
336
|
self.running = True
|
|
337
|
-
print(f'replx agent started
|
|
337
|
+
print(f'replx agent started - listening on {AGENT_HOST}:{self.agent_port} (UDP)')
|
|
338
338
|
|
|
339
339
|
heartbeat_task = asyncio.create_task(
|
|
340
340
|
self._heartbeat_coro(), name='replx-heartbeat'
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
import importlib
|
|
2
3
|
from typing import Optional
|
|
3
4
|
import typer
|
|
4
5
|
from rich.panel import Panel
|
|
5
6
|
|
|
7
|
+
if __name__ == '__main__':
|
|
8
|
+
sys.modules['replx.cli.app'] = sys.modules[__name__]
|
|
9
|
+
|
|
6
10
|
from replx import __version__
|
|
7
11
|
from .helpers.output import OutputHelper, get_panel_box, CONSOLE_WIDTH
|
|
8
|
-
from .helpers.updater import UpdateChecker
|
|
9
|
-
from .helpers.environment import EnvironmentManager
|
|
10
|
-
from .connection import _ensure_connected, _create_agent_client
|
|
11
12
|
from .config import STATE, _set_global_options, _find_env_file, _get_theme_config
|
|
12
13
|
|
|
13
14
|
|
|
@@ -118,32 +119,43 @@ def _get_console():
|
|
|
118
119
|
return OutputHelper.make_console(width=CONSOLE_WIDTH)
|
|
119
120
|
|
|
120
121
|
|
|
121
|
-
|
|
122
|
-
import typer.rich_utils
|
|
123
|
-
|
|
124
|
-
def _patched_get_rich_console():
|
|
125
|
-
return _get_console()
|
|
126
|
-
|
|
127
|
-
typer.rich_utils._get_rich_console = _patched_get_rich_console
|
|
128
|
-
except ImportError:
|
|
129
|
-
pass
|
|
122
|
+
_RICH_HELP_CONFIGURED = False
|
|
130
123
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
124
|
+
|
|
125
|
+
def _configure_rich_help_rendering() -> None:
|
|
126
|
+
global _RICH_HELP_CONFIGURED
|
|
127
|
+
if _RICH_HELP_CONFIGURED:
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
import typer.rich_utils
|
|
132
|
+
|
|
133
|
+
def _patched_get_rich_console():
|
|
134
|
+
return _get_console()
|
|
135
|
+
|
|
136
|
+
typer.rich_utils._get_rich_console = _patched_get_rich_console
|
|
137
|
+
except ImportError:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
from typer.core import RichCommand
|
|
142
|
+
|
|
143
|
+
original_format_help = RichCommand.format_help
|
|
144
|
+
|
|
145
|
+
def _format_help_width(self, ctx, formatter):
|
|
146
|
+
old_console = getattr(self, '_rich_console', None)
|
|
147
|
+
try:
|
|
148
|
+
self._rich_console = OutputHelper.make_console(width=CONSOLE_WIDTH)
|
|
149
|
+
return original_format_help(self, ctx, formatter)
|
|
150
|
+
finally:
|
|
151
|
+
if old_console is not None:
|
|
152
|
+
self._rich_console = old_console
|
|
153
|
+
|
|
154
|
+
RichCommand.format_help = _format_help_width
|
|
155
|
+
except ImportError:
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
_RICH_HELP_CONFIGURED = True
|
|
147
159
|
|
|
148
160
|
|
|
149
161
|
import click
|
|
@@ -303,6 +315,64 @@ app = typer.Typer(
|
|
|
303
315
|
)
|
|
304
316
|
|
|
305
317
|
|
|
318
|
+
_COMMAND_MODULES = {
|
|
319
|
+
'get': 'file',
|
|
320
|
+
'cat': 'file',
|
|
321
|
+
'put': 'file',
|
|
322
|
+
'ls': 'file',
|
|
323
|
+
'cp': 'file',
|
|
324
|
+
'mv': 'file',
|
|
325
|
+
'rm': 'file',
|
|
326
|
+
'mkdir': 'file',
|
|
327
|
+
'touch': 'file',
|
|
328
|
+
'setup': 'device',
|
|
329
|
+
'usage': 'device',
|
|
330
|
+
'scan': 'device',
|
|
331
|
+
'exec': 'exec',
|
|
332
|
+
'run': 'exec',
|
|
333
|
+
'repl': 'exec',
|
|
334
|
+
'shell': 'exec',
|
|
335
|
+
'reset': 'exec',
|
|
336
|
+
'pkg': 'package',
|
|
337
|
+
'mpy': 'package',
|
|
338
|
+
'version': 'utility',
|
|
339
|
+
'status': 'utility',
|
|
340
|
+
'fg': 'utility',
|
|
341
|
+
'whoami': 'utility',
|
|
342
|
+
'disconnect': 'utility',
|
|
343
|
+
'shutdown': 'utility',
|
|
344
|
+
'format': 'utility',
|
|
345
|
+
'init': 'utility',
|
|
346
|
+
'firmware': 'firmware',
|
|
347
|
+
'i2c': 'i2c',
|
|
348
|
+
'gpio': 'gpio',
|
|
349
|
+
'adc': 'adc',
|
|
350
|
+
'pwm': 'pwm',
|
|
351
|
+
'uart': 'uart',
|
|
352
|
+
'spi': 'spi',
|
|
353
|
+
'wifi': 'wifi',
|
|
354
|
+
}
|
|
355
|
+
_KNOWN_COMMANDS = set(_COMMAND_MODULES) | {'connect', 'help'}
|
|
356
|
+
_LOADED_COMMAND_MODULES: set[str] = set()
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _load_command_module(module_name: str) -> None:
|
|
360
|
+
if module_name in _LOADED_COMMAND_MODULES:
|
|
361
|
+
return
|
|
362
|
+
|
|
363
|
+
importlib.import_module(f'replx.cli.commands.{module_name}')
|
|
364
|
+
_LOADED_COMMAND_MODULES.add(module_name)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _load_required_command_module(command_name: Optional[str]) -> None:
|
|
368
|
+
if not command_name:
|
|
369
|
+
return
|
|
370
|
+
|
|
371
|
+
module_name = _COMMAND_MODULES.get(command_name)
|
|
372
|
+
if module_name:
|
|
373
|
+
_load_command_module(module_name)
|
|
374
|
+
|
|
375
|
+
|
|
306
376
|
def _print_main_help():
|
|
307
377
|
lines = []
|
|
308
378
|
lines.append("[bold]MicroPython development tool for VSCode[/bold]")
|
|
@@ -384,16 +454,7 @@ def _print_main_help():
|
|
|
384
454
|
|
|
385
455
|
|
|
386
456
|
def _get_known_commands() -> set[str]:
|
|
387
|
-
|
|
388
|
-
for command_info in getattr(app, 'registered_commands', []):
|
|
389
|
-
name = getattr(command_info, 'name', None)
|
|
390
|
-
if name:
|
|
391
|
-
known.add(name)
|
|
392
|
-
callback = getattr(command_info, 'callback', None)
|
|
393
|
-
callback_name = getattr(callback, '__name__', None)
|
|
394
|
-
if callback_name:
|
|
395
|
-
known.add(callback_name)
|
|
396
|
-
return known
|
|
457
|
+
return set(_KNOWN_COMMANDS)
|
|
397
458
|
|
|
398
459
|
|
|
399
460
|
@app.callback(invoke_without_command=True)
|
|
@@ -426,9 +487,6 @@ def cli(
|
|
|
426
487
|
_print_main_help()
|
|
427
488
|
raise typer.Exit()
|
|
428
489
|
|
|
429
|
-
|
|
430
|
-
from .commands import file, device, exec, package, utility, firmware, i2c, gpio, adc, pwm, uart, spi, wifi
|
|
431
|
-
|
|
432
490
|
def main():
|
|
433
491
|
if len(sys.argv) == 1:
|
|
434
492
|
OutputHelper.print_panel(
|
|
@@ -462,6 +520,8 @@ def main():
|
|
|
462
520
|
command_str = sys.argv[2]
|
|
463
521
|
|
|
464
522
|
try:
|
|
523
|
+
from .connection import _ensure_connected, _create_agent_client
|
|
524
|
+
|
|
465
525
|
_ensure_connected()
|
|
466
526
|
client = _create_agent_client()
|
|
467
527
|
result = client.send_command('exec', code=command_str)
|
|
@@ -559,12 +619,21 @@ def main():
|
|
|
559
619
|
first_nonopt_idx = next((i for i, a in enumerate(sys.argv[1:], 1) if not a.startswith('-')), None)
|
|
560
620
|
first_nonopt = sys.argv[first_nonopt_idx] if first_nonopt_idx is not None else None
|
|
561
621
|
|
|
622
|
+
_load_required_command_module(first_nonopt)
|
|
623
|
+
|
|
562
624
|
suppressed = {'scan'}
|
|
625
|
+
if any(x in sys.argv for x in ('--help', '-h')):
|
|
626
|
+
_configure_rich_help_rendering()
|
|
627
|
+
|
|
563
628
|
if not any(x in sys.argv for x in ('--help','-h','--version','-v')):
|
|
564
629
|
if (first_nonopt is None) or (first_nonopt not in suppressed):
|
|
630
|
+
from .helpers.updater import UpdateChecker
|
|
631
|
+
|
|
565
632
|
UpdateChecker.check_for_updates(__version__)
|
|
566
633
|
|
|
567
634
|
try:
|
|
635
|
+
from .helpers.environment import EnvironmentManager
|
|
636
|
+
|
|
568
637
|
EnvironmentManager.load_env_from_rep()
|
|
569
638
|
app(standalone_mode=False)
|
|
570
639
|
exit_code = 0
|
|
@@ -23,7 +23,7 @@ from ..config import (
|
|
|
23
23
|
_find_env_file, _find_or_create_vscode_dir,
|
|
24
24
|
_read_env_ini,
|
|
25
25
|
_update_connection_config, _find_available_agent_port, _find_running_agent_ports,
|
|
26
|
-
_get_global_options
|
|
26
|
+
_get_global_options, AgentPortManager
|
|
27
27
|
)
|
|
28
28
|
from ..connection import (
|
|
29
29
|
_ensure_connected, _create_agent_client
|
|
@@ -141,17 +141,30 @@ def _load_jsonc(path: str) -> dict | None:
|
|
|
141
141
|
|
|
142
142
|
|
|
143
143
|
def _get_portable_vscode_root_from_pshome() -> str | None:
|
|
144
|
+
cached_root = AgentPortManager._read_cached_vscode_root()
|
|
145
|
+
if cached_root:
|
|
146
|
+
return cached_root
|
|
147
|
+
|
|
144
148
|
pshome = os.environ.get("PSHOME", "").strip()
|
|
145
149
|
if not pshome:
|
|
146
150
|
try:
|
|
151
|
+
run_kwargs = {
|
|
152
|
+
"capture_output": True,
|
|
153
|
+
"text": True,
|
|
154
|
+
"encoding": "utf-8",
|
|
155
|
+
"errors": "replace",
|
|
156
|
+
"timeout": 2,
|
|
157
|
+
"check": False,
|
|
158
|
+
}
|
|
159
|
+
if sys.platform.startswith("win"):
|
|
160
|
+
startupinfo = subprocess.STARTUPINFO()
|
|
161
|
+
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
162
|
+
startupinfo.wShowWindow = 0
|
|
163
|
+
run_kwargs["startupinfo"] = startupinfo
|
|
164
|
+
run_kwargs["creationflags"] = getattr(subprocess, "CREATE_NO_WINDOW", 0)
|
|
147
165
|
result = subprocess.run(
|
|
148
166
|
["pwsh", "-NoLogo", "-NoProfile", "-Command", "$PSHOME"],
|
|
149
|
-
|
|
150
|
-
text=True,
|
|
151
|
-
encoding="utf-8",
|
|
152
|
-
errors="replace",
|
|
153
|
-
timeout=2,
|
|
154
|
-
check=False,
|
|
167
|
+
**run_kwargs,
|
|
155
168
|
)
|
|
156
169
|
pshome = result.stdout.strip()
|
|
157
170
|
except Exception:
|
|
@@ -160,7 +173,9 @@ def _get_portable_vscode_root_from_pshome() -> str | None:
|
|
|
160
173
|
if not pshome:
|
|
161
174
|
return None
|
|
162
175
|
|
|
163
|
-
|
|
176
|
+
vscode_root = os.path.dirname(os.path.dirname(os.path.dirname(pshome)))
|
|
177
|
+
AgentPortManager._write_cached_vscode_root(vscode_root)
|
|
178
|
+
return vscode_root
|
|
164
179
|
|
|
165
180
|
|
|
166
181
|
def _map_vscode_theme_to_replx(vscode_theme: str | None) -> str | None:
|
|
@@ -381,6 +396,12 @@ Run this once per project folder to set up your workspace.
|
|
|
381
396
|
)
|
|
382
397
|
raise typer.Exit(1)
|
|
383
398
|
|
|
399
|
+
stored_theme = selected_theme
|
|
400
|
+
theme_mode = None
|
|
401
|
+
if auto_theme and not theme:
|
|
402
|
+
stored_theme = display_theme
|
|
403
|
+
theme_mode = 'vscode-auto'
|
|
404
|
+
|
|
384
405
|
if clean:
|
|
385
406
|
_port = _get_global_options().get('port')
|
|
386
407
|
if _port:
|
|
@@ -476,7 +497,8 @@ Run this once per project folder to set up your workspace.
|
|
|
476
497
|
env_path, port,
|
|
477
498
|
version=version, core=core, device=device,
|
|
478
499
|
manufacturer=manufacturer,
|
|
479
|
-
theme=
|
|
500
|
+
theme=stored_theme,
|
|
501
|
+
theme_mode=theme_mode,
|
|
480
502
|
set_default=True
|
|
481
503
|
)
|
|
482
504
|
|
|
@@ -542,7 +564,8 @@ Run this once per project folder to set up your workspace.
|
|
|
542
564
|
env_path, port,
|
|
543
565
|
version=STATE.version, core=STATE.core, device=STATE.device,
|
|
544
566
|
manufacturer=STATE.manufacturer,
|
|
545
|
-
theme=
|
|
567
|
+
theme=stored_theme,
|
|
568
|
+
theme_mode=theme_mode,
|
|
546
569
|
set_default=True
|
|
547
570
|
)
|
|
548
571
|
|
|
@@ -680,7 +703,8 @@ Run this once per project folder to set up your workspace.
|
|
|
680
703
|
core=STATE.core,
|
|
681
704
|
device=STATE.device,
|
|
682
705
|
manufacturer=STATE.manufacturer,
|
|
683
|
-
theme=
|
|
706
|
+
theme=stored_theme,
|
|
707
|
+
theme_mode=theme_mode,
|
|
684
708
|
set_default=set_as_default
|
|
685
709
|
)
|
|
686
710
|
|
|
@@ -29,9 +29,6 @@ from ..connection import (
|
|
|
29
29
|
|
|
30
30
|
from ..app import app
|
|
31
31
|
|
|
32
|
-
from .file import ls, cat, cp, mv, rm, mkdir, touch
|
|
33
|
-
from .device import usage
|
|
34
|
-
|
|
35
32
|
|
|
36
33
|
def _wrap_trailing_expression_for_print(code: str) -> str:
|
|
37
34
|
try:
|
|
@@ -1637,6 +1634,9 @@ Use familiar commands (ls, cd, cat, etc.) without typing "replx" each time.
|
|
|
1637
1634
|
raise typer.Exit()
|
|
1638
1635
|
|
|
1639
1636
|
_ensure_connected()
|
|
1637
|
+
|
|
1638
|
+
from .device import usage
|
|
1639
|
+
from .file import cat, cp, ls, mkdir, mv, rm, touch
|
|
1640
1640
|
|
|
1641
1641
|
SHELL_COMMANDS = {
|
|
1642
1642
|
'ls', 'cat', 'cp', 'mv', 'rm', 'mkdir', 'touch', 'usage', 'exec', 'repl', 'run',
|
|
@@ -1799,6 +1799,16 @@ Upload files or directories from your computer to the device.
|
|
|
1799
1799
|
|
|
1800
1800
|
client = _create_agent_client()
|
|
1801
1801
|
|
|
1802
|
+
def _exit_upload_error(error: RuntimeError) -> None:
|
|
1803
|
+
if OutputHelper.handle_error(error, "Upload"):
|
|
1804
|
+
raise typer.Exit(1)
|
|
1805
|
+
OutputHelper.print_panel(
|
|
1806
|
+
f"Upload failed: [red]{error}[/red]",
|
|
1807
|
+
title="Upload Failed",
|
|
1808
|
+
border_style="red"
|
|
1809
|
+
)
|
|
1810
|
+
raise typer.Exit(1)
|
|
1811
|
+
|
|
1802
1812
|
def ensure_remote_dir_tree(dir_path: str) -> None:
|
|
1803
1813
|
normalized = OutputHelper.normalize_remote_path(dir_path).rstrip('/')
|
|
1804
1814
|
if not normalized:
|
|
@@ -1837,7 +1847,10 @@ Upload files or directories from your computer to the device.
|
|
|
1837
1847
|
|
|
1838
1848
|
target_dir = remote if is_remote_dir else posixpath.dirname(remote_path)
|
|
1839
1849
|
if target_dir and target_dir != '/':
|
|
1840
|
-
|
|
1850
|
+
try:
|
|
1851
|
+
ensure_remote_dir_tree(target_dir)
|
|
1852
|
+
except RuntimeError as e:
|
|
1853
|
+
_exit_upload_error(e)
|
|
1841
1854
|
|
|
1842
1855
|
display_remote = remote_path.replace(device_root_fs, "", 1)
|
|
1843
1856
|
item_type = "Directory" if is_dir else "File"
|
|
@@ -1914,6 +1927,8 @@ Upload files or directories from your computer to the device.
|
|
|
1914
1927
|
border_style="green"
|
|
1915
1928
|
)
|
|
1916
1929
|
except Exception as e:
|
|
1930
|
+
if isinstance(e, RuntimeError):
|
|
1931
|
+
_exit_upload_error(e)
|
|
1917
1932
|
OutputHelper.print_panel(
|
|
1918
1933
|
f"Upload failed: [red]{str(e)}[/red]",
|
|
1919
1934
|
title="Upload Failed",
|
|
@@ -1930,7 +1945,10 @@ Upload files or directories from your computer to the device.
|
|
|
1930
1945
|
raise typer.Exit(1)
|
|
1931
1946
|
|
|
1932
1947
|
if remote and remote != '/':
|
|
1933
|
-
|
|
1948
|
+
try:
|
|
1949
|
+
ensure_remote_dir_tree(remote)
|
|
1950
|
+
except RuntimeError as e:
|
|
1951
|
+
_exit_upload_error(e)
|
|
1934
1952
|
|
|
1935
1953
|
def count_bytes(path):
|
|
1936
1954
|
if os.path.isfile(path):
|
|
@@ -2051,6 +2069,13 @@ Upload files or directories from your computer to the device.
|
|
|
2051
2069
|
))
|
|
2052
2070
|
break
|
|
2053
2071
|
|
|
2072
|
+
except RuntimeError as e:
|
|
2073
|
+
if retry == max_retries - 1:
|
|
2074
|
+
if OutputHelper.handle_error(e, f"Upload: {remote_path}"):
|
|
2075
|
+
raise typer.Exit(1)
|
|
2076
|
+
OutputHelper._console.print(f"[red]Failed to upload {base_name}: {str(e)}[/red]")
|
|
2077
|
+
else:
|
|
2078
|
+
time.sleep(0.2)
|
|
2054
2079
|
except Exception as e:
|
|
2055
2080
|
if retry == max_retries - 1:
|
|
2056
2081
|
OutputHelper._console.print(f"[red]Failed to upload {base_name}: {str(e)}[/red]")
|
|
@@ -29,7 +29,6 @@ from ..connection import (
|
|
|
29
29
|
_get_current_agent_port
|
|
30
30
|
)
|
|
31
31
|
from ..app import app
|
|
32
|
-
from .package import _install_spec_internal
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
def _port_sort_key(port: str) -> tuple:
|
|
@@ -1291,6 +1290,8 @@ Completely reset device: format filesystem and install all libraries.
|
|
|
1291
1290
|
|
|
1292
1291
|
try:
|
|
1293
1292
|
specs_to_install = ["core.all"]
|
|
1293
|
+
|
|
1294
|
+
from .package import _install_spec_internal
|
|
1294
1295
|
|
|
1295
1296
|
dev_src = os.path.join(StoreManager.pkg_root(), "device", device_name_to_path(STATE.device), "src")
|
|
1296
1297
|
if os.path.isdir(dev_src):
|
|
@@ -204,6 +204,7 @@ class ConfigManager:
|
|
|
204
204
|
serial_port: str = None,
|
|
205
205
|
agent_port: int = None,
|
|
206
206
|
theme: str = None,
|
|
207
|
+
theme_mode: str = None,
|
|
207
208
|
set_default: bool = False):
|
|
208
209
|
|
|
209
210
|
def _resolve_os_serial_port_name(port: str) -> str:
|
|
@@ -286,6 +287,7 @@ class ConfigManager:
|
|
|
286
287
|
|
|
287
288
|
if theme is not None:
|
|
288
289
|
AgentPortManager._write_registered_theme(theme)
|
|
290
|
+
AgentPortManager._write_registered_theme_mode(theme_mode)
|
|
289
291
|
|
|
290
292
|
ConfigManager.write(env_path, env_data['connections'], env_data['default'])
|
|
291
293
|
|
|
@@ -296,18 +298,25 @@ class ConfigManager:
|
|
|
296
298
|
|
|
297
299
|
@staticmethod
|
|
298
300
|
def get_theme(env_path: str | None = None) -> str:
|
|
301
|
+
theme_mode = AgentPortManager._read_registered_theme_mode()
|
|
302
|
+
if theme_mode:
|
|
303
|
+
return theme_mode
|
|
304
|
+
|
|
299
305
|
theme = AgentPortManager._read_registered_theme(legacy_env_path=env_path)
|
|
300
306
|
return theme or 'dark'
|
|
301
307
|
|
|
302
308
|
@staticmethod
|
|
303
|
-
def set_theme(env_path: str | None, theme: str):
|
|
309
|
+
def set_theme(env_path: str | None, theme: str, theme_mode: str | None = None):
|
|
304
310
|
AgentPortManager._write_registered_theme(theme)
|
|
311
|
+
AgentPortManager._write_registered_theme_mode(theme_mode)
|
|
305
312
|
|
|
306
313
|
|
|
307
314
|
class AgentPortManager:
|
|
308
315
|
|
|
309
316
|
_AGENT_PORT_KEY = 'AGENT_PORT'
|
|
310
317
|
_THEME_KEY = 'THEME'
|
|
318
|
+
_THEME_MODE_KEY = 'THEME_MODE'
|
|
319
|
+
_VSCODE_ROOT_KEY = 'VSCODE_ROOT'
|
|
311
320
|
|
|
312
321
|
@staticmethod
|
|
313
322
|
def _kill_agent_process_by_port(port: int) -> bool:
|
|
@@ -341,20 +350,49 @@ class AgentPortManager:
|
|
|
341
350
|
|
|
342
351
|
return min(running_ports)
|
|
343
352
|
|
|
353
|
+
_singleton_cache: Optional[int] = None
|
|
354
|
+
_singleton_cache_miss: bool = False
|
|
355
|
+
|
|
344
356
|
@staticmethod
|
|
345
357
|
def _ensure_singleton_running_agent(preferred_port: Optional[int] = None) -> Optional[int]:
|
|
346
358
|
from .agent.client import AgentClient
|
|
347
359
|
|
|
360
|
+
# Process-level cache: ``_ensure_connected`` calls this twice in the same
|
|
361
|
+
# CLI invocation. The set of running agents cannot change between those
|
|
362
|
+
# back-to-back calls, so reuse the prior result.
|
|
363
|
+
if AgentPortManager._singleton_cache is not None:
|
|
364
|
+
return AgentPortManager._singleton_cache
|
|
365
|
+
if AgentPortManager._singleton_cache_miss:
|
|
366
|
+
return None
|
|
367
|
+
|
|
368
|
+
# Fast path: if the preferred / registered / default port already responds
|
|
369
|
+
# to a ping, skip the slow scan-and-cleanup phase. Stale agent processes
|
|
370
|
+
# left from previous sessions can otherwise add ~0.3s per dead candidate
|
|
371
|
+
# (UDP probe timeout) on every CLI invocation. Singleton enforcement and
|
|
372
|
+
# zombie cleanup still happen on cold paths (setup / shutdown) where the
|
|
373
|
+
# priority candidate is unreachable.
|
|
374
|
+
registered_port = AgentPortManager._read_registered_port()
|
|
375
|
+
for candidate in (preferred_port, registered_port, DEFAULT_AGENT_PORT):
|
|
376
|
+
if not isinstance(candidate, int):
|
|
377
|
+
continue
|
|
378
|
+
if AgentClient.is_agent_running(port=candidate):
|
|
379
|
+
AgentPortManager._singleton_cache = candidate
|
|
380
|
+
if registered_port != candidate:
|
|
381
|
+
AgentPortManager._write_registered_port(candidate)
|
|
382
|
+
return candidate
|
|
383
|
+
|
|
348
384
|
running_ports = []
|
|
349
385
|
for port in AgentPortManager._get_candidate_agent_ports(preferred_port=preferred_port):
|
|
350
386
|
if AgentClient.is_agent_running(port=port):
|
|
351
387
|
running_ports.append(port)
|
|
352
388
|
|
|
353
389
|
if not running_ports:
|
|
390
|
+
AgentPortManager._singleton_cache_miss = True
|
|
354
391
|
return None
|
|
355
392
|
|
|
356
393
|
primary_port = AgentPortManager._pick_primary_port(running_ports, preferred_port=preferred_port)
|
|
357
394
|
if primary_port is None:
|
|
395
|
+
AgentPortManager._singleton_cache_miss = True
|
|
358
396
|
return None
|
|
359
397
|
|
|
360
398
|
for port in running_ports:
|
|
@@ -382,6 +420,7 @@ class AgentPortManager:
|
|
|
382
420
|
)
|
|
383
421
|
|
|
384
422
|
AgentPortManager._write_registered_port(primary_port)
|
|
423
|
+
AgentPortManager._singleton_cache = primary_port
|
|
385
424
|
return primary_port
|
|
386
425
|
|
|
387
426
|
@staticmethod
|
|
@@ -482,6 +521,14 @@ class AgentPortManager:
|
|
|
482
521
|
|
|
483
522
|
return None
|
|
484
523
|
|
|
524
|
+
@staticmethod
|
|
525
|
+
def _read_registered_theme_mode() -> Optional[str]:
|
|
526
|
+
config_entries = AgentPortManager._read_agent_config()
|
|
527
|
+
theme_mode = config_entries.get(AgentPortManager._THEME_MODE_KEY)
|
|
528
|
+
if theme_mode:
|
|
529
|
+
return theme_mode.strip() or None
|
|
530
|
+
return None
|
|
531
|
+
|
|
485
532
|
@staticmethod
|
|
486
533
|
def _write_registered_theme(theme: Optional[str]) -> None:
|
|
487
534
|
if theme is None:
|
|
@@ -495,6 +542,54 @@ class AgentPortManager:
|
|
|
495
542
|
entries[AgentPortManager._THEME_KEY] = normalized
|
|
496
543
|
AgentPortManager._write_agent_config(entries)
|
|
497
544
|
|
|
545
|
+
@staticmethod
|
|
546
|
+
def _write_registered_theme_mode(theme_mode: Optional[str]) -> None:
|
|
547
|
+
entries = AgentPortManager._read_agent_config()
|
|
548
|
+
|
|
549
|
+
if theme_mode is None:
|
|
550
|
+
entries.pop(AgentPortManager._THEME_MODE_KEY, None)
|
|
551
|
+
AgentPortManager._write_agent_config(entries)
|
|
552
|
+
return
|
|
553
|
+
|
|
554
|
+
normalized = str(theme_mode).strip()
|
|
555
|
+
if not normalized:
|
|
556
|
+
entries.pop(AgentPortManager._THEME_MODE_KEY, None)
|
|
557
|
+
AgentPortManager._write_agent_config(entries)
|
|
558
|
+
return
|
|
559
|
+
|
|
560
|
+
entries[AgentPortManager._THEME_MODE_KEY] = normalized
|
|
561
|
+
AgentPortManager._write_agent_config(entries)
|
|
562
|
+
|
|
563
|
+
@staticmethod
|
|
564
|
+
def _read_cached_vscode_root() -> Optional[str]:
|
|
565
|
+
entries = AgentPortManager._read_agent_config()
|
|
566
|
+
root = entries.get(AgentPortManager._VSCODE_ROOT_KEY)
|
|
567
|
+
if not root:
|
|
568
|
+
return None
|
|
569
|
+
|
|
570
|
+
settings_path = os.path.join(root, 'data', 'user-data', 'User', 'settings.json')
|
|
571
|
+
if os.path.exists(settings_path):
|
|
572
|
+
return root
|
|
573
|
+
|
|
574
|
+
return None
|
|
575
|
+
|
|
576
|
+
@staticmethod
|
|
577
|
+
def _write_cached_vscode_root(root: Optional[str]) -> None:
|
|
578
|
+
if root is None:
|
|
579
|
+
return
|
|
580
|
+
|
|
581
|
+
normalized = str(root).strip()
|
|
582
|
+
if not normalized:
|
|
583
|
+
return
|
|
584
|
+
|
|
585
|
+
settings_path = os.path.join(normalized, 'data', 'user-data', 'User', 'settings.json')
|
|
586
|
+
if not os.path.exists(settings_path):
|
|
587
|
+
return
|
|
588
|
+
|
|
589
|
+
entries = AgentPortManager._read_agent_config()
|
|
590
|
+
entries[AgentPortManager._VSCODE_ROOT_KEY] = normalized
|
|
591
|
+
AgentPortManager._write_agent_config(entries)
|
|
592
|
+
|
|
498
593
|
@staticmethod
|
|
499
594
|
def _can_bind_port(port: Optional[int]) -> bool:
|
|
500
595
|
if not isinstance(port, int) or not (MIN_AGENT_PORT <= port <= MAX_AGENT_PORT):
|
|
@@ -732,8 +827,8 @@ def _get_default_connection(env_path: str) -> Optional[str]:
|
|
|
732
827
|
def _get_theme_config(env_path: str | None = None) -> str:
|
|
733
828
|
return ConfigManager.get_theme(env_path)
|
|
734
829
|
|
|
735
|
-
def _set_theme_config(env_path: str | None, theme: str):
|
|
736
|
-
return ConfigManager.set_theme(env_path, theme)
|
|
830
|
+
def _set_theme_config(env_path: str | None, theme: str, theme_mode: str | None = None):
|
|
831
|
+
return ConfigManager.set_theme(env_path, theme, theme_mode)
|
|
737
832
|
|
|
738
833
|
def _find_available_agent_port(env_path: str) -> int:
|
|
739
834
|
return AgentPortManager.find_available_port(env_path)
|