ata-coder 2.4.7__tar.gz → 2.4.8__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 (141) hide show
  1. {ata_coder-2.4.7/ata_coder.egg-info → ata_coder-2.4.8}/PKG-INFO +1 -1
  2. {ata_coder-2.4.7 → ata_coder-2.4.8}/agent.py +4 -0
  3. {ata_coder-2.4.7 → ata_coder-2.4.8}/agent_tools.py +2 -1
  4. {ata_coder-2.4.7 → ata_coder-2.4.8/ata_coder.egg-info}/PKG-INFO +1 -1
  5. {ata_coder-2.4.7 → ata_coder-2.4.8}/change_tracker.py +1 -1
  6. {ata_coder-2.4.7 → ata_coder-2.4.8}/config.py +7 -3
  7. {ata_coder-2.4.7 → ata_coder-2.4.8}/main.py +3 -3
  8. {ata_coder-2.4.7 → ata_coder-2.4.8}/memory.py +3 -0
  9. {ata_coder-2.4.7 → ata_coder-2.4.8}/privilege.py +10 -6
  10. {ata_coder-2.4.7 → ata_coder-2.4.8}/pyproject.toml +1 -1
  11. {ata_coder-2.4.7 → ata_coder-2.4.8}/safety_guard.py +1 -1
  12. {ata_coder-2.4.7 → ata_coder-2.4.8}/server.py +6 -2
  13. {ata_coder-2.4.7 → ata_coder-2.4.8}/server_session.py +1 -1
  14. {ata_coder-2.4.7 → ata_coder-2.4.8}/setup_wizard.py +1 -1
  15. {ata_coder-2.4.7 → ata_coder-2.4.8}/LICENSE +0 -0
  16. {ata_coder-2.4.7 → ata_coder-2.4.8}/MANIFEST.in +0 -0
  17. {ata_coder-2.4.7 → ata_coder-2.4.8}/README.md +0 -0
  18. {ata_coder-2.4.7 → ata_coder-2.4.8}/__init__.py +0 -0
  19. {ata_coder-2.4.7 → ata_coder-2.4.8}/agent_compact.py +0 -0
  20. {ata_coder-2.4.7 → ata_coder-2.4.8}/agent_controller.py +0 -0
  21. {ata_coder-2.4.7 → ata_coder-2.4.8}/agent_extension.py +0 -0
  22. {ata_coder-2.4.7 → ata_coder-2.4.8}/agent_routing.py +0 -0
  23. {ata_coder-2.4.7 → ata_coder-2.4.8}/agent_subsystems.py +0 -0
  24. {ata_coder-2.4.7 → ata_coder-2.4.8}/agent_undo.py +0 -0
  25. {ata_coder-2.4.7 → ata_coder-2.4.8}/anthropic_client.py +0 -0
  26. {ata_coder-2.4.7 → ata_coder-2.4.8}/ata_coder.egg-info/SOURCES.txt +0 -0
  27. {ata_coder-2.4.7 → ata_coder-2.4.8}/ata_coder.egg-info/dependency_links.txt +0 -0
  28. {ata_coder-2.4.7 → ata_coder-2.4.8}/ata_coder.egg-info/entry_points.txt +0 -0
  29. {ata_coder-2.4.7 → ata_coder-2.4.8}/ata_coder.egg-info/requires.txt +0 -0
  30. {ata_coder-2.4.7 → ata_coder-2.4.8}/ata_coder.egg-info/top_level.txt +0 -0
  31. {ata_coder-2.4.7 → ata_coder-2.4.8}/clawd_integration.py +0 -0
  32. {ata_coder-2.4.7 → ata_coder-2.4.8}/commands/__init__.py +0 -0
  33. {ata_coder-2.4.7 → ata_coder-2.4.8}/commands/_core.py +0 -0
  34. {ata_coder-2.4.7 → ata_coder-2.4.8}/commands/_safety.py +0 -0
  35. {ata_coder-2.4.7 → ata_coder-2.4.8}/commands/_settings.py +0 -0
  36. {ata_coder-2.4.7 → ata_coder-2.4.8}/commands/_workflow.py +0 -0
  37. {ata_coder-2.4.7 → ata_coder-2.4.8}/context_manager.py +0 -0
  38. {ata_coder-2.4.7 → ata_coder-2.4.8}/core/__init__.py +0 -0
  39. {ata_coder-2.4.7 → ata_coder-2.4.8}/core/events.py +0 -0
  40. {ata_coder-2.4.7 → ata_coder-2.4.8}/core/queue.py +0 -0
  41. {ata_coder-2.4.7 → ata_coder-2.4.8}/core/state.py +0 -0
  42. {ata_coder-2.4.7 → ata_coder-2.4.8}/event_queue.py +0 -0
  43. {ata_coder-2.4.7 → ata_coder-2.4.8}/extension.py +0 -0
  44. {ata_coder-2.4.7 → ata_coder-2.4.8}/extensions/__init__.py +0 -0
  45. {ata_coder-2.4.7 → ata_coder-2.4.8}/extensions/hello_skill.py +0 -0
  46. {ata_coder-2.4.7 → ata_coder-2.4.8}/fool_proof.py +0 -0
  47. {ata_coder-2.4.7 → ata_coder-2.4.8}/git_workflow.py +0 -0
  48. {ata_coder-2.4.7 → ata_coder-2.4.8}/gui.py +0 -0
  49. {ata_coder-2.4.7 → ata_coder-2.4.8}/llm_client.py +0 -0
  50. {ata_coder-2.4.7 → ata_coder-2.4.8}/mcp_client.py +0 -0
  51. {ata_coder-2.4.7 → ata_coder-2.4.8}/model_registry.py +0 -0
  52. {ata_coder-2.4.7 → ata_coder-2.4.8}/model_router.py +0 -0
  53. {ata_coder-2.4.7 → ata_coder-2.4.8}/permissions.py +0 -0
  54. {ata_coder-2.4.7 → ata_coder-2.4.8}/project.py +0 -0
  55. {ata_coder-2.4.7 → ata_coder-2.4.8}/prompt_template.py +0 -0
  56. {ata_coder-2.4.7 → ata_coder-2.4.8}/prompts/auto-mode.md +0 -0
  57. {ata_coder-2.4.7 → ata_coder-2.4.8}/prompts/coding-rules.md +0 -0
  58. {ata_coder-2.4.7 → ata_coder-2.4.8}/prompts/execution-guardrails.md +0 -0
  59. {ata_coder-2.4.7 → ata_coder-2.4.8}/prompts/memory-system.md +0 -0
  60. {ata_coder-2.4.7 → ata_coder-2.4.8}/prompts/output-style.md +0 -0
  61. {ata_coder-2.4.7 → ata_coder-2.4.8}/prompts/safety.md +0 -0
  62. {ata_coder-2.4.7 → ata_coder-2.4.8}/prompts/slash-commands.md +0 -0
  63. {ata_coder-2.4.7 → ata_coder-2.4.8}/prompts/sub-agents.md +0 -0
  64. {ata_coder-2.4.7 → ata_coder-2.4.8}/prompts/system-reminders.md +0 -0
  65. {ata_coder-2.4.7 → ata_coder-2.4.8}/prompts/system.md +0 -0
  66. {ata_coder-2.4.7 → ata_coder-2.4.8}/prompts/tool-policy.md +0 -0
  67. {ata_coder-2.4.7 → ata_coder-2.4.8}/py.typed +0 -0
  68. {ata_coder-2.4.7 → ata_coder-2.4.8}/repl_theme.py +0 -0
  69. {ata_coder-2.4.7 → ata_coder-2.4.8}/repl_tracker.py +0 -0
  70. {ata_coder-2.4.7 → ata_coder-2.4.8}/repl_ui.py +0 -0
  71. {ata_coder-2.4.7 → ata_coder-2.4.8}/self_correct.py +0 -0
  72. {ata_coder-2.4.7 → ata_coder-2.4.8}/server_shell.py +0 -0
  73. {ata_coder-2.4.7 → ata_coder-2.4.8}/session.py +0 -0
  74. {ata_coder-2.4.7 → ata_coder-2.4.8}/settings.py +0 -0
  75. {ata_coder-2.4.7 → ata_coder-2.4.8}/setup.cfg +0 -0
  76. {ata_coder-2.4.7 → ata_coder-2.4.8}/skill_extension.py +0 -0
  77. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/architect/SKILL.md +0 -0
  78. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/code-reviewer/SKILL.md +0 -0
  79. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/codecraft/SKILL.md +0 -0
  80. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/debugger/SKILL.md +0 -0
  81. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/doc-writer/SKILL.md +0 -0
  82. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/general-coder/SKILL.md +0 -0
  83. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/math-calculator/README.md +0 -0
  84. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/math-calculator/SKILL.md +0 -0
  85. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/math-calculator/handler.py +0 -0
  86. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/math-calculator/prompts/system.md +0 -0
  87. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/math-calculator/requirements.txt +0 -0
  88. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/math-calculator/resources/constants.json +0 -0
  89. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/math-calculator/tests/test_handler.py +0 -0
  90. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/security-auditor/SKILL.md +0 -0
  91. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/test-writer/SKILL.md +0 -0
  92. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/weather-skill/README.md +0 -0
  93. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/weather-skill/handler.py +0 -0
  94. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/weather-skill/manifest.json +0 -0
  95. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/weather-skill/prompts/system_prompt.txt +0 -0
  96. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/weather-skill/prompts/user_prompt_template.txt +0 -0
  97. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/weather-skill/requirements.txt +0 -0
  98. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/weather-skill/resources/city_list.json +0 -0
  99. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/weather-skill/resources/error_messages.json +0 -0
  100. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/weather-skill/tests/test_handler.py +0 -0
  101. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills/weather-skill/weather_utils.py +0 -0
  102. {ata_coder-2.4.7 → ata_coder-2.4.8}/skills.py +0 -0
  103. {ata_coder-2.4.7 → ata_coder-2.4.8}/sub_agent.py +0 -0
  104. {ata_coder-2.4.7 → ata_coder-2.4.8}/sub_agent_manager.py +0 -0
  105. {ata_coder-2.4.7 → ata_coder-2.4.8}/system_prompt_builder.py +0 -0
  106. {ata_coder-2.4.7 → ata_coder-2.4.8}/task_planner.py +0 -0
  107. {ata_coder-2.4.7 → ata_coder-2.4.8}/terminal.py +0 -0
  108. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_agent.py +0 -0
  109. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_change_tracker.py +0 -0
  110. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_config.py +0 -0
  111. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_event_queue.py +0 -0
  112. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_extension.py +0 -0
  113. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_fibonacci.py +0 -0
  114. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_fool_proof.py +0 -0
  115. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_llm_client.py +0 -0
  116. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_memory.py +0 -0
  117. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_model_registry.py +0 -0
  118. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_permissions.py +0 -0
  119. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_privilege.py +0 -0
  120. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_prompt_template.py +0 -0
  121. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_safety_guard.py +0 -0
  122. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_server.py +0 -0
  123. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_skill_handlers.py +0 -0
  124. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_sub_agent.py +0 -0
  125. {ata_coder-2.4.7 → ata_coder-2.4.8}/tests/test_tools.py +0 -0
  126. {ata_coder-2.4.7 → ata_coder-2.4.8}/token_counter.py +0 -0
  127. {ata_coder-2.4.7 → ata_coder-2.4.8}/tools/__init__.py +0 -0
  128. {ata_coder-2.4.7 → ata_coder-2.4.8}/tools/definitions.py +0 -0
  129. {ata_coder-2.4.7 → ata_coder-2.4.8}/tools/executor.py +0 -0
  130. {ata_coder-2.4.7 → ata_coder-2.4.8}/tools/result.py +0 -0
  131. {ata_coder-2.4.7 → ata_coder-2.4.8}/tools/strategy.py +0 -0
  132. {ata_coder-2.4.7 → ata_coder-2.4.8}/tools/subagent.py +0 -0
  133. {ata_coder-2.4.7 → ata_coder-2.4.8}/tools/web.py +0 -0
  134. {ata_coder-2.4.7 → ata_coder-2.4.8}/types.py +0 -0
  135. {ata_coder-2.4.7 → ata_coder-2.4.8}/utils.py +0 -0
  136. {ata_coder-2.4.7 → ata_coder-2.4.8}/web/css/style.css +0 -0
  137. {ata_coder-2.4.7 → ata_coder-2.4.8}/web/index.html +0 -0
  138. {ata_coder-2.4.7 → ata_coder-2.4.8}/web/js/app.js +0 -0
  139. {ata_coder-2.4.7 → ata_coder-2.4.8}/web/package-lock.json +0 -0
  140. {ata_coder-2.4.7 → ata_coder-2.4.8}/web/package.json +0 -0
  141. {ata_coder-2.4.7 → ata_coder-2.4.8}/web/tsconfig.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ata-coder
3
- Version: 2.4.7
3
+ Version: 2.4.8
4
4
  Summary: ATA Coder — AI-powered coding assistant
5
5
  Author: ATA Coder Team
6
6
  License-Expression: MIT
@@ -142,6 +142,10 @@ class CoderAgent(CompactionMixin, ToolExecutionMixin,
142
142
  self.self_correct = SelfCorrectionEngine(max_retries=1)
143
143
  self.git = GitWorkflow(self.tools.workspace)
144
144
 
145
+ # Per-instance self-correction depth (was a class variable shared across
146
+ # all agent instances — dangerous under ThreadingHTTPServer in server mode).
147
+ self._self_correct_depth: int = 0
148
+
145
149
  self._state = AgentState()
146
150
  self._on_event: Callable[[AgentEvent], None] | None = None
147
151
  self._current_session_id: str = ""
@@ -17,7 +17,8 @@ class ToolExecutionMixin:
17
17
  # ── Tool execution ────────────────────────────────────────────────────
18
18
 
19
19
  # Guard depth for self-correction retry — prevents infinite recursion.
20
- _self_correct_depth: int = 0
20
+ # These are set as INSTANCE variables in CoderAgent.__init__ to avoid
21
+ # cross-session contamination under ThreadingHTTPServer (server mode).
21
22
  _MAX_SELF_CORRECT_DEPTH: int = 1
22
23
 
23
24
  async def _execute_tool(self, tool_name: str, arguments: dict[str, Any]) -> ToolResult:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ata-coder
3
- Version: 2.4.7
3
+ Version: 2.4.8
4
4
  Summary: ATA Coder — AI-powered coding assistant
5
5
  Author: ATA Coder Team
6
6
  License-Expression: MIT
@@ -143,7 +143,7 @@ class ChangeTracker:
143
143
  old_content = f.read()
144
144
  self._backup(file_path)
145
145
  except Exception:
146
- pass
146
+ logger.debug("Failed to read/backup existing file %s", file_path, exc_info=True)
147
147
 
148
148
  change = FileChange(
149
149
  id=self._next_id,
@@ -197,17 +197,21 @@ class AppConfig:
197
197
  # Using a lazy pattern because AppConfig.load() references _from_settings()
198
198
  # which is defined after the dataclass body in this module.
199
199
  _config: AppConfig | None = None
200
+ _config_lock = threading.Lock()
200
201
 
201
202
 
202
203
  def get_config() -> AppConfig:
203
204
  """Return the module-level config singleton (lazy init on first call).
204
205
 
205
- After the first call the config is cached. The codebase is
206
- single-threaded by design so no lock is needed.
206
+ After the first call the config is cached. Double-checked locking
207
+ protects the lazy-init path when server.py runs under ThreadingHTTPServer
208
+ (where multiple threads may race on the first call).
207
209
  """
208
210
  global _config
209
211
  if _config is None:
210
- _config = AppConfig.load()
212
+ with _config_lock:
213
+ if _config is None: # double-check
214
+ _config = AppConfig.load()
211
215
  return _config
212
216
 
213
217
 
@@ -44,7 +44,7 @@ if sys.platform == 'win32':
44
44
  _patched_init.__ata_patched__ = True
45
45
  _sp.Popen.__init__ = _patched_init
46
46
 
47
- __version__ = "2.4.7"
47
+ __version__ = "2.4.8"
48
48
 
49
49
  import asyncio
50
50
  import logging
@@ -90,12 +90,12 @@ def _signal_handler(sig, frame):
90
90
  try:
91
91
  get_clawd().shutdown()
92
92
  except Exception:
93
- pass
93
+ logger.debug("Clawd shutdown failed during signal handler", exc_info=True)
94
94
  for handler in _cleanup_handlers:
95
95
  try:
96
96
  handler()
97
97
  except Exception:
98
- pass
98
+ logger.debug("Cleanup handler %s failed", handler, exc_info=True)
99
99
  sys.exit(1)
100
100
 
101
101
 
@@ -97,6 +97,9 @@ class Memory:
97
97
  @classmethod
98
98
  def from_frontmatter(cls, raw: str) -> "Memory | None":
99
99
  """Parse a markdown file with YAML frontmatter into a Memory."""
100
+ # Non-greedy match for the frontmatter separator; uses a negative
101
+ # lookahead to ensure we match the FIRST closing --- (avoiding false
102
+ # matches on YAML separators inside code blocks in the body).
100
103
  match = re.match(r"^---\s*\n(.*?)\n---\s*\n(.*)", raw, re.DOTALL)
101
104
  if not match:
102
105
  return None
@@ -167,15 +167,19 @@ def wrap_privileged_command(command: str) -> str:
167
167
  )
168
168
 
169
169
  elif os_family == OSFamily.WINDOWS:
170
- # Encode command as base64 (utf-16-le) to avoid escaping nightmares
171
- # in PowerShell's nested quoting. Use subprocess-style argument list
172
- # rather than string interpolation to prevent command injection.
170
+ # Encode the entire command as a single base64 PowerShell script to
171
+ # avoid nested-quoting injection through cmd.exe. The script is
172
+ # executed directly by PowerShell without going through cmd.exe at all,
173
+ # which eliminates the shlex.quote / cmd.exe quoting mismatch.
174
+ # We embed the base64 script in a here-string so that special
175
+ # characters ($, `, ", etc.) in the original command are harmless.
173
176
  encoded = base64.b64encode(command.encode("utf-16-le")).decode()
174
- inner_cmd = f"/c powershell -EncodedCommand {encoded}"
177
+ # Use PowerShell -EncodedCommand directly (no cmd.exe intermediary)
175
178
  return (
176
179
  'powershell -Command "'
177
- 'Start-Process -Verb RunAs -Wait -FilePath cmd.exe '
178
- f'-ArgumentList {shlex.quote(inner_cmd)}"'
180
+ 'Start-Process -Verb RunAs -Wait -FilePath powershell.exe '
181
+ f'-ArgumentList \\"-NoProfile -EncodedCommand {encoded}\\"'
182
+ '"'
179
183
  )
180
184
 
181
185
  return command
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ata-coder"
7
- version = "2.4.7"
7
+ version = "2.4.8"
8
8
  description = "ATA Coder — AI-powered coding assistant"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -114,7 +114,7 @@ DESTRUCTIVE_PATTERNS = [
114
114
  (r">\s*/dev/sd", RiskLevel.CRITICAL, "Direct disk write"),
115
115
  (r">\s*/dev/nvme", RiskLevel.CRITICAL, "Direct NVMe write"),
116
116
  (r"\bshred\s+", RiskLevel.DANGER, "Secure file deletion"),
117
- (r"\$\(.*\)", RiskLevel.CAUTION, "Command substitution detected"),
117
+ (r"\$\([^)]+\)", RiskLevel.CAUTION, "Command substitution detected"),
118
118
  (r"`[^`]+`", RiskLevel.CAUTION, "Backtick command substitution detected"),
119
119
  (r"chmod\s+777\s+/", RiskLevel.CRITICAL, "World-writable root"),
120
120
  (r"chmod\s+-R\s+777\s+/", RiskLevel.CRITICAL, "World-writable root recursive"),
@@ -70,12 +70,12 @@ class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
70
70
  b"",
71
71
  ]))
72
72
  except Exception:
73
- pass
73
+ logger.debug("Failed to send 503 response", exc_info=True)
74
74
  finally:
75
75
  try:
76
76
  request.close()
77
77
  except Exception:
78
- pass
78
+ logger.debug("Failed to close rejected request socket", exc_info=True)
79
79
  return
80
80
  # Atomic read-modify-write under lock (was: bare GIL-only read+write)
81
81
  self._active_threads += 1
@@ -83,6 +83,10 @@ class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
83
83
  super().process_request(request, client_address)
84
84
  finally:
85
85
  with self._thread_lock:
86
+ if self._active_threads <= 0:
87
+ logger.error("Thread counter underflow! _active_threads=%d — "
88
+ "a decrement was matched to no increment, indicating "
89
+ "a bug in process_request pairing.", self._active_threads)
86
90
  self._active_threads = max(0, self._active_threads - 1)
87
91
  from pathlib import Path
88
92
  from typing import Any
@@ -45,7 +45,7 @@ class SessionStore:
45
45
  try:
46
46
  asyncio.run(agent.shutdown())
47
47
  except Exception:
48
- pass
48
+ logger.debug("Agent shutdown via asyncio.run() failed", exc_info=True)
49
49
  return
50
50
  # Event loop is running — schedule on it via thread-safe mechanism
51
51
  try:
@@ -111,7 +111,7 @@ def run_setup_wizard() -> None:
111
111
  print()
112
112
 
113
113
 
114
- __version__ = "2.4.7"
114
+ __version__ = "2.4.8"
115
115
 
116
116
 
117
117
  def print_banner() -> None:
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