replx 1.5__tar.gz → 1.6__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.
Files changed (154) hide show
  1. {replx-1.5/replx.egg-info → replx-1.6}/PKG-INFO +1 -1
  2. {replx-1.5 → replx-1.6}/replx/__init__.py +1 -1
  3. {replx-1.5 → replx-1.6}/replx/cli/agent/server/connection_manager.py +9 -2
  4. {replx-1.5 → replx-1.6}/replx/cli/commands/exec.py +3 -5
  5. {replx-1.5 → replx-1.6/replx.egg-info}/PKG-INFO +1 -1
  6. replx-1.6/test/test_termio.py +70 -0
  7. replx-1.5/test/test_termio.py +0 -42
  8. {replx-1.5 → replx-1.6}/LICENSE +0 -0
  9. {replx-1.5 → replx-1.6}/README.md +0 -0
  10. {replx-1.5 → replx-1.6}/pyproject.toml +0 -0
  11. {replx-1.5 → replx-1.6}/replx/cli/__init__.py +0 -0
  12. {replx-1.5 → replx-1.6}/replx/cli/agent/__init__.py +0 -0
  13. {replx-1.5 → replx-1.6}/replx/cli/agent/client/__init__.py +0 -0
  14. {replx-1.5 → replx-1.6}/replx/cli/agent/client/core.py +0 -0
  15. {replx-1.5 → replx-1.6}/replx/cli/agent/client/session.py +0 -0
  16. {replx-1.5 → replx-1.6}/replx/cli/agent/protocol.py +0 -0
  17. {replx-1.5 → replx-1.6}/replx/cli/agent/server/__init__.py +0 -0
  18. {replx-1.5 → replx-1.6}/replx/cli/agent/server/__main__.py +0 -0
  19. {replx-1.5 → replx-1.6}/replx/cli/agent/server/command_dispatcher.py +0 -0
  20. {replx-1.5 → replx-1.6}/replx/cli/agent/server/core.py +0 -0
  21. {replx-1.5 → replx-1.6}/replx/cli/agent/server/handlers/__init__.py +0 -0
  22. {replx-1.5 → replx-1.6}/replx/cli/agent/server/handlers/exec.py +0 -0
  23. {replx-1.5 → replx-1.6}/replx/cli/agent/server/handlers/filesystem.py +0 -0
  24. {replx-1.5 → replx-1.6}/replx/cli/agent/server/handlers/repl.py +0 -0
  25. {replx-1.5 → replx-1.6}/replx/cli/agent/server/handlers/session.py +0 -0
  26. {replx-1.5 → replx-1.6}/replx/cli/agent/server/handlers/transfer.py +0 -0
  27. {replx-1.5 → replx-1.6}/replx/cli/agent/server/session_manager.py +0 -0
  28. {replx-1.5 → replx-1.6}/replx/cli/app.py +0 -0
  29. {replx-1.5 → replx-1.6}/replx/cli/commands/__init__.py +0 -0
  30. {replx-1.5 → replx-1.6}/replx/cli/commands/device.py +0 -0
  31. {replx-1.5 → replx-1.6}/replx/cli/commands/file.py +0 -0
  32. {replx-1.5 → replx-1.6}/replx/cli/commands/firmware.py +0 -0
  33. {replx-1.5 → replx-1.6}/replx/cli/commands/package.py +0 -0
  34. {replx-1.5 → replx-1.6}/replx/cli/commands/utility.py +0 -0
  35. {replx-1.5 → replx-1.6}/replx/cli/config.py +0 -0
  36. {replx-1.5 → replx-1.6}/replx/cli/connection.py +0 -0
  37. {replx-1.5 → replx-1.6}/replx/cli/helpers/__init__.py +0 -0
  38. {replx-1.5 → replx-1.6}/replx/cli/helpers/compiler.py +0 -0
  39. {replx-1.5 → replx-1.6}/replx/cli/helpers/environment.py +0 -0
  40. {replx-1.5 → replx-1.6}/replx/cli/helpers/output.py +0 -0
  41. {replx-1.5 → replx-1.6}/replx/cli/helpers/registry.py +0 -0
  42. {replx-1.5 → replx-1.6}/replx/cli/helpers/scanner.py +0 -0
  43. {replx-1.5 → replx-1.6}/replx/cli/helpers/store.py +0 -0
  44. {replx-1.5 → replx-1.6}/replx/cli/helpers/updater.py +0 -0
  45. {replx-1.5 → replx-1.6}/replx/commands.py +0 -0
  46. {replx-1.5 → replx-1.6}/replx/protocol/__init__.py +0 -0
  47. {replx-1.5 → replx-1.6}/replx/protocol/repl.py +0 -0
  48. {replx-1.5 → replx-1.6}/replx/protocol/storage.py +0 -0
  49. {replx-1.5 → replx-1.6}/replx/terminal.py +0 -0
  50. {replx-1.5 → replx-1.6}/replx/tests/__init__.py +0 -0
  51. {replx-1.5 → replx-1.6}/replx/tests/test_agent_asyncio.py +0 -0
  52. {replx-1.5 → replx-1.6}/replx/tests/test_agent_port_canonicalization.py +0 -0
  53. {replx-1.5 → replx-1.6}/replx/tests/test_agent_thread_pool.py +0 -0
  54. {replx-1.5 → replx-1.6}/replx/tests/test_compiler_arch.py +0 -0
  55. {replx-1.5 → replx-1.6}/replx/tests/test_connection_info_lookup.py +0 -0
  56. {replx-1.5 → replx-1.6}/replx/tests/test_device_info_esp_multi_core.py +0 -0
  57. {replx-1.5 → replx-1.6}/replx/tests/test_disconnect_cleanup.py +0 -0
  58. {replx-1.5 → replx-1.6}/replx/tests/test_lock_cleanup.py +0 -0
  59. {replx-1.5 → replx-1.6}/replx/tests/test_pkg_local_version.py +0 -0
  60. {replx-1.5 → replx-1.6}/replx/tests/test_pkg_search_scope_filter.py +0 -0
  61. {replx-1.5 → replx-1.6}/replx/tests/test_repl_reader_task.py +0 -0
  62. {replx-1.5 → replx-1.6}/replx/tests/test_session_disconnect_releases_shared_port.py +0 -0
  63. {replx-1.5 → replx-1.6}/replx/tests/test_session_id_fallback.py +0 -0
  64. {replx-1.5 → replx-1.6}/replx/tests/test_shutdown_status_message.py +0 -0
  65. {replx-1.5 → replx-1.6}/replx/tests/test_windows_com_port_normalization.py +0 -0
  66. {replx-1.5 → replx-1.6}/replx/transport/__init__.py +0 -0
  67. {replx-1.5 → replx-1.6}/replx/transport/base.py +0 -0
  68. {replx-1.5 → replx-1.6}/replx/transport/serial.py +0 -0
  69. {replx-1.5 → replx-1.6}/replx/typehints/comm/_thread.pyi +0 -0
  70. {replx-1.5 → replx-1.6}/replx/typehints/comm/aioble/__init__.pyi +0 -0
  71. {replx-1.5 → replx-1.6}/replx/typehints/comm/array.pyi +0 -0
  72. {replx-1.5 → replx-1.6}/replx/typehints/comm/asyncio/__init__.pyi +0 -0
  73. {replx-1.5 → replx-1.6}/replx/typehints/comm/binascii.pyi +0 -0
  74. {replx-1.5 → replx-1.6}/replx/typehints/comm/bluetooth.pyi +0 -0
  75. {replx-1.5 → replx-1.6}/replx/typehints/comm/builtins.pyi +0 -0
  76. {replx-1.5 → replx-1.6}/replx/typehints/comm/cmath.pyi +0 -0
  77. {replx-1.5 → replx-1.6}/replx/typehints/comm/collections.pyi +0 -0
  78. {replx-1.5 → replx-1.6}/replx/typehints/comm/cryptolib.pyi +0 -0
  79. {replx-1.5 → replx-1.6}/replx/typehints/comm/deflate.pyi +0 -0
  80. {replx-1.5 → replx-1.6}/replx/typehints/comm/errno.pyi +0 -0
  81. {replx-1.5 → replx-1.6}/replx/typehints/comm/framebuf.pyi +0 -0
  82. {replx-1.5 → replx-1.6}/replx/typehints/comm/gc.pyi +0 -0
  83. {replx-1.5 → replx-1.6}/replx/typehints/comm/hashlib.pyi +0 -0
  84. {replx-1.5 → replx-1.6}/replx/typehints/comm/heapq.pyi +0 -0
  85. {replx-1.5 → replx-1.6}/replx/typehints/comm/io.pyi +0 -0
  86. {replx-1.5 → replx-1.6}/replx/typehints/comm/json.pyi +0 -0
  87. {replx-1.5 → replx-1.6}/replx/typehints/comm/lwip.pyi +0 -0
  88. {replx-1.5 → replx-1.6}/replx/typehints/comm/machine.pyi +0 -0
  89. {replx-1.5 → replx-1.6}/replx/typehints/comm/math.pyi +0 -0
  90. {replx-1.5 → replx-1.6}/replx/typehints/comm/micropython.pyi +0 -0
  91. {replx-1.5 → replx-1.6}/replx/typehints/comm/mip/__init__.pyi +0 -0
  92. {replx-1.5 → replx-1.6}/replx/typehints/comm/network.pyi +0 -0
  93. {replx-1.5 → replx-1.6}/replx/typehints/comm/ntptime.pyi +0 -0
  94. {replx-1.5 → replx-1.6}/replx/typehints/comm/os.pyi +0 -0
  95. {replx-1.5 → replx-1.6}/replx/typehints/comm/platform.pyi +0 -0
  96. {replx-1.5 → replx-1.6}/replx/typehints/comm/random.pyi +0 -0
  97. {replx-1.5 → replx-1.6}/replx/typehints/comm/re.pyi +0 -0
  98. {replx-1.5 → replx-1.6}/replx/typehints/comm/requests/__init__.pyi +0 -0
  99. {replx-1.5 → replx-1.6}/replx/typehints/comm/select.pyi +0 -0
  100. {replx-1.5 → replx-1.6}/replx/typehints/comm/socket.pyi +0 -0
  101. {replx-1.5 → replx-1.6}/replx/typehints/comm/ssl.pyi +0 -0
  102. {replx-1.5 → replx-1.6}/replx/typehints/comm/struct.pyi +0 -0
  103. {replx-1.5 → replx-1.6}/replx/typehints/comm/sys.pyi +0 -0
  104. {replx-1.5 → replx-1.6}/replx/typehints/comm/time.pyi +0 -0
  105. {replx-1.5 → replx-1.6}/replx/typehints/comm/tls.pyi +0 -0
  106. {replx-1.5 → replx-1.6}/replx/typehints/comm/uasyncio.pyi +0 -0
  107. {replx-1.5 → replx-1.6}/replx/typehints/comm/uctypes.pyi +0 -0
  108. {replx-1.5 → replx-1.6}/replx/typehints/comm/urequests.pyi +0 -0
  109. {replx-1.5 → replx-1.6}/replx/typehints/comm/vfs.pyi +0 -0
  110. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/binascii.pyi +0 -0
  111. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/errno.pyi +0 -0
  112. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/hashlib.pyi +0 -0
  113. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/io.pyi +0 -0
  114. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/json.pyi +0 -0
  115. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/machine.pyi +0 -0
  116. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/math.pyi +0 -0
  117. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/micropython.pyi +0 -0
  118. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/network.pyi +0 -0
  119. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/os.pyi +0 -0
  120. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/select.pyi +0 -0
  121. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/socket.pyi +0 -0
  122. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/ssl.pyi +0 -0
  123. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/struct.pyi +0 -0
  124. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/sys.pyi +0 -0
  125. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/time.pyi +0 -0
  126. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/ubinascii.pyi +0 -0
  127. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/ucryptolib.pyi +0 -0
  128. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/uerrno.pyi +0 -0
  129. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/uhashlib.pyi +0 -0
  130. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/uio.pyi +0 -0
  131. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/ujson.pyi +0 -0
  132. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/umachine.pyi +0 -0
  133. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/uos.pyi +0 -0
  134. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/uselect.pyi +0 -0
  135. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/usocket.pyi +0 -0
  136. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/ussl.pyi +0 -0
  137. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/ustruct.pyi +0 -0
  138. {replx-1.5 → replx-1.6}/replx/typehints/comm_separate/EFR32MG/utime.pyi +0 -0
  139. {replx-1.5 → replx-1.6}/replx/typehints/core/ESP32/aioespnow.pyi +0 -0
  140. {replx-1.5 → replx-1.6}/replx/typehints/core/ESP32/esp.pyi +0 -0
  141. {replx-1.5 → replx-1.6}/replx/typehints/core/ESP32/esp32.pyi +0 -0
  142. {replx-1.5 → replx-1.6}/replx/typehints/core/ESP32/espnow.pyi +0 -0
  143. {replx-1.5 → replx-1.6}/replx/typehints/core/MIMXRT1062DVJ6A/mimxrt.pyi +0 -0
  144. {replx-1.5 → replx-1.6}/replx/typehints/core/RP2350/rp2.pyi +0 -0
  145. {replx-1.5 → replx-1.6}/replx/utils/__init__.py +0 -0
  146. {replx-1.5 → replx-1.6}/replx/utils/constants.py +0 -0
  147. {replx-1.5 → replx-1.6}/replx/utils/device_info.py +0 -0
  148. {replx-1.5 → replx-1.6}/replx/utils/exceptions.py +0 -0
  149. {replx-1.5 → replx-1.6}/replx.egg-info/SOURCES.txt +0 -0
  150. {replx-1.5 → replx-1.6}/replx.egg-info/dependency_links.txt +0 -0
  151. {replx-1.5 → replx-1.6}/replx.egg-info/entry_points.txt +0 -0
  152. {replx-1.5 → replx-1.6}/replx.egg-info/requires.txt +0 -0
  153. {replx-1.5 → replx-1.6}/replx.egg-info/top_level.txt +0 -0
  154. {replx-1.5 → replx-1.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: replx
3
- Version: 1.5
3
+ Version: 1.6
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
@@ -1,5 +1,5 @@
1
1
  __all__ = ["__version__", "get_version", "__description__"]
2
- __version__ = "1.5"
2
+ __version__ = "1.6"
3
3
  __description__ = "Fast, modern MicroPython CLI with REPL, file sync, install, and smart port detection."
4
4
  __author__ = "PlanX Lab Development Team"
5
5
 
@@ -455,6 +455,10 @@ 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
+ was_detached = conn.is_detached()
458
462
  conn.stop_detached()
459
463
 
460
464
  if conn.repl.active:
@@ -469,8 +473,11 @@ class ConnectionManager:
469
473
  transport = conn.repl_protocol.transport
470
474
  if transport:
471
475
  try:
472
- transport.write(CTRL_B)
473
- time.sleep(0.1 if sys.platform == 'win32' else 0.05)
476
+ 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
+ transport.write(CTRL_B)
480
+ time.sleep(0.1 if sys.platform == 'win32' else 0.05)
474
481
  except Exception:
475
482
  pass
476
483
  transport.close()
@@ -344,13 +344,11 @@ By default, runs a file from your computer. Use -d to run from device.
344
344
  if stream_type == "stderr":
345
345
  stderr_buffer.extend(data)
346
346
  else:
347
- # Normalize line endings for all platforms
348
347
  if not IS_WINDOWS:
349
- # Unix: CR+LF -> LF, then remove standalone CR
350
- data = data.replace(b'\r\n', b'\n')
351
- data = data.replace(b'\r', b'')
348
+ data = data.replace(b'\r\n', b'\n') # CRLF -> LF
349
+ data = data.replace(b'\r', b'\n') # lone CR -> LF
350
+ data = data.replace(b'\n', b'\r\n') # LF -> CRLF
352
351
  else:
353
- # Windows: just remove CR (terminal handles LF)
354
352
  data = data.replace(b'\r', b'')
355
353
 
356
354
  if data:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: replx
3
- Version: 1.5
3
+ Version: 1.6
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,70 @@
1
+ from termio import ReplSerial
2
+ from utools import rand, clamp, intervalChecker
3
+ import time
4
+
5
+
6
+ def new_token() -> str:
7
+ return f"{rand(4):08x}" # 예: "a3f2c1b4"
8
+
9
+
10
+ with ReplSerial(timeout=0) as ser:
11
+ token = new_token()
12
+ authed = False
13
+ every_sess = intervalChecker(1000) # 미인증: 1초마다 SESSION 재전송
14
+ heartbeat = intervalChecker(5000) # 인증 후: 5초마다 ALIVE
15
+
16
+ # 최초 토큰 전송
17
+ ser.write(f"SESSION={token}\r\n".encode())
18
+
19
+ while True:
20
+ # timeout=0이므로 \r\n이 두 pump 사이클에 걸려 도착할 수 있음 → b"\n" 사용
21
+ line = ser.read_until(b"\n", max_size=64)
22
+ if line and line.endswith(b"\n"):
23
+ cmd = line.decode("utf-8", "replace").strip()
24
+
25
+ # 인증 (항상 허용)
26
+ if cmd.startswith("AUTH "):
27
+ if cmd[5:] == token:
28
+ authed = True
29
+ ser.write(b"OK authenticated\r\n")
30
+ else:
31
+ authed = False
32
+ ser.write(b"ERR wrong token\r\n")
33
+
34
+ # 미인증 요청 거부
35
+ elif not authed:
36
+ ser.write(b"ERR not authenticated\r\n")
37
+
38
+ # 인증 후 커맨드
39
+ elif cmd.startswith("SET_PWM:"):
40
+ try:
41
+ duty = clamp(int(cmd[8:]), 0, 100)
42
+ # pwm.duty(int(duty * 655.35)) # 0–100% → 0–65535
43
+ ser.write(f"OK pwm={duty}%\r\n".encode())
44
+ except (ValueError, TypeError):
45
+ ser.write(b"ERR bad value\r\n")
46
+
47
+ elif cmd.startswith("SET_FREQ:"):
48
+ try:
49
+ freq = clamp(int(cmd[9:]), 1, 20_000)
50
+ # pwm.freq(freq)
51
+ ser.write(f"OK freq={freq}Hz\r\n".encode())
52
+ except (ValueError, TypeError):
53
+ ser.write(b"ERR bad value\r\n")
54
+
55
+ elif cmd == "RESET_SESSION":
56
+ token = new_token()
57
+ authed = False
58
+ every_sess = intervalChecker(1000)
59
+ ser.write(f"SESSION={token}\r\n".encode())
60
+
61
+ else:
62
+ ser.write(b"ERR unknown cmd\r\n")
63
+
64
+ # 주기적 전송 (커맨드 수신 여부와 무관)
65
+ if not authed and every_sess():
66
+ ser.write(f"SESSION={token}\r\n".encode())
67
+ elif authed and heartbeat():
68
+ ser.write(b"ALIVE\r\n")
69
+
70
+ time.sleep_ms(10)
@@ -1,42 +0,0 @@
1
- from termio import ReplSerial
2
- import time
3
-
4
-
5
- def discard_until_eol(ser, eol=b"\r\n", max_size=64):
6
- """너무 긴 줄로 잘린 뒤, 다음 개행(\r\n)이 나타날 때까지 남은 바이트를 버립니다."""
7
- old_timeout = ser.timeout
8
- ser.timeout = 0.15
9
- try:
10
- while True:
11
- chunk = ser.read_until(eol, max_size=max_size)
12
- if not chunk:
13
- # 타임아웃: 버퍼에 남은 조각이 있으면 read()로 비운 뒤 재시도합니다.
14
- n = ser.in_waiting
15
- if n:
16
- ser.read(n)
17
- continue
18
- return
19
- if chunk.endswith(eol):
20
- return
21
- finally:
22
- ser.timeout = old_timeout
23
-
24
-
25
- with ReplSerial(timeout=0.1) as ser:
26
- while True:
27
- line = ser.read_until(b"\r\n", max_size=64)
28
- if not line:
29
- time.sleep_ms(10)
30
- continue
31
-
32
- if not line.endswith(b"\r\n"):
33
- # max_size(64)로 잘려 나온 경우 → 나머지 조각을 버리고 다음 줄부터 다시 맞춥니다.
34
- print("too long line -> discard")
35
- discard_until_eol(ser, b"\r\n", max_size=64)
36
- continue
37
-
38
- cmd = line[:-2].decode("utf-8", "replace")
39
- print("cmd=", cmd)
40
- ser.write(("OK:" + cmd + "\r\n").encode("utf-8"))
41
- if cmd == "quit":
42
- break
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes