pentool 0.2.4__tar.gz → 0.2.5__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.
- {pentool-0.2.4 → pentool-0.2.5}/PKG-INFO +10 -10
- {pentool-0.2.4 → pentool-0.2.5}/README.md +5 -5
- {pentool-0.2.4 → pentool-0.2.5}/pentool/__init__.py +29 -2
- pentool-0.2.5/pentool/__main__.py +95 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/cli/main.py +42 -3
- {pentool-0.2.4 → pentool-0.2.5}/pentool/core/license.py +167 -28
- {pentool-0.2.4 → pentool-0.2.5}/pentool/core/plugin_manager.py +34 -1
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/app.py +39 -5
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/intruder/screen.py +24 -13
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/spider/screen.py +27 -5
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/target/screen.py +30 -15
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/terminal/screen.py +10 -5
- pentool-0.2.5/pentool/tui/widgets/activity_indicator.py +215 -0
- pentool-0.2.5/pentool/tui/widgets/activity_indicator.tcss +63 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool.egg-info/PKG-INFO +10 -10
- {pentool-0.2.4 → pentool-0.2.5}/pyproject.toml +5 -5
- pentool-0.2.4/pentool/__main__.py +0 -39
- pentool-0.2.4/pentool/tui/widgets/activity_indicator.py +0 -143
- pentool-0.2.4/pentool/tui/widgets/activity_indicator.tcss +0 -30
- {pentool-0.2.4 → pentool-0.2.5}/LICENSE +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/api/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/api/base_api.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/api/comparer_api.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/api/decoder_api.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/api/intruder_api.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/api/proxy_api.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/api/repeater_api.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/api/sequencer_api.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/api/spider_api.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/api/target_api.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/cli/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/cli/project.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/cli/proxy.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/cli/scan.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/core/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/core/config.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/core/crash_reporter.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/core/database.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/core/event_bus.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/core/events.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/core/features.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/core/logging.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/core/project.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/core/storage_interface.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/core/updater.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/core/utils.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/modules/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/modules/comparer.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/modules/decoder.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/modules/intruder.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/modules/match_replace.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/modules/proxy.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/modules/repeater.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/modules/sequencer.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/modules/spider.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/modules/target.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/modules/websocket_handler.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/plugins/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/plugins/builtin/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/plugins/example_plugin.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/plugins/example_plugin.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/services/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/services/base_service.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/services/intruder_service.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/services/proxy_service.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/services/repeater_service.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/services/scan_service.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/storage/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/storage/http_storage.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/storage/large_body_handler.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/storage/lru_cache.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/t.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/constants.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/cert_dialog.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/cert_dialog.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/comment_dialog.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/comment_dialog.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/file_selector.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/file_selector.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/load_from_proxy.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/load_from_proxy.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/match_replace_dialog.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/match_replace_dialog.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/project_dialog.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/project_dialog.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/scope_dialog.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/dialogs/scope_dialog.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/messages.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/mixins/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/mixins/app_mixin.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/mixins/dialog_cancel.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/mixins/request_context_menu.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/project_manager.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/base.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/comparer/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/comparer/screen.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/comparer/screen.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/dashboard/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/dashboard/live_dashboard.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/dashboard/screen.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/dashboard/screen.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/decoder/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/decoder/screen.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/decoder/screen.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/extensions/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/extensions/screen.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/extensions/screen.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/intruder/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/intruder/screen.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/proxy/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/proxy/screen.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/proxy/screen.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/repeater/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/repeater/screen.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/repeater/screen.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/scanner_unavailable.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/sequencer/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/sequencer/screen.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/sequencer/screen.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/settings/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/settings/hotkeys.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/settings/hotkeys.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/settings/screen.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/settings/screen.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/spider/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/spider/screen.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/target/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/target/screen.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/terminal/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/screens/terminal/screen.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/context_menu.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/context_menu.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/filter_bar.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/filter_bar.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/inspector_panel.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/inspector_panel.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/menu.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/menu_bar.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/module_tabs.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/module_tabs.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/nice_checkbox.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/payload_drop_zone.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/payload_drop_zone.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/request_editor.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/request_editor.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/resize_handle.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/resize_handle.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/search_bar.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/search_bar.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/statusbar.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/statusbar.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/toolbar_button.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/tui/widgets/toolbar_button.tcss +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/utils/__init__.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/utils/cert.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/utils/coder.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/utils/copy_as.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/utils/diff.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/utils/http_client.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/utils/parser.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool/utils/terminal_check.py +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool.egg-info/SOURCES.txt +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool.egg-info/dependency_links.txt +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool.egg-info/entry_points.txt +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool.egg-info/requires.txt +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/pentool.egg-info/top_level.txt +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/setup.cfg +0 -0
- {pentool-0.2.4 → pentool-0.2.5}/tests/test_user_workflow.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pentool
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
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
|
|
7
|
-
Project-URL: Homepage, https://github.com/
|
|
8
|
-
Project-URL: Repository, https://github.com/
|
|
9
|
-
Project-URL: Issues, https://github.com/
|
|
10
|
-
Project-URL: Changelog, https://github.com/
|
|
7
|
+
Project-URL: Homepage, https://github.com/DrXOps/pentool
|
|
8
|
+
Project-URL: Repository, https://github.com/DrXOps/pentool
|
|
9
|
+
Project-URL: Issues, https://github.com/DrXOps/pentool/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/DrXOps/pentool/blob/main/CHANGELOG.md
|
|
11
11
|
Keywords: pentesting,security,burp,proxy,scanner,intruder
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -50,7 +50,7 @@ Dynamic: license-file
|
|
|
50
50
|
|
|
51
51
|
[](https://pypi.org/project/pentool/)
|
|
52
52
|
[](https://pypi.org/project/pentool/)
|
|
53
|
-
[](https://github.com/DrXOps/pentool/actions)
|
|
54
54
|
[](LICENSE)
|
|
55
55
|
[](https://pypi.org/project/pentool/)
|
|
56
56
|
|
|
@@ -70,15 +70,15 @@ Fast, transparent, and built for real-world testing.
|
|
|
70
70
|
|
|
71
71
|
| Dashboard | Scanner |
|
|
72
72
|
|:---------:|:-------:|
|
|
73
|
-
|  |  |
|
|
74
74
|
|
|
75
75
|
| Proxy | Repeater |
|
|
76
76
|
|:-----:|:--------:|
|
|
77
|
-
|  |  |
|
|
78
78
|
|
|
79
79
|
| Intruder | Settings |
|
|
80
80
|
|:--------:|:--------:|
|
|
81
|
-
|  |  |
|
|
82
82
|
|
|
83
83
|
---
|
|
84
84
|
|
|
@@ -185,7 +185,7 @@ No minimum follower count. We value quality over reach.
|
|
|
185
185
|
Pentool is built and maintained by a solo developer in spare time.
|
|
186
186
|
If it saves you hours on a pentest — consider giving back. Every contribution directly funds new features, fixes, and faster releases.
|
|
187
187
|
|
|
188
|
-
- ⭐ **[Star on GitHub](https://github.com/
|
|
188
|
+
- ⭐ **[Star on GitHub](https://github.com/DrXOps/pentool)** — free, takes 2 seconds, helps visibility enormously
|
|
189
189
|
- 🔑 **PRO license** — get early access and support development → **[@sudores](https://t.me/sudores)**
|
|
190
190
|
- 💬 **Share** — tell a colleague, post a review, or mention Pentool in your writeups
|
|
191
191
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://pypi.org/project/pentool/)
|
|
6
6
|
[](https://pypi.org/project/pentool/)
|
|
7
|
-
[](https://github.com/DrXOps/pentool/actions)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://pypi.org/project/pentool/)
|
|
10
10
|
|
|
@@ -24,15 +24,15 @@ Fast, transparent, and built for real-world testing.
|
|
|
24
24
|
|
|
25
25
|
| Dashboard | Scanner |
|
|
26
26
|
|:---------:|:-------:|
|
|
27
|
-
|  |  |
|
|
28
28
|
|
|
29
29
|
| Proxy | Repeater |
|
|
30
30
|
|:-----:|:--------:|
|
|
31
|
-
|  |  |
|
|
32
32
|
|
|
33
33
|
| Intruder | Settings |
|
|
34
34
|
|:--------:|:--------:|
|
|
35
|
-
|  |  |
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
@@ -139,7 +139,7 @@ No minimum follower count. We value quality over reach.
|
|
|
139
139
|
Pentool is built and maintained by a solo developer in spare time.
|
|
140
140
|
If it saves you hours on a pentest — consider giving back. Every contribution directly funds new features, fixes, and faster releases.
|
|
141
141
|
|
|
142
|
-
- ⭐ **[Star on GitHub](https://github.com/
|
|
142
|
+
- ⭐ **[Star on GitHub](https://github.com/DrXOps/pentool)** — free, takes 2 seconds, helps visibility enormously
|
|
143
143
|
- 🔑 **PRO license** — get early access and support development → **[@sudores](https://t.me/sudores)**
|
|
144
144
|
- 💬 **Share** — tell a colleague, post a review, or mention Pentool in your writeups
|
|
145
145
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Pentool — professional web security testing toolkit with Textual TUI."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.2.
|
|
3
|
+
__version__ = "0.2.5"
|
|
4
4
|
__author__ = "pentool"
|
|
5
5
|
|
|
6
6
|
|
|
@@ -26,6 +26,22 @@ def _bootstrap_pro() -> None:
|
|
|
26
26
|
every obfuscated PRO module fails to import with
|
|
27
27
|
`ModuleNotFoundError: No module named 'codeenigma_runtime'` even though
|
|
28
28
|
pentool/ itself resolves fine via __path__ extension above.
|
|
29
|
+
|
|
30
|
+
The installed (~/.pentool/pro/) PRO package is skipped entirely — not
|
|
31
|
+
even added to __path__ — if it was built for a different FREE version
|
|
32
|
+
than this one (core.license.is_pro_package_compatible()). This is the
|
|
33
|
+
scenario where a FREE upgrade (`pip install --upgrade pentool` /
|
|
34
|
+
`pentool update`) succeeded but the PRO package's own re-sync
|
|
35
|
+
afterwards failed or never ran (e.g. offline, or an unrelated version
|
|
36
|
+
check like the GitHub release lookup errored out first and stopped the
|
|
37
|
+
whole `pentool update` command before it got to the PRO sync step).
|
|
38
|
+
Loading a mismatched PRO build there risks a hard-to-diagnose crash: it
|
|
39
|
+
ships a compiled Cython extension, and an ABI/version mismatch can
|
|
40
|
+
segfault the process instead of raising a catchable Python exception.
|
|
41
|
+
The dev `pro/` submodule checkout is never version-mismatched (same
|
|
42
|
+
source tree as the running FREE code) and is always loaded normally.
|
|
43
|
+
A warning is printed to stderr so the user isn't left with unexplained
|
|
44
|
+
missing PRO features and no context.
|
|
29
45
|
"""
|
|
30
46
|
import sys
|
|
31
47
|
from pathlib import Path
|
|
@@ -33,9 +49,10 @@ def _bootstrap_pro() -> None:
|
|
|
33
49
|
_pkg_dir = Path(__file__).resolve().parent # .../pentool/pentool/
|
|
34
50
|
_repo_root = _pkg_dir.parent # .../pentool/
|
|
35
51
|
|
|
52
|
+
_installed_pro_pkg = Path.home() / ".pentool" / "pro" / "pentool"
|
|
36
53
|
candidates: list[Path] = [
|
|
37
54
|
_repo_root / "pro" / "pentool", # dev submodule
|
|
38
|
-
|
|
55
|
+
_installed_pro_pkg, # installed PRO package
|
|
39
56
|
]
|
|
40
57
|
|
|
41
58
|
import pentool as _self
|
|
@@ -44,6 +61,16 @@ def _bootstrap_pro() -> None:
|
|
|
44
61
|
if not _pro_pkg.exists():
|
|
45
62
|
continue
|
|
46
63
|
|
|
64
|
+
if _pro_pkg == _installed_pro_pkg:
|
|
65
|
+
try:
|
|
66
|
+
from pentool.core.license import is_pro_package_compatible
|
|
67
|
+
_compatible, _warning = is_pro_package_compatible()
|
|
68
|
+
except Exception:
|
|
69
|
+
_compatible, _warning = True, "" # never block startup over this check itself
|
|
70
|
+
if not _compatible:
|
|
71
|
+
print(f"[pentool] {_warning}", file=sys.stderr)
|
|
72
|
+
continue
|
|
73
|
+
|
|
47
74
|
# 1. Extend top-level pentool.__path__ so that sub-packages
|
|
48
75
|
# resolved via pkgutil.extend_path below will find pro/pentool/XXX.
|
|
49
76
|
if str(_pro_pkg) not in _self.__path__:
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Entry point: no arguments — TUI, with arguments — CLI."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
# Escape hatch for local development: let the TUI start even when the
|
|
7
|
+
# installed PRO package is stale/version-mismatched (see
|
|
8
|
+
# pentool.core.license.is_pro_package_compatible). Intentionally verbose and
|
|
9
|
+
# scary-looking rather than a short flag like "-f" — this bypasses a check
|
|
10
|
+
# that exists specifically because loading a mismatched PRO build can
|
|
11
|
+
# segfault the process, so an accidental/muscle-memory use should be hard.
|
|
12
|
+
# NOT meant for end users — only for developers iterating on FREE-only code
|
|
13
|
+
# who don't want to rebuild/reactivate PRO for every unrelated test run.
|
|
14
|
+
#
|
|
15
|
+
# Note this does NOT force-load the mismatched PRO package itself — that
|
|
16
|
+
# refusal (pentool.__init__._bootstrap_pro) runs unconditionally at import
|
|
17
|
+
# time, before this flag is even parsed, and always prints its own warning
|
|
18
|
+
# when it skips a mismatched PRO install. This flag only bypasses the
|
|
19
|
+
# separate hard stop below that would otherwise refuse to start the TUI at
|
|
20
|
+
# all in that situation — with it, the TUI still starts, just with PRO
|
|
21
|
+
# features unavailable (same as if no PRO package were installed).
|
|
22
|
+
_UNSAFE_SKIP_PRO_CHECK_FLAG = "--unsafe-skip-pro-compat-check"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main() -> None:
|
|
26
|
+
unsafe_skip_pro_check = _UNSAFE_SKIP_PRO_CHECK_FLAG in sys.argv
|
|
27
|
+
if unsafe_skip_pro_check:
|
|
28
|
+
sys.argv.remove(_UNSAFE_SKIP_PRO_CHECK_FLAG)
|
|
29
|
+
|
|
30
|
+
if len(sys.argv) > 1:
|
|
31
|
+
from pentool.cli.main import cli
|
|
32
|
+
cli()
|
|
33
|
+
else:
|
|
34
|
+
# Refuse to start the TUI at all if a PRO package is installed but
|
|
35
|
+
# was built for a different (or unknown) FREE version — e.g. FREE
|
|
36
|
+
# was upgraded via `pip install --upgrade pentool` / `pentool
|
|
37
|
+
# update`, but the PRO package's own re-sync afterwards failed or
|
|
38
|
+
# never ran (offline, or an unrelated check — like the GitHub
|
|
39
|
+
# release lookup used for the FREE version check — errored out
|
|
40
|
+
# first and stopped `pentool update` before it reached the PRO
|
|
41
|
+
# sync step). The PRO package bundles a compiled Cython extension;
|
|
42
|
+
# importing a mismatched build can segfault the process with no
|
|
43
|
+
# log output at all, which is exactly the silent, unexplained
|
|
44
|
+
# crash this check exists to prevent. Bail out here, before any
|
|
45
|
+
# Textual app or PRO import is attempted, with a clear message and
|
|
46
|
+
# a fix instead of a mystery crash.
|
|
47
|
+
try:
|
|
48
|
+
from pentool.core.license import is_pro_package_compatible
|
|
49
|
+
compatible, warning = is_pro_package_compatible()
|
|
50
|
+
except Exception:
|
|
51
|
+
compatible, warning = True, "" # never block startup over this check itself
|
|
52
|
+
|
|
53
|
+
if not compatible:
|
|
54
|
+
if unsafe_skip_pro_check:
|
|
55
|
+
print(
|
|
56
|
+
"[pentool] UNSAFE: --unsafe-skip-pro-compat-check set — "
|
|
57
|
+
"starting anyway despite the PRO version mismatch above. "
|
|
58
|
+
"PRO features stay disabled (as already reported), but "
|
|
59
|
+
"be aware this bypass is unpredictable if anything in "
|
|
60
|
+
"this session still ends up touching the mismatched "
|
|
61
|
+
"PRO package. Do not use this outside local development.",
|
|
62
|
+
file=sys.stderr,
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
print(f"[pentool] {warning}", file=sys.stderr)
|
|
66
|
+
raise SystemExit(1)
|
|
67
|
+
|
|
68
|
+
# Anonymous install-counter ping (fire-and-forget, one increment per
|
|
69
|
+
# machine — dedup happens server-side). Runs in a background thread —
|
|
70
|
+
# send_first_run_ping() opens a network connection with an 8s
|
|
71
|
+
# timeout, which must never delay the TUI actually starting when
|
|
72
|
+
# the network is slow/unreachable.
|
|
73
|
+
try:
|
|
74
|
+
from pentool.core.crash_reporter import send_first_run_ping
|
|
75
|
+
threading.Thread(target=send_first_run_ping, daemon=True).start()
|
|
76
|
+
except Exception:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
from pentool.tui.app import PentoolApp
|
|
81
|
+
PentoolApp().run()
|
|
82
|
+
except (KeyboardInterrupt, SystemExit):
|
|
83
|
+
raise
|
|
84
|
+
except Exception as exc:
|
|
85
|
+
# Send anonymous crash report (if not disabled in settings)
|
|
86
|
+
try:
|
|
87
|
+
from pentool.core.crash_reporter import send_crash
|
|
88
|
+
send_crash(exc)
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
raise
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if __name__ == "__main__":
|
|
95
|
+
main()
|
|
@@ -110,16 +110,30 @@ def update_cmd(check_only: bool) -> None:
|
|
|
110
110
|
|
|
111
111
|
if info.error:
|
|
112
112
|
click.echo(f"Could not check for updates: {info.error}", err=True)
|
|
113
|
+
# The FREE-version check above failing (network down, GitHub API
|
|
114
|
+
# rate-limited/404, ...) is unrelated to the PRO package, but it
|
|
115
|
+
# used to make this command bail out here BEFORE ever reaching
|
|
116
|
+
# _sync_pro_package_after_upgrade() below — silently leaving an
|
|
117
|
+
# already-installed PRO package on a stale build with no warning.
|
|
118
|
+
# Since PRO ships a compiled Cython extension, running a stale
|
|
119
|
+
# build against a since-upgraded FREE version can segfault the
|
|
120
|
+
# process with no log output — exactly the "just crashed, no idea
|
|
121
|
+
# why" report this is meant to prevent. So: still check/warn about
|
|
122
|
+
# PRO staleness before exiting, even though we can't do the FREE
|
|
123
|
+
# upgrade itself right now.
|
|
124
|
+
_warn_if_pro_incompatible()
|
|
113
125
|
raise SystemExit(1)
|
|
114
126
|
|
|
115
127
|
if not info.has_update:
|
|
116
128
|
click.echo(f"Already up to date (version {info.latest_version}).")
|
|
129
|
+
_warn_if_pro_incompatible()
|
|
117
130
|
return
|
|
118
131
|
|
|
119
132
|
click.echo(f"New version available: {info.latest_version}")
|
|
120
133
|
click.echo(f"Release notes: {info.url}")
|
|
121
134
|
|
|
122
135
|
if check_only:
|
|
136
|
+
_warn_if_pro_incompatible()
|
|
123
137
|
return
|
|
124
138
|
|
|
125
139
|
if click.confirm(f"Install {info.latest_version} now?", default=True):
|
|
@@ -134,6 +148,25 @@ def update_cmd(check_only: bool) -> None:
|
|
|
134
148
|
err=True,
|
|
135
149
|
)
|
|
136
150
|
raise SystemExit(1)
|
|
151
|
+
else:
|
|
152
|
+
_warn_if_pro_incompatible()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _warn_if_pro_incompatible() -> None:
|
|
156
|
+
"""Print a warning to the terminal if the installed PRO package (if any)
|
|
157
|
+
was built for a different FREE version than the one currently running.
|
|
158
|
+
|
|
159
|
+
Called from every exit path of `pentool update` that does NOT go through
|
|
160
|
+
_sync_pro_package_after_upgrade() below — so a user is never left with a
|
|
161
|
+
stale/incompatible PRO package and zero indication of it, regardless of
|
|
162
|
+
which branch (FREE check failed, already up to date, --check, declined
|
|
163
|
+
to install) the command took.
|
|
164
|
+
"""
|
|
165
|
+
from pentool.core.license import is_pro_package_compatible
|
|
166
|
+
|
|
167
|
+
compatible, warning = is_pro_package_compatible()
|
|
168
|
+
if not compatible:
|
|
169
|
+
click.echo(f"Warning: {warning}", err=True)
|
|
137
170
|
|
|
138
171
|
|
|
139
172
|
def _sync_pro_package_after_upgrade() -> None:
|
|
@@ -144,7 +177,10 @@ def _sync_pro_package_after_upgrade() -> None:
|
|
|
144
177
|
(re)downloaded by check_and_update_pro_package(), which normally runs in
|
|
145
178
|
the background on TUI startup. Doing it here too means a CLI-driven
|
|
146
179
|
upgrade doesn't leave a stale PRO build around until the next TUI launch.
|
|
147
|
-
Best-effort
|
|
180
|
+
Best-effort for the FREE upgrade's sake — a failure here never raises
|
|
181
|
+
SystemExit — but always surfaces a warning to the terminal if the PRO
|
|
182
|
+
package ends up (or remains) incompatible with the just-upgraded FREE
|
|
183
|
+
version, since that combination risks a crash on next launch.
|
|
148
184
|
"""
|
|
149
185
|
import asyncio
|
|
150
186
|
|
|
@@ -155,11 +191,14 @@ def _sync_pro_package_after_upgrade() -> None:
|
|
|
155
191
|
|
|
156
192
|
try:
|
|
157
193
|
click.echo("Checking for a newer PRO build...")
|
|
158
|
-
|
|
159
|
-
if updated:
|
|
194
|
+
result = asyncio.run(check_and_update_pro_package())
|
|
195
|
+
if result.updated:
|
|
160
196
|
click.echo("PRO package updated to the latest build.")
|
|
197
|
+
elif result.warning:
|
|
198
|
+
click.echo(f"Warning: {result.warning}", err=True)
|
|
161
199
|
except Exception as exc:
|
|
162
200
|
click.echo(f"Could not check for a PRO update: {exc}", err=True)
|
|
201
|
+
_warn_if_pro_incompatible()
|
|
163
202
|
|
|
164
203
|
|
|
165
204
|
@cli.group()
|
|
@@ -503,13 +503,131 @@ def _current_platform() -> str:
|
|
|
503
503
|
return "linux"
|
|
504
504
|
|
|
505
505
|
|
|
506
|
-
# File recording which PRO build is currently unpacked into PRO_PACKAGE_DIR
|
|
507
|
-
#
|
|
508
|
-
#
|
|
509
|
-
#
|
|
506
|
+
# File recording which PRO build is currently unpacked into PRO_PACKAGE_DIR,
|
|
507
|
+
# together with the FREE (pip) pentool version it was downloaded for — lets
|
|
508
|
+
# check_and_update_pro_package() tell "same build as last time" apart from "a
|
|
509
|
+
# new build was published" without re-downloading the archive to compare it,
|
|
510
|
+
# and lets is_pro_package_compatible() detect a FREE/PRO version mismatch.
|
|
511
|
+
# See _fetch_pro_build_id() / /api/pro/version on the Worker.
|
|
512
|
+
_PRO_META_FILE = PRO_PACKAGE_DIR / ".build_meta.json"
|
|
513
|
+
|
|
514
|
+
# Legacy pre-2026-08 marker: plain text build_id, no version recorded. Still
|
|
515
|
+
# read (as "unknown version" -> treated as incompatible/stale, see
|
|
516
|
+
# is_pro_package_compatible()) so an old install isn't mistaken for corrupt.
|
|
510
517
|
_PRO_BUILD_MARKER = PRO_PACKAGE_DIR / ".build_id"
|
|
511
518
|
|
|
512
519
|
|
|
520
|
+
@dataclass
|
|
521
|
+
class ProSyncResult:
|
|
522
|
+
"""Outcome of a check_and_update_pro_package() call.
|
|
523
|
+
|
|
524
|
+
`warning` is meant to be shown to the user verbatim (TUI notify / CLI
|
|
525
|
+
stderr) whenever the locally installed PRO package is stale or was built
|
|
526
|
+
for a different FREE version than the one currently running — silently
|
|
527
|
+
running on a mismatched PRO package is what risks a hard crash (it ships
|
|
528
|
+
a compiled Cython extension; an ABI/version mismatch there can segfault
|
|
529
|
+
instead of raising a catchable Python exception).
|
|
530
|
+
"""
|
|
531
|
+
updated: bool = False
|
|
532
|
+
warning: str = ""
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def _read_pro_meta() -> dict:
|
|
536
|
+
"""Read the PRO package's build metadata (build_id + the FREE pentool
|
|
537
|
+
version it was downloaded for). Falls back to the legacy plain-text
|
|
538
|
+
.build_id file with an empty free_version if the JSON meta file isn't
|
|
539
|
+
there yet — is_pro_package_compatible() then correctly treats that as
|
|
540
|
+
"unknown version, don't trust it" rather than crashing on missing data.
|
|
541
|
+
"""
|
|
542
|
+
try:
|
|
543
|
+
if _PRO_META_FILE.exists():
|
|
544
|
+
data = json.loads(_PRO_META_FILE.read_text(encoding="utf-8"))
|
|
545
|
+
if isinstance(data, dict):
|
|
546
|
+
return data
|
|
547
|
+
except Exception:
|
|
548
|
+
pass
|
|
549
|
+
try:
|
|
550
|
+
if _PRO_BUILD_MARKER.exists():
|
|
551
|
+
return {"build_id": _PRO_BUILD_MARKER.read_text(encoding="utf-8").strip(), "free_version": ""}
|
|
552
|
+
except Exception:
|
|
553
|
+
pass
|
|
554
|
+
return {}
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def _write_pro_meta(build_id: str) -> None:
|
|
558
|
+
"""Record which build_id is now unpacked, and which FREE version it was
|
|
559
|
+
fetched for — called right after a successful extract in
|
|
560
|
+
download_pro_package()."""
|
|
561
|
+
try:
|
|
562
|
+
from pentool import __version__ as free_version
|
|
563
|
+
except Exception:
|
|
564
|
+
free_version = ""
|
|
565
|
+
try:
|
|
566
|
+
_PRO_META_FILE.write_text(
|
|
567
|
+
json.dumps({"build_id": build_id, "free_version": free_version}),
|
|
568
|
+
encoding="utf-8",
|
|
569
|
+
)
|
|
570
|
+
_PRO_BUILD_MARKER.unlink(missing_ok=True) # migrate off the legacy marker
|
|
571
|
+
except Exception:
|
|
572
|
+
pass
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def is_pro_package_compatible() -> tuple[bool, str]:
|
|
576
|
+
"""Check whether the PRO package in PRO_PACKAGE_DIR matches the FREE
|
|
577
|
+
(pip) version of pentool currently running.
|
|
578
|
+
|
|
579
|
+
This is the guard against a stale/incompatible PRO package: the FREE
|
|
580
|
+
package updates via `pip install --upgrade pentool`, but the PRO package
|
|
581
|
+
(a separately downloaded, platform-specific compiled Cython extension)
|
|
582
|
+
only updates when check_and_update_pro_package() successfully reaches
|
|
583
|
+
the server — e.g. `pentool update`'s own version-check step failing
|
|
584
|
+
(network down, GitHub 404, ...) does NOT stop the FREE upgrade, but DOES
|
|
585
|
+
leave PRO on its old build. Loading that mismatched PRO build can
|
|
586
|
+
segfault the whole process instead of raising a catchable exception,
|
|
587
|
+
so every call site that would otherwise import from the PRO package
|
|
588
|
+
(plugin_manager, __init__._bootstrap_pro) checks this FIRST and refuses
|
|
589
|
+
to load it rather than relying on try/except around the import.
|
|
590
|
+
|
|
591
|
+
Returns:
|
|
592
|
+
(True, "") — nothing installed, or the installed PRO package matches
|
|
593
|
+
the running FREE version.
|
|
594
|
+
(False, message) — a PRO package is installed but was built for a
|
|
595
|
+
different (or unknown) FREE version. `message` is a
|
|
596
|
+
user-facing explanation of what to do.
|
|
597
|
+
"""
|
|
598
|
+
if not PRO_PACKAGE_DIR.exists():
|
|
599
|
+
return True, ""
|
|
600
|
+
|
|
601
|
+
try:
|
|
602
|
+
from pentool import __version__ as current_version
|
|
603
|
+
except Exception:
|
|
604
|
+
current_version = ""
|
|
605
|
+
|
|
606
|
+
meta = _read_pro_meta()
|
|
607
|
+
pro_free_version = meta.get("free_version", "")
|
|
608
|
+
|
|
609
|
+
if not pro_free_version:
|
|
610
|
+
return False, (
|
|
611
|
+
"A PRO package is installed, but its build metadata doesn't record "
|
|
612
|
+
"which Pentool version it was built for (a previous PRO update "
|
|
613
|
+
"likely didn't finish). PRO features are disabled to avoid a "
|
|
614
|
+
"crash from a version mismatch — run "
|
|
615
|
+
"'pentool license activate <key>' to re-download the PRO package."
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
if pro_free_version != current_version:
|
|
619
|
+
return False, (
|
|
620
|
+
f"Version mismatch: Pentool {current_version} is installed, but "
|
|
621
|
+
f"the PRO package was built for version {pro_free_version}. "
|
|
622
|
+
f"Running it as-is could crash the app. PRO features are "
|
|
623
|
+
f"disabled until this is resynced — run "
|
|
624
|
+
f"'pentool license activate <key>' or wait for the background "
|
|
625
|
+
f"PRO update to complete."
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
return True, ""
|
|
629
|
+
|
|
630
|
+
|
|
513
631
|
async def download_pro_package(key: str, machine_id: str) -> bool:
|
|
514
632
|
"""Download, verify, and install the obfuscated PRO package.
|
|
515
633
|
|
|
@@ -562,13 +680,15 @@ async def download_pro_package(key: str, machine_id: str) -> bool:
|
|
|
562
680
|
finally:
|
|
563
681
|
tmp_archive.unlink(missing_ok=True)
|
|
564
682
|
|
|
565
|
-
# Record which build this is
|
|
566
|
-
#
|
|
567
|
-
#
|
|
683
|
+
# Record which build this is (and which FREE version it was fetched
|
|
684
|
+
# for — see is_pro_package_compatible()), so a later
|
|
685
|
+
# check_and_update_pro_package() can tell "nothing changed" from "a
|
|
686
|
+
# new build was published" without re-downloading the archive just
|
|
687
|
+
# to compare it.
|
|
568
688
|
try:
|
|
569
689
|
build_id = await _fetch_pro_build_id(plat)
|
|
570
690
|
if build_id:
|
|
571
|
-
|
|
691
|
+
_write_pro_meta(build_id)
|
|
572
692
|
except Exception:
|
|
573
693
|
pass # non-fatal — worst case, next check just re-downloads once more
|
|
574
694
|
|
|
@@ -604,8 +724,9 @@ async def _fetch_pro_build_id(plat: str) -> str | None:
|
|
|
604
724
|
return None
|
|
605
725
|
|
|
606
726
|
|
|
607
|
-
async def check_and_update_pro_package() ->
|
|
608
|
-
"""Re-download the PRO package if a newer build has been published
|
|
727
|
+
async def check_and_update_pro_package() -> "ProSyncResult":
|
|
728
|
+
"""Re-download the PRO package if a newer build has been published, or if
|
|
729
|
+
the currently-installed one no longer matches the running FREE version.
|
|
609
730
|
|
|
610
731
|
Called on TUI startup (background worker, same pattern as
|
|
611
732
|
_check_for_updates for the pip package) and after `pentool update` — this
|
|
@@ -614,33 +735,51 @@ async def check_and_update_pro_package() -> bool:
|
|
|
614
735
|
~/.pentool/pro/ forever, because download_pro_package() otherwise only
|
|
615
736
|
ever runs once, at initial activation/trial-start.
|
|
616
737
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
738
|
+
Crucially, this is also the ONLY place that re-syncs PRO after a FREE
|
|
739
|
+
upgrade (`pip install --upgrade pentool` / `pentool update`) — if this
|
|
740
|
+
step can't reach the license server (network down, or the *unrelated*
|
|
741
|
+
FREE-side GitHub version check already failed and the caller never even
|
|
742
|
+
got here), the PRO package silently stays on its old build while FREE
|
|
743
|
+
moves on. Since the PRO package ships a compiled Cython extension, that
|
|
744
|
+
mismatch can segfault the process instead of raising a Python exception.
|
|
745
|
+
So on every "couldn't sync" path below, this now also checks
|
|
746
|
+
is_pro_package_compatible() and returns a non-empty `warning` if the
|
|
747
|
+
installed PRO build is stale/unknown — callers (TUI, CLI) must surface
|
|
748
|
+
that warning to the user rather than silently doing nothing.
|
|
749
|
+
|
|
750
|
+
Returns a ProSyncResult:
|
|
751
|
+
- updated=True — a new build was found and successfully installed.
|
|
752
|
+
- updated=False, warning="" — nothing to do (no PRO license, PRO
|
|
753
|
+
never installed, or already on the latest compatible build).
|
|
754
|
+
- updated=False, warning="..." — could not verify/refresh the PRO
|
|
755
|
+
package AND the installed one is stale or version-mismatched;
|
|
756
|
+
`warning` is a user-facing message to display.
|
|
624
757
|
"""
|
|
625
758
|
info = get_license()
|
|
626
759
|
if not info.valid or not info.features:
|
|
627
|
-
return False
|
|
760
|
+
return ProSyncResult(updated=False, warning="")
|
|
628
761
|
if not PRO_PACKAGE_DIR.exists():
|
|
629
|
-
return False # never activated
|
|
762
|
+
return ProSyncResult(updated=False, warning="") # never activated — nothing to refresh
|
|
630
763
|
|
|
631
764
|
plat = _current_platform()
|
|
632
765
|
remote_build_id = await _fetch_pro_build_id(plat)
|
|
633
766
|
if not remote_build_id:
|
|
634
|
-
|
|
767
|
+
# Couldn't reach the server to check — don't touch the package, but
|
|
768
|
+
# DO tell the user if what's on disk is already known-stale/mismatched.
|
|
769
|
+
_compatible, warning = is_pro_package_compatible()
|
|
770
|
+
return ProSyncResult(updated=False, warning=warning)
|
|
635
771
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if _PRO_BUILD_MARKER.exists():
|
|
639
|
-
local_build_id = _PRO_BUILD_MARKER.read_text(encoding="utf-8").strip()
|
|
640
|
-
except Exception:
|
|
641
|
-
pass
|
|
772
|
+
meta = _read_pro_meta()
|
|
773
|
+
local_build_id = meta.get("build_id", "")
|
|
642
774
|
|
|
643
775
|
if local_build_id == remote_build_id:
|
|
644
|
-
|
|
776
|
+
_compatible, warning = is_pro_package_compatible()
|
|
777
|
+
return ProSyncResult(updated=False, warning=warning)
|
|
778
|
+
|
|
779
|
+
updated = await download_pro_package(info.license_key, info.machine_id)
|
|
780
|
+
if updated:
|
|
781
|
+
return ProSyncResult(updated=True, warning="")
|
|
645
782
|
|
|
646
|
-
|
|
783
|
+
# Download of the newer build failed — warn if what's left on disk is stale.
|
|
784
|
+
_compatible, warning = is_pro_package_compatible()
|
|
785
|
+
return ProSyncResult(updated=False, warning=warning)
|
|
@@ -161,6 +161,12 @@ class PluginManager:
|
|
|
161
161
|
self._passive_checks: list[type[BaseCheck]] = []
|
|
162
162
|
self._loaded: list[str] = []
|
|
163
163
|
self._meta: list[PluginMeta] = []
|
|
164
|
+
# Set by load_user_plugins() when a PRO package is present on disk
|
|
165
|
+
# but was built for a different (or unknown) FREE version — see
|
|
166
|
+
# pentool.core.license.is_pro_package_compatible(). Empty string
|
|
167
|
+
# means no incompatibility was detected. Callers (TUI startup, CLI)
|
|
168
|
+
# should surface this to the user rather than silently ignoring it.
|
|
169
|
+
self.pro_incompatible_warning: str = ""
|
|
164
170
|
|
|
165
171
|
# ── Loading ───────────────────────────────────────────────────────────────
|
|
166
172
|
|
|
@@ -197,7 +203,21 @@ class PluginManager:
|
|
|
197
203
|
# Obfuscated PRO package downloaded via 'pentool license trial'/'activate'
|
|
198
204
|
# (see pentool.core.license.download_pro_package). Its plugins directory
|
|
199
205
|
# mirrors the builtin layout: pro/pentool/plugins/builtin/*.py
|
|
200
|
-
|
|
206
|
+
#
|
|
207
|
+
# Refuse to import it if it was built for a different FREE version
|
|
208
|
+
# than the one currently running (e.g. FREE was upgraded via
|
|
209
|
+
# `pip install --upgrade pentool` but the PRO re-sync afterwards
|
|
210
|
+
# failed/never ran) — the PRO package bundles a compiled Cython
|
|
211
|
+
# extension, and importing a mismatched build there can segfault the
|
|
212
|
+
# whole process instead of raising a catchable Python exception. See
|
|
213
|
+
# pentool.core.license.is_pro_package_compatible().
|
|
214
|
+
from pentool.core.license import PRO_PACKAGE_DIR, is_pro_package_compatible
|
|
215
|
+
compatible, warning = is_pro_package_compatible()
|
|
216
|
+
if not compatible:
|
|
217
|
+
logger.warning("PRO package skipped — %s", warning)
|
|
218
|
+
self.pro_incompatible_warning = warning
|
|
219
|
+
return
|
|
220
|
+
|
|
201
221
|
pro_plugins_dir = PRO_PACKAGE_DIR / "pentool" / "plugins" / "builtin"
|
|
202
222
|
if pro_plugins_dir.exists():
|
|
203
223
|
self.load_plugins([str(pro_plugins_dir)], warn_untrusted=False)
|
|
@@ -360,11 +380,24 @@ def load_pro_module(module_stem: str):
|
|
|
360
380
|
Raises FileNotFoundError if the module isn't found in any known PRO
|
|
361
381
|
location (dev submodule or installed package) — callers should show
|
|
362
382
|
this to the user instead of silently failing.
|
|
383
|
+
|
|
384
|
+
Raises RuntimeError if the installed PRO package (PRO_PACKAGE_DIR) was
|
|
385
|
+
built for a different FREE version than the one currently running — see
|
|
386
|
+
pentool.core.license.is_pro_package_compatible(). Refusing to import it
|
|
387
|
+
is deliberate: it's a compiled Cython extension, and importing a
|
|
388
|
+
mismatched build can segfault the process instead of raising a
|
|
389
|
+
catchable Python exception. Does NOT apply to the dev `pro/` submodule
|
|
390
|
+
checkout, which is always the same source tree as the running FREE code.
|
|
363
391
|
"""
|
|
364
392
|
module_name = f"_pentool_plugin_{module_stem}"
|
|
365
393
|
if module_name in sys.modules:
|
|
366
394
|
return sys.modules[module_name]
|
|
367
395
|
|
|
396
|
+
from pentool.core.license import PRO_PACKAGE_DIR, is_pro_package_compatible
|
|
397
|
+
compatible, warning = is_pro_package_compatible()
|
|
398
|
+
if not compatible:
|
|
399
|
+
raise RuntimeError(warning)
|
|
400
|
+
|
|
368
401
|
for plugin_dir in _candidate_pro_builtin_dirs():
|
|
369
402
|
candidate = plugin_dir / f"{module_stem}.py"
|
|
370
403
|
if not candidate.exists():
|