pentool 0.2.6__tar.gz → 0.2.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 (169) hide show
  1. {pentool-0.2.6 → pentool-0.2.8}/PKG-INFO +1 -1
  2. {pentool-0.2.6 → pentool-0.2.8}/pentool/__init__.py +1 -1
  3. {pentool-0.2.6 → pentool-0.2.8}/pentool/__main__.py +31 -0
  4. {pentool-0.2.6 → pentool-0.2.8}/pentool/core/license.py +41 -11
  5. {pentool-0.2.6 → pentool-0.2.8}/pentool.egg-info/PKG-INFO +1 -1
  6. {pentool-0.2.6 → pentool-0.2.8}/pyproject.toml +1 -1
  7. {pentool-0.2.6 → pentool-0.2.8}/LICENSE +0 -0
  8. {pentool-0.2.6 → pentool-0.2.8}/README.md +0 -0
  9. {pentool-0.2.6 → pentool-0.2.8}/pentool/api/__init__.py +0 -0
  10. {pentool-0.2.6 → pentool-0.2.8}/pentool/api/base_api.py +0 -0
  11. {pentool-0.2.6 → pentool-0.2.8}/pentool/api/comparer_api.py +0 -0
  12. {pentool-0.2.6 → pentool-0.2.8}/pentool/api/decoder_api.py +0 -0
  13. {pentool-0.2.6 → pentool-0.2.8}/pentool/api/intruder_api.py +0 -0
  14. {pentool-0.2.6 → pentool-0.2.8}/pentool/api/proxy_api.py +0 -0
  15. {pentool-0.2.6 → pentool-0.2.8}/pentool/api/repeater_api.py +0 -0
  16. {pentool-0.2.6 → pentool-0.2.8}/pentool/api/sequencer_api.py +0 -0
  17. {pentool-0.2.6 → pentool-0.2.8}/pentool/api/spider_api.py +0 -0
  18. {pentool-0.2.6 → pentool-0.2.8}/pentool/api/target_api.py +0 -0
  19. {pentool-0.2.6 → pentool-0.2.8}/pentool/cli/__init__.py +0 -0
  20. {pentool-0.2.6 → pentool-0.2.8}/pentool/cli/main.py +0 -0
  21. {pentool-0.2.6 → pentool-0.2.8}/pentool/cli/project.py +0 -0
  22. {pentool-0.2.6 → pentool-0.2.8}/pentool/cli/proxy.py +0 -0
  23. {pentool-0.2.6 → pentool-0.2.8}/pentool/cli/scan.py +0 -0
  24. {pentool-0.2.6 → pentool-0.2.8}/pentool/core/__init__.py +0 -0
  25. {pentool-0.2.6 → pentool-0.2.8}/pentool/core/config.py +0 -0
  26. {pentool-0.2.6 → pentool-0.2.8}/pentool/core/crash_reporter.py +0 -0
  27. {pentool-0.2.6 → pentool-0.2.8}/pentool/core/database.py +0 -0
  28. {pentool-0.2.6 → pentool-0.2.8}/pentool/core/event_bus.py +0 -0
  29. {pentool-0.2.6 → pentool-0.2.8}/pentool/core/events.py +0 -0
  30. {pentool-0.2.6 → pentool-0.2.8}/pentool/core/features.py +0 -0
  31. {pentool-0.2.6 → pentool-0.2.8}/pentool/core/logging.py +0 -0
  32. {pentool-0.2.6 → pentool-0.2.8}/pentool/core/plugin_manager.py +0 -0
  33. {pentool-0.2.6 → pentool-0.2.8}/pentool/core/project.py +0 -0
  34. {pentool-0.2.6 → pentool-0.2.8}/pentool/core/storage_interface.py +0 -0
  35. {pentool-0.2.6 → pentool-0.2.8}/pentool/core/updater.py +0 -0
  36. {pentool-0.2.6 → pentool-0.2.8}/pentool/core/utils.py +0 -0
  37. {pentool-0.2.6 → pentool-0.2.8}/pentool/modules/__init__.py +0 -0
  38. {pentool-0.2.6 → pentool-0.2.8}/pentool/modules/comparer.py +0 -0
  39. {pentool-0.2.6 → pentool-0.2.8}/pentool/modules/decoder.py +0 -0
  40. {pentool-0.2.6 → pentool-0.2.8}/pentool/modules/intruder.py +0 -0
  41. {pentool-0.2.6 → pentool-0.2.8}/pentool/modules/match_replace.py +0 -0
  42. {pentool-0.2.6 → pentool-0.2.8}/pentool/modules/proxy.py +0 -0
  43. {pentool-0.2.6 → pentool-0.2.8}/pentool/modules/repeater.py +0 -0
  44. {pentool-0.2.6 → pentool-0.2.8}/pentool/modules/sequencer.py +0 -0
  45. {pentool-0.2.6 → pentool-0.2.8}/pentool/modules/spider.py +0 -0
  46. {pentool-0.2.6 → pentool-0.2.8}/pentool/modules/target.py +0 -0
  47. {pentool-0.2.6 → pentool-0.2.8}/pentool/modules/websocket_handler.py +0 -0
  48. {pentool-0.2.6 → pentool-0.2.8}/pentool/plugins/__init__.py +0 -0
  49. {pentool-0.2.6 → pentool-0.2.8}/pentool/plugins/builtin/__init__.py +0 -0
  50. {pentool-0.2.6 → pentool-0.2.8}/pentool/plugins/example_plugin.py +0 -0
  51. {pentool-0.2.6 → pentool-0.2.8}/pentool/plugins/example_plugin.tcss +0 -0
  52. {pentool-0.2.6 → pentool-0.2.8}/pentool/services/__init__.py +0 -0
  53. {pentool-0.2.6 → pentool-0.2.8}/pentool/services/base_service.py +0 -0
  54. {pentool-0.2.6 → pentool-0.2.8}/pentool/services/intruder_service.py +0 -0
  55. {pentool-0.2.6 → pentool-0.2.8}/pentool/services/proxy_service.py +0 -0
  56. {pentool-0.2.6 → pentool-0.2.8}/pentool/services/repeater_service.py +0 -0
  57. {pentool-0.2.6 → pentool-0.2.8}/pentool/services/scan_service.py +0 -0
  58. {pentool-0.2.6 → pentool-0.2.8}/pentool/storage/__init__.py +0 -0
  59. {pentool-0.2.6 → pentool-0.2.8}/pentool/storage/http_storage.py +0 -0
  60. {pentool-0.2.6 → pentool-0.2.8}/pentool/storage/large_body_handler.py +0 -0
  61. {pentool-0.2.6 → pentool-0.2.8}/pentool/storage/lru_cache.py +0 -0
  62. {pentool-0.2.6 → pentool-0.2.8}/pentool/t.py +0 -0
  63. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/__init__.py +0 -0
  64. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/app.py +0 -0
  65. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/constants.py +0 -0
  66. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/__init__.py +0 -0
  67. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/cert_dialog.py +0 -0
  68. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/cert_dialog.tcss +0 -0
  69. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/comment_dialog.py +0 -0
  70. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/comment_dialog.tcss +0 -0
  71. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/file_selector.py +0 -0
  72. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/file_selector.tcss +0 -0
  73. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/load_from_proxy.py +0 -0
  74. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/load_from_proxy.tcss +0 -0
  75. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/match_replace_dialog.py +0 -0
  76. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/match_replace_dialog.tcss +0 -0
  77. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/project_dialog.py +0 -0
  78. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/project_dialog.tcss +0 -0
  79. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/scope_dialog.py +0 -0
  80. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/dialogs/scope_dialog.tcss +0 -0
  81. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/messages.py +0 -0
  82. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/mixins/__init__.py +0 -0
  83. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/mixins/app_mixin.py +0 -0
  84. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/mixins/dialog_cancel.py +0 -0
  85. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/mixins/request_context_menu.py +0 -0
  86. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/project_manager.py +0 -0
  87. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/__init__.py +0 -0
  88. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/base.py +0 -0
  89. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/comparer/__init__.py +0 -0
  90. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/comparer/screen.py +0 -0
  91. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/comparer/screen.tcss +0 -0
  92. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/dashboard/__init__.py +0 -0
  93. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/dashboard/live_dashboard.py +0 -0
  94. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/dashboard/screen.py +0 -0
  95. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/dashboard/screen.tcss +0 -0
  96. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/decoder/__init__.py +0 -0
  97. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/decoder/screen.py +0 -0
  98. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/decoder/screen.tcss +0 -0
  99. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/extensions/__init__.py +0 -0
  100. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/extensions/screen.py +0 -0
  101. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/extensions/screen.tcss +0 -0
  102. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/intruder/__init__.py +0 -0
  103. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/intruder/screen.py +0 -0
  104. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/intruder/screen.tcss +0 -0
  105. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/proxy/__init__.py +0 -0
  106. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/proxy/screen.py +0 -0
  107. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/proxy/screen.tcss +0 -0
  108. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/repeater/__init__.py +0 -0
  109. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/repeater/screen.py +0 -0
  110. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/repeater/screen.tcss +0 -0
  111. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/scanner_unavailable.py +0 -0
  112. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/sequencer/__init__.py +0 -0
  113. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/sequencer/screen.py +0 -0
  114. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/sequencer/screen.tcss +0 -0
  115. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/settings/__init__.py +0 -0
  116. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/settings/hotkeys.py +0 -0
  117. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/settings/hotkeys.tcss +0 -0
  118. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/settings/screen.py +0 -0
  119. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/settings/screen.tcss +0 -0
  120. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/spider/__init__.py +0 -0
  121. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/spider/screen.py +0 -0
  122. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/spider/screen.tcss +0 -0
  123. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/target/__init__.py +0 -0
  124. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/target/screen.py +0 -0
  125. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/target/screen.tcss +0 -0
  126. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/terminal/__init__.py +0 -0
  127. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/terminal/screen.py +0 -0
  128. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/screens/terminal/screen.tcss +0 -0
  129. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/__init__.py +0 -0
  130. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/activity_indicator.py +0 -0
  131. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/activity_indicator.tcss +0 -0
  132. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/context_menu.py +0 -0
  133. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/context_menu.tcss +0 -0
  134. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/filter_bar.py +0 -0
  135. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/filter_bar.tcss +0 -0
  136. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/inspector_panel.py +0 -0
  137. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/inspector_panel.tcss +0 -0
  138. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/menu.tcss +0 -0
  139. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/menu_bar.tcss +0 -0
  140. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/module_tabs.py +0 -0
  141. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/module_tabs.tcss +0 -0
  142. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/nice_checkbox.py +0 -0
  143. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/payload_drop_zone.py +0 -0
  144. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/payload_drop_zone.tcss +0 -0
  145. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/request_editor.py +0 -0
  146. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/request_editor.tcss +0 -0
  147. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/resize_handle.py +0 -0
  148. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/resize_handle.tcss +0 -0
  149. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/search_bar.py +0 -0
  150. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/search_bar.tcss +0 -0
  151. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/statusbar.py +0 -0
  152. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/statusbar.tcss +0 -0
  153. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/toolbar_button.py +0 -0
  154. {pentool-0.2.6 → pentool-0.2.8}/pentool/tui/widgets/toolbar_button.tcss +0 -0
  155. {pentool-0.2.6 → pentool-0.2.8}/pentool/utils/__init__.py +0 -0
  156. {pentool-0.2.6 → pentool-0.2.8}/pentool/utils/cert.py +0 -0
  157. {pentool-0.2.6 → pentool-0.2.8}/pentool/utils/coder.py +0 -0
  158. {pentool-0.2.6 → pentool-0.2.8}/pentool/utils/copy_as.py +0 -0
  159. {pentool-0.2.6 → pentool-0.2.8}/pentool/utils/diff.py +0 -0
  160. {pentool-0.2.6 → pentool-0.2.8}/pentool/utils/http_client.py +0 -0
  161. {pentool-0.2.6 → pentool-0.2.8}/pentool/utils/parser.py +0 -0
  162. {pentool-0.2.6 → pentool-0.2.8}/pentool/utils/terminal_check.py +0 -0
  163. {pentool-0.2.6 → pentool-0.2.8}/pentool.egg-info/SOURCES.txt +0 -0
  164. {pentool-0.2.6 → pentool-0.2.8}/pentool.egg-info/dependency_links.txt +0 -0
  165. {pentool-0.2.6 → pentool-0.2.8}/pentool.egg-info/entry_points.txt +0 -0
  166. {pentool-0.2.6 → pentool-0.2.8}/pentool.egg-info/requires.txt +0 -0
  167. {pentool-0.2.6 → pentool-0.2.8}/pentool.egg-info/top_level.txt +0 -0
  168. {pentool-0.2.6 → pentool-0.2.8}/setup.cfg +0 -0
  169. {pentool-0.2.6 → pentool-0.2.8}/tests/test_user_workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pentool
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: Modern Web Pentesting TUI - Professional security testing tool
5
5
  Author-email: Pentool Team <dev@pentool.pro>
6
6
  License: AGPL-3.0
@@ -1,6 +1,6 @@
1
1
  """Pentool — professional web security testing toolkit with Textual TUI."""
2
2
 
3
- __version__ = "0.2.6"
3
+ __version__ = "0.2.8"
4
4
  __author__ = "pentool"
5
5
 
6
6
 
@@ -50,6 +50,37 @@ def main() -> None:
50
50
  except Exception:
51
51
  compatible, warning = True, "" # never block startup over this check itself
52
52
 
53
+ if not compatible:
54
+ # Try to self-heal BEFORE deciding to exit. check_and_update_pro_
55
+ # package() is normally only reached from inside a running TUI
56
+ # session (on_mount's background worker) or from `pentool
57
+ # update`/`--upgrade` — but a bare `pentool` launch with an
58
+ # incompatible package used to bail out via SystemExit below
59
+ # before ever getting a chance to call it, so a broken install
60
+ # could never repair itself from a plain `pentool` invocation:
61
+ # the user was stuck needing `pentool license activate <key>`
62
+ # even though the exact same license key/machine_id could have
63
+ # fixed it automatically. Attempt one repair pass here first.
64
+ try:
65
+ import asyncio
66
+
67
+ from pentool.core.license import check_and_update_pro_package
68
+ result = asyncio.run(check_and_update_pro_package())
69
+ if result.updated:
70
+ print(
71
+ "[pentool] PRO package was out of sync — "
72
+ "re-downloaded successfully.",
73
+ file=sys.stderr,
74
+ )
75
+ compatible, warning = True, ""
76
+ elif not result.warning:
77
+ # No warning back means check_and_update_pro_package()
78
+ # itself now considers things fine (e.g. it just healed
79
+ # a previously-broken package with a matching build_id).
80
+ compatible, warning = is_pro_package_compatible()
81
+ except Exception:
82
+ pass # fall through to the original warning/exit below
83
+
53
84
  if not compatible:
54
85
  if unsafe_skip_pro_check:
55
86
  print(
@@ -648,6 +648,7 @@ async def download_pro_package(key: str, machine_id: str) -> bool:
648
648
  plat = _current_platform()
649
649
  try:
650
650
  import aiohttp
651
+ build_id: str | None = None
651
652
  async with aiohttp.ClientSession(
652
653
  timeout=aiohttp.ClientTimeout(total=60)
653
654
  ) as session:
@@ -659,6 +660,13 @@ async def download_pro_package(key: str, machine_id: str) -> bool:
659
660
  if resp.status != 200:
660
661
  return False
661
662
  archive_bytes = await resp.read()
663
+ # The Worker resolves the release/asset to serve this archive
664
+ # and knows its build_id at that point — it now echoes it back
665
+ # in this same response (see pentool-backend's /api/download
666
+ # handler) so we don't need a separate follow-up request just
667
+ # to learn it. Falls back to _fetch_pro_build_id() below if
668
+ # an older Worker deploy doesn't send the header yet.
669
+ build_id = resp.headers.get("X-Pro-Build-Id")
662
670
 
663
671
  async with session.post(
664
672
  f"{_LICENSE_API_BASE}/api/download",
@@ -685,12 +693,25 @@ async def download_pro_package(key: str, machine_id: str) -> bool:
685
693
  # check_and_update_pro_package() can tell "nothing changed" from "a
686
694
  # new build was published" without re-downloading the archive just
687
695
  # to compare it.
688
- try:
689
- build_id = await _fetch_pro_build_id(plat)
690
- if build_id:
691
- _write_pro_meta(build_id)
692
- except Exception:
693
- pass # non-fatal — worst case, next check just re-downloads once more
696
+ #
697
+ # Writing this is what makes the extracted package "trusted" —
698
+ # is_pro_package_compatible() refuses to load it otherwise. Getting
699
+ # here means the archive already extracted successfully, so this
700
+ # must not silently no-op: if the Worker didn't send the header
701
+ # (old deploy) AND the fallback /api/pro/version call also fails
702
+ # (flaky network), fall back to recording the current FREE version
703
+ # with a synthetic build_id rather than leaving .build_meta.json
704
+ # unwritten — a fully-installed, working PRO package should never be
705
+ # stuck showing "doesn't record which version" just because one
706
+ # extra network round-trip hiccupped after the real work was done.
707
+ if not build_id:
708
+ try:
709
+ build_id = await _fetch_pro_build_id(plat)
710
+ except Exception:
711
+ build_id = None
712
+ if not build_id:
713
+ build_id = f"unknown:{time.time()}"
714
+ _write_pro_meta(build_id)
694
715
 
695
716
  return True
696
717
  except Exception:
@@ -771,15 +792,24 @@ async def check_and_update_pro_package() -> "ProSyncResult":
771
792
 
772
793
  meta = _read_pro_meta()
773
794
  local_build_id = meta.get("build_id", "")
774
-
775
- if local_build_id == remote_build_id:
776
- _compatible, warning = is_pro_package_compatible()
777
- return ProSyncResult(updated=False, warning=warning)
795
+ _compatible, _warning = is_pro_package_compatible()
796
+
797
+ # Even if the build_id hasn't changed (no new release published since),
798
+ # a package already marked incompatible (e.g. free_version never got
799
+ # recorded, from before the /api/download build_id-header fix) must
800
+ # still be re-downloaded — otherwise this function forever reports the
801
+ # same "doesn't record which version" warning and never actually heals
802
+ # it, since download_pro_package() is only reached below when the
803
+ # build_id differs. A once-broken install with a matching build_id
804
+ # would never take that path without this check.
805
+ if local_build_id == remote_build_id and _compatible:
806
+ return ProSyncResult(updated=False, warning="")
778
807
 
779
808
  updated = await download_pro_package(info.license_key, info.machine_id)
780
809
  if updated:
781
810
  return ProSyncResult(updated=True, warning="")
782
811
 
783
- # Download of the newer build failed — warn if what's left on disk is stale.
812
+ # Download of the newer/repaired build failed — warn if what's left on
813
+ # disk is stale.
784
814
  _compatible, warning = is_pro_package_compatible()
785
815
  return ProSyncResult(updated=False, warning=warning)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pentool
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: Modern Web Pentesting TUI - Professional security testing tool
5
5
  Author-email: Pentool Team <dev@pentool.pro>
6
6
  License: AGPL-3.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pentool"
7
- version = "0.2.6"
7
+ version = "0.2.8"
8
8
  description = "Modern Web Pentesting TUI - Professional security testing tool"
9
9
  authors = [
10
10
  {name = "Pentool Team", email = "dev@pentool.pro"}
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