tokenade 1.0.0__py3-none-any.whl

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 (363) hide show
  1. tokenade/__init__.py +84 -0
  2. tokenade/__main__.py +5 -0
  3. tokenade/cli/__init__.py +1937 -0
  4. tokenade/cli/__main__.py +5 -0
  5. tokenade/cli/advanced.py +490 -0
  6. tokenade/cli/completions.py +73 -0
  7. tokenade/cli/config.py +47 -0
  8. tokenade/cli/handlers/__init__.py +51 -0
  9. tokenade/cli/handlers/browser_ops.py +1373 -0
  10. tokenade/cli/handlers/ci.py +361 -0
  11. tokenade/cli/handlers/infrastructure.py +452 -0
  12. tokenade/cli/handlers/misc.py +222 -0
  13. tokenade/cli/handlers/session_ops.py +1263 -0
  14. tokenade/cli/management.py +82 -0
  15. tokenade/cli/output.py +176 -0
  16. tokenade/cli/proxy.py +279 -0
  17. tokenade/cli/security.py +167 -0
  18. tokenade/cli/session.py +885 -0
  19. tokenade/core/__init__.py +0 -0
  20. tokenade/core/antidetection/__init__.py +4 -0
  21. tokenade/core/antidetection/behavioral.py +140 -0
  22. tokenade/core/antidetection/cdp_cleaner.py +60 -0
  23. tokenade/core/api/__init__.py +3 -0
  24. tokenade/core/api/server.py +445 -0
  25. tokenade/core/batch/__init__.py +23 -0
  26. tokenade/core/batch/operations.py +398 -0
  27. tokenade/core/browser/__init__.py +84 -0
  28. tokenade/core/browser/battle.py +865 -0
  29. tokenade/core/browser/captcha.py +244 -0
  30. tokenade/core/browser/cdp_connection.py +1095 -0
  31. tokenade/core/browser/cloak.py +24 -0
  32. tokenade/core/browser/cloudflare.py +294 -0
  33. tokenade/core/browser/dashboard.py +195 -0
  34. tokenade/core/browser/dependencies.py +271 -0
  35. tokenade/core/browser/fingerprint.py +262 -0
  36. tokenade/core/browser/manager.py +317 -0
  37. tokenade/core/browser/patcher.py +322 -0
  38. tokenade/core/browser/profile_cloner.py +344 -0
  39. tokenade/core/browser/profiles.py +221 -0
  40. tokenade/core/browser/session_state.py +254 -0
  41. tokenade/core/browser/stealth/__init__.py +68 -0
  42. tokenade/core/browser/stealth/backend.py +101 -0
  43. tokenade/core/browser/stealth/cloak.py +258 -0
  44. tokenade/core/browser/stealth/launcher.py +593 -0
  45. tokenade/core/browser/stealth/manager.py +671 -0
  46. tokenade/core/browser/stealth.py +24 -0
  47. tokenade/core/browser/stealth_test.py +324 -0
  48. tokenade/core/browser/storage_extractor.py +231 -0
  49. tokenade/core/browser/synchronizer.py +200 -0
  50. tokenade/core/browser/tls_fingerprint.py +179 -0
  51. tokenade/core/browser/undetectable.py +16 -0
  52. tokenade/core/browser/xvfb.py +222 -0
  53. tokenade/core/cicd/__init__.py +29 -0
  54. tokenade/core/cicd/runner.py +548 -0
  55. tokenade/core/cicd/workflow_generator.py +432 -0
  56. tokenade/core/config.py +103 -0
  57. tokenade/core/crypto/__init__.py +19 -0
  58. tokenade/core/crypto/at_rest.py +178 -0
  59. tokenade/core/crypto/cookie_crypto.py +805 -0
  60. tokenade/core/crypto/encryptor.py +301 -0
  61. tokenade/core/daemon/__init__.py +4 -0
  62. tokenade/core/daemon/session_daemon.py +675 -0
  63. tokenade/core/errors.py +77 -0
  64. tokenade/core/fingerprint/__init__.py +0 -0
  65. tokenade/core/fingerprint/collectors/__init__.py +30 -0
  66. tokenade/core/fingerprint/collectors/audio.py +124 -0
  67. tokenade/core/fingerprint/collectors/base.py +66 -0
  68. tokenade/core/fingerprint/collectors/battery.py +59 -0
  69. tokenade/core/fingerprint/collectors/canvas.py +81 -0
  70. tokenade/core/fingerprint/collectors/fonts.py +52 -0
  71. tokenade/core/fingerprint/collectors/navigator.py +99 -0
  72. tokenade/core/fingerprint/collectors/plugins.py +92 -0
  73. tokenade/core/fingerprint/collectors/screen.py +81 -0
  74. tokenade/core/fingerprint/collectors/webgl.py +104 -0
  75. tokenade/core/fingerprint/collectors/webrtc.py +91 -0
  76. tokenade/core/fingerprint/injector.py +94 -0
  77. tokenade/core/fingerprint/manager.py +278 -0
  78. tokenade/core/fingerprint/stealth.py +357 -0
  79. tokenade/core/forensics/__init__.py +15 -0
  80. tokenade/core/forensics/autopsy.py +464 -0
  81. tokenade/core/importer/__init__.py +23 -0
  82. tokenade/core/importer/adb_extractor.py +259 -0
  83. tokenade/core/importer/advanced_validator.py +665 -0
  84. tokenade/core/importer/browser_discovery.py +313 -0
  85. tokenade/core/importer/chromium_forks.py +174 -0
  86. tokenade/core/importer/competitor_import.py +324 -0
  87. tokenade/core/importer/cookie_extractor.py +612 -0
  88. tokenade/core/importer/db_utils.py +66 -0
  89. tokenade/core/importer/format_exporter.py +210 -0
  90. tokenade/core/importer/format_importer.py +277 -0
  91. tokenade/core/importer/local_storage_extractor.py +271 -0
  92. tokenade/core/importer/mobile_extractor.py +263 -0
  93. tokenade/core/importer/mobile_import.py +556 -0
  94. tokenade/core/importer/plugin_export.py +321 -0
  95. tokenade/core/importer/safari_extractor.py +376 -0
  96. tokenade/core/importer/session_comparator.py +126 -0
  97. tokenade/core/importer/session_loader.py +494 -0
  98. tokenade/core/importer/session_manager.py +295 -0
  99. tokenade/core/importer/session_packager.py +492 -0
  100. tokenade/core/importer/session_refresher.py +467 -0
  101. tokenade/core/importer/session_rotation.py +147 -0
  102. tokenade/core/importer/session_rotator.py +279 -0
  103. tokenade/core/importer/session_sharer.py +713 -0
  104. tokenade/core/importer/session_sync.py +298 -0
  105. tokenade/core/importer/session_vault.py +282 -0
  106. tokenade/core/importer/site_configs.py +314 -0
  107. tokenade/core/importer/tor_extractor.py +138 -0
  108. tokenade/core/importer/validator.py +497 -0
  109. tokenade/core/injector/__init__.py +15 -0
  110. tokenade/core/injector/profile_manager.py +386 -0
  111. tokenade/core/integration/__init__.py +32 -0
  112. tokenade/core/integration/container_orchestrator.py +512 -0
  113. tokenade/core/integration/docker_manager.py +349 -0
  114. tokenade/core/integration/fleet.py +464 -0
  115. tokenade/core/integration/kubernetes.py +393 -0
  116. tokenade/core/integration/plugin_browser.py +251 -0
  117. tokenade/core/integration/plugin_loader.py +395 -0
  118. tokenade/core/integration/plugin_registry.py +620 -0
  119. tokenade/core/integration/plugin_search.py +186 -0
  120. tokenade/core/integration/plugin_testing.py +422 -0
  121. tokenade/core/integration/plugin_verifier.py +193 -0
  122. tokenade/core/integration/rating_sync.py +286 -0
  123. tokenade/core/integration/webhooks.py +219 -0
  124. tokenade/core/logging/__init__.py +6 -0
  125. tokenade/core/logging/structured.py +182 -0
  126. tokenade/core/monitoring/__init__.py +0 -0
  127. tokenade/core/monitoring/analytics.py +194 -0
  128. tokenade/core/monitoring/session_monitor.py +479 -0
  129. tokenade/core/proxy/__init__.py +42 -0
  130. tokenade/core/proxy/cdp_api.py +97 -0
  131. tokenade/core/proxy/cdp_gui.py +349 -0
  132. tokenade/core/proxy/cdp_injection.py +376 -0
  133. tokenade/core/proxy/cdp_proxy.py +808 -0
  134. tokenade/core/proxy/cdp_routing.py +419 -0
  135. tokenade/core/proxy/cdp_stealth.py +229 -0
  136. tokenade/core/proxy/extension_bridge.py +132 -0
  137. tokenade/core/proxy/forward_proxy.py +317 -0
  138. tokenade/core/proxy/manager.py +275 -0
  139. tokenade/core/proxy/multi_site_proxy.py +255 -0
  140. tokenade/core/proxy/residential.py +305 -0
  141. tokenade/core/proxy/rotation.py +365 -0
  142. tokenade/core/proxy/server.py +422 -0
  143. tokenade/core/proxy/server_gui.py +214 -0
  144. tokenade/core/proxy/server_routing.py +128 -0
  145. tokenade/core/proxy/server_utils.py +253 -0
  146. tokenade/core/refresh/__init__.py +67 -0
  147. tokenade/core/refresh/batch_refresh.py +319 -0
  148. tokenade/core/refresh/encrypted_refresh.py +342 -0
  149. tokenade/core/refresh/health_checker.py +355 -0
  150. tokenade/core/refresh/health_reporter.py +379 -0
  151. tokenade/core/refresh/health_scorer.py +292 -0
  152. tokenade/core/refresh/oauth_refresh.py +611 -0
  153. tokenade/core/refresh/session_validator.py +367 -0
  154. tokenade/core/runtime/__init__.py +30 -0
  155. tokenade/core/runtime/engine.py +783 -0
  156. tokenade/core/runtime/tls_matcher.py +282 -0
  157. tokenade/core/security/__init__.py +27 -0
  158. tokenade/core/security/audit.py +577 -0
  159. tokenade/core/security/credentials.py +417 -0
  160. tokenade/core/storage/__init__.py +1 -0
  161. tokenade/core/storage/session_versions.py +261 -0
  162. tokenade/core/utils/__init__.py +0 -0
  163. tokenade/core/utils/performance.py +446 -0
  164. tokenade/handlers/__init__.py +42 -0
  165. tokenade/handlers/base.py +288 -0
  166. tokenade/handlers/generic_oauth.py +593 -0
  167. tokenade/handlers/github.py +347 -0
  168. tokenade/handlers/google.py +301 -0
  169. tokenade/handlers/resolve.py +49 -0
  170. tokenade/plugin/__init__.py +62 -0
  171. tokenade/plugin/api.py +139 -0
  172. tokenade/plugin/base.py +681 -0
  173. tokenade/plugin/oauth2/__init__.py +4 -0
  174. tokenade/plugin/oauth2/plugin.py +282 -0
  175. tokenade/sdk/__init__.py +257 -0
  176. tokenade/tests/__init__.py +0 -0
  177. tokenade/tests/portability.py +308 -0
  178. tokenade/tests/test_accounts.py +286 -0
  179. tokenade/tests/test_advanced_validator.py +209 -0
  180. tokenade/tests/test_advanced_validator_edge_cases.py +1115 -0
  181. tokenade/tests/test_analytics.py +222 -0
  182. tokenade/tests/test_antidetection.py +145 -0
  183. tokenade/tests/test_api_monitor_endpoints.py +204 -0
  184. tokenade/tests/test_api_monitoring.py +26 -0
  185. tokenade/tests/test_api_sdk.py +262 -0
  186. tokenade/tests/test_api_server_comprehensive.py +400 -0
  187. tokenade/tests/test_api_server_coverage.py +232 -0
  188. tokenade/tests/test_at_rest_encryption.py +210 -0
  189. tokenade/tests/test_audit_coverage.py +560 -0
  190. tokenade/tests/test_autopsy.py +375 -0
  191. tokenade/tests/test_base_handler_coverage.py +365 -0
  192. tokenade/tests/test_batch_operations.py +311 -0
  193. tokenade/tests/test_batch_operations_coverage.py +415 -0
  194. tokenade/tests/test_batch_refresh.py +175 -0
  195. tokenade/tests/test_battle.py +274 -0
  196. tokenade/tests/test_benchmarks.py +209 -0
  197. tokenade/tests/test_binary_patcher.py +419 -0
  198. tokenade/tests/test_browser.py +367 -0
  199. tokenade/tests/test_browser_discovery.py +58 -0
  200. tokenade/tests/test_browser_discovery_coverage.py +516 -0
  201. tokenade/tests/test_browser_improvements.py +277 -0
  202. tokenade/tests/test_browser_manager_coverage.py +764 -0
  203. tokenade/tests/test_browser_support.py +728 -0
  204. tokenade/tests/test_cdp_gui_coverage.py +573 -0
  205. tokenade/tests/test_cdp_injection_coverage.py +1056 -0
  206. tokenade/tests/test_cdp_injection_edge_cases.py +396 -0
  207. tokenade/tests/test_cdp_proxy_coverage.py +534 -0
  208. tokenade/tests/test_cdp_routing_coverage.py +1162 -0
  209. tokenade/tests/test_cdp_stealth_urls.py +50 -0
  210. tokenade/tests/test_chromium_forks_coverage.py +411 -0
  211. tokenade/tests/test_ci_runner.py +500 -0
  212. tokenade/tests/test_cicd.py +122 -0
  213. tokenade/tests/test_cli.py +200 -0
  214. tokenade/tests/test_cli_advanced.py +943 -0
  215. tokenade/tests/test_cli_helpers.py +99 -0
  216. tokenade/tests/test_cli_init_coverage.py +651 -0
  217. tokenade/tests/test_cli_integration.py +205 -0
  218. tokenade/tests/test_cli_management.py +231 -0
  219. tokenade/tests/test_cli_management_coverage.py +419 -0
  220. tokenade/tests/test_cli_monitor.py +201 -0
  221. tokenade/tests/test_cli_output.py +219 -0
  222. tokenade/tests/test_cli_proxy.py +1004 -0
  223. tokenade/tests/test_cli_refactor.py +454 -0
  224. tokenade/tests/test_cli_security_coverage.py +306 -0
  225. tokenade/tests/test_cli_security_proxy.py +242 -0
  226. tokenade/tests/test_cli_session.py +1168 -0
  227. tokenade/tests/test_cloak.py +375 -0
  228. tokenade/tests/test_cloudflare_akamai.py +384 -0
  229. tokenade/tests/test_collectors.py +283 -0
  230. tokenade/tests/test_comprehensive.py +311 -0
  231. tokenade/tests/test_config_and_cdp_modules.py +610 -0
  232. tokenade/tests/test_container_orchestration.py +488 -0
  233. tokenade/tests/test_cookie_crypto.py +835 -0
  234. tokenade/tests/test_cookie_extractor.py +1088 -0
  235. tokenade/tests/test_credentials_keyring_errors.py +68 -0
  236. tokenade/tests/test_crypto.py +188 -0
  237. tokenade/tests/test_custom_errors.py +100 -0
  238. tokenade/tests/test_daemon.py +430 -0
  239. tokenade/tests/test_db_utils.py +93 -0
  240. tokenade/tests/test_db_utils_coverage.py +218 -0
  241. tokenade/tests/test_docker_manager.py +587 -0
  242. tokenade/tests/test_e2e_headless.py +207 -0
  243. tokenade/tests/test_encrypted_refresh.py +169 -0
  244. tokenade/tests/test_encryptor.py +323 -0
  245. tokenade/tests/test_encryptor_coverage.py +339 -0
  246. tokenade/tests/test_encryptor_edge.py +66 -0
  247. tokenade/tests/test_engine_coverage.py +748 -0
  248. tokenade/tests/test_enterprise.py +819 -0
  249. tokenade/tests/test_errors.py +49 -0
  250. tokenade/tests/test_extension_bridge_coverage.py +215 -0
  251. tokenade/tests/test_fingerprint.py +66 -0
  252. tokenade/tests/test_fingerprint_collectors.py +397 -0
  253. tokenade/tests/test_fingerprint_collectors_coverage.py +298 -0
  254. tokenade/tests/test_fingerprint_consistency.py +202 -0
  255. tokenade/tests/test_fingerprint_injector.py +308 -0
  256. tokenade/tests/test_fingerprint_manager.py +580 -0
  257. tokenade/tests/test_fleet.py +398 -0
  258. tokenade/tests/test_format_export.py +414 -0
  259. tokenade/tests/test_format_importer_and_helpers.py +656 -0
  260. tokenade/tests/test_forward_proxy.py +52 -0
  261. tokenade/tests/test_forward_proxy_coverage.py +725 -0
  262. tokenade/tests/test_generic_oauth_coverage.py +576 -0
  263. tokenade/tests/test_github_handler_coverage.py +342 -0
  264. tokenade/tests/test_google_handler_coverage.py +317 -0
  265. tokenade/tests/test_handler_resolve.py +32 -0
  266. tokenade/tests/test_handlers.py +521 -0
  267. tokenade/tests/test_health_checker_coverage.py +451 -0
  268. tokenade/tests/test_health_reporter.py +287 -0
  269. tokenade/tests/test_health_scorer.py +537 -0
  270. tokenade/tests/test_importer_integration.py +290 -0
  271. tokenade/tests/test_integration.py +662 -0
  272. tokenade/tests/test_kubernetes.py +484 -0
  273. tokenade/tests/test_local_storage.py +56 -0
  274. tokenade/tests/test_local_storage_extractor.py +174 -0
  275. tokenade/tests/test_local_storage_extractor_coverage.py +291 -0
  276. tokenade/tests/test_local_storage_extractor_plyvel.py +400 -0
  277. tokenade/tests/test_mac_crypto.py +74 -0
  278. tokenade/tests/test_mobile_extractor_coverage.py +397 -0
  279. tokenade/tests/test_mobile_import.py +240 -0
  280. tokenade/tests/test_monitoring.py +92 -0
  281. tokenade/tests/test_multi_site_proxy.py +51 -0
  282. tokenade/tests/test_multi_site_proxy_coverage.py +408 -0
  283. tokenade/tests/test_mutmut_killers.py +122 -0
  284. tokenade/tests/test_new_plugins.py +295 -0
  285. tokenade/tests/test_oauth_refresh.py +412 -0
  286. tokenade/tests/test_official_plugin_contracts.py +52 -0
  287. tokenade/tests/test_performance.py +294 -0
  288. tokenade/tests/test_performance_coverage.py +392 -0
  289. tokenade/tests/test_phase80.py +266 -0
  290. tokenade/tests/test_playwright_e2e.py +663 -0
  291. tokenade/tests/test_plugin_api.py +347 -0
  292. tokenade/tests/test_plugin_api_v11.py +357 -0
  293. tokenade/tests/test_plugin_loader.py +297 -0
  294. tokenade/tests/test_plugin_loader_coverage.py +254 -0
  295. tokenade/tests/test_plugin_marketplace.py +838 -0
  296. tokenade/tests/test_plugin_registry.py +447 -0
  297. tokenade/tests/test_plugin_system.py +339 -0
  298. tokenade/tests/test_plugin_types.py +358 -0
  299. tokenade/tests/test_profile_cloner.py +226 -0
  300. tokenade/tests/test_profile_management.py +280 -0
  301. tokenade/tests/test_profile_manager.py +338 -0
  302. tokenade/tests/test_profile_manager_coverage.py +400 -0
  303. tokenade/tests/test_profile_manager_errors.py +397 -0
  304. tokenade/tests/test_property_based.py +214 -0
  305. tokenade/tests/test_proxy.py +652 -0
  306. tokenade/tests/test_proxy_config.py +86 -0
  307. tokenade/tests/test_proxy_gui.py +80 -0
  308. tokenade/tests/test_proxy_manager.py +218 -0
  309. tokenade/tests/test_proxy_modules.py +197 -0
  310. tokenade/tests/test_proxy_rotation.py +584 -0
  311. tokenade/tests/test_rating_sync.py +156 -0
  312. tokenade/tests/test_refresh_browser.py +511 -0
  313. tokenade/tests/test_runtime.py +474 -0
  314. tokenade/tests/test_safari_extractor_coverage.py +538 -0
  315. tokenade/tests/test_sdk_coverage.py +429 -0
  316. tokenade/tests/test_security.py +349 -0
  317. tokenade/tests/test_server_coverage.py +696 -0
  318. tokenade/tests/test_server_routing_coverage.py +349 -0
  319. tokenade/tests/test_server_utils_coverage.py +485 -0
  320. tokenade/tests/test_session_comparator.py +110 -0
  321. tokenade/tests/test_session_loader.py +52 -0
  322. tokenade/tests/test_session_loader_coverage.py +621 -0
  323. tokenade/tests/test_session_manager.py +135 -0
  324. tokenade/tests/test_session_monitor_coverage.py +297 -0
  325. tokenade/tests/test_session_monitor_enhanced.py +618 -0
  326. tokenade/tests/test_session_packager.py +104 -0
  327. tokenade/tests/test_session_packager_coverage.py +466 -0
  328. tokenade/tests/test_session_refresher.py +543 -0
  329. tokenade/tests/test_session_rotation.py +408 -0
  330. tokenade/tests/test_session_rotator.py +377 -0
  331. tokenade/tests/test_session_sharer.py +591 -0
  332. tokenade/tests/test_session_sharer_qr_versions.py +272 -0
  333. tokenade/tests/test_session_sync.py +598 -0
  334. tokenade/tests/test_session_sync_coverage.py +302 -0
  335. tokenade/tests/test_session_validator.py +204 -0
  336. tokenade/tests/test_session_vault.py +418 -0
  337. tokenade/tests/test_session_versions.py +262 -0
  338. tokenade/tests/test_share_improvements.py +181 -0
  339. tokenade/tests/test_site_configs.py +92 -0
  340. tokenade/tests/test_site_filter.py +65 -0
  341. tokenade/tests/test_site_handlers.py +318 -0
  342. tokenade/tests/test_ssrf.py +54 -0
  343. tokenade/tests/test_stealth.py +147 -0
  344. tokenade/tests/test_stealth_enhanced.py +444 -0
  345. tokenade/tests/test_stealth_validation.py +217 -0
  346. tokenade/tests/test_structured_logging.py +243 -0
  347. tokenade/tests/test_sync_import.py +302 -0
  348. tokenade/tests/test_tls.py +39 -0
  349. tokenade/tests/test_tls_matcher.py +133 -0
  350. tokenade/tests/test_tls_matcher_coverage.py +310 -0
  351. tokenade/tests/test_tor_extractor_coverage.py +141 -0
  352. tokenade/tests/test_tui.py +259 -0
  353. tokenade/tests/test_undetectable.py +176 -0
  354. tokenade/tests/test_validator_coverage.py +582 -0
  355. tokenade/tests/test_webhooks.py +290 -0
  356. tokenade/tests/test_xvfb.py +172 -0
  357. tokenade/tui/__init__.py +7 -0
  358. tokenade/tui/app.py +1145 -0
  359. tokenade-1.0.0.dist-info/METADATA +831 -0
  360. tokenade-1.0.0.dist-info/RECORD +363 -0
  361. tokenade-1.0.0.dist-info/WHEEL +5 -0
  362. tokenade-1.0.0.dist-info/entry_points.txt +2 -0
  363. tokenade-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1937 @@
1
+ """Tokenade CLI - Main entry point and argument parser."""
2
+ import argparse
3
+ import asyncio
4
+ import logging
5
+ import sys
6
+ import time
7
+
8
+ from tokenade.cli.session import cmd_extract, cmd_export, cmd_load, cmd_transfer, cmd_inject_profile
9
+ from tokenade.cli.security import cmd_encrypt, cmd_decrypt, cmd_rekey
10
+ from tokenade.cli.proxy import cmd_proxy
11
+ from tokenade.cli.management import (
12
+ cmd_sessions, cmd_health, cmd_refresh, cmd_share, cmd_unshare,
13
+ cmd_sync, cmd_monitor, cmd_analytics,
14
+ cmd_refresh_oauth, cmd_oauth_config, cmd_batch_refresh, cmd_cicd, cmd_ci,
15
+ cmd_validate_session, cmd_encrypted_refresh, cmd_launch,
16
+ cmd_refresh_browser, cmd_accounts, cmd_patch_chrome,
17
+ cmd_daemon, cmd_versions, cmd_rollback, cmd_session_diff,
18
+ cmd_logs, cmd_health_report, cmd_mobile_import, cmd_clone_profile, cmd_import,
19
+ cmd_container, cmd_k8s, cmd_fleet, cmd_autopsy, cmd_cloak, cmd_tui,
20
+ )
21
+ from tokenade.cli.advanced import (
22
+ cmd_batch_export, cmd_batch_load, cmd_validate, cmd_validate_rules,
23
+ cmd_diff, cmd_fingerprint, cmd_test, cmd_setup,
24
+ )
25
+
26
+
27
+ def cmd_config(args):
28
+ """Manage configuration (~/.tokenade/config.json)."""
29
+ from tokenade.core.config import load_config, DEFAULTS
30
+
31
+ config = load_config()
32
+
33
+ if args.config_command == "path":
34
+ print(config.config_path)
35
+
36
+ elif args.config_command == "show":
37
+ print(f"\n📋 Tokenade Config ({config.config_path})\n")
38
+ for key in sorted(DEFAULTS.keys()):
39
+ value = config.get(key)
40
+ default = DEFAULTS[key]
41
+ marker = "" if value != default else " (default)"
42
+ print(f" {key}: {value}{marker}")
43
+
44
+ elif args.config_command == "get":
45
+ if not args.key:
46
+ print("❌ Usage: tokenade config get <key>")
47
+ return
48
+ value = config.get(args.key)
49
+ if value is None:
50
+ print(f"❌ Unknown config key: {args.key}")
51
+ else:
52
+ print(value)
53
+
54
+ elif args.config_command == "set":
55
+ if not args.key or not args.value:
56
+ print("❌ Usage: tokenade config set <key> <value>")
57
+ return
58
+ # Type coercion for booleans
59
+ value = args.value
60
+ if value.lower() in ("true", "false"):
61
+ value = value.lower() == "true"
62
+ elif value.isdigit():
63
+ value = int(value)
64
+ config.set(args.key, value)
65
+ config.save()
66
+ print(f"✅ Set {args.key} = {value}")
67
+
68
+
69
+ def cmd_completion(args):
70
+ """Generate shell completion scripts."""
71
+ shell = args.shell
72
+
73
+ if shell == "bash":
74
+ print('''# Tokenade bash completion
75
+ _tokenade() {
76
+ local cur prev commands
77
+ COMPREPLY=()
78
+ cur="${COMP_WORDS[COMP_CWORD]}"
79
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
80
+ commands="setup config extract transfer test fingerprint validate export load inject-profile encrypt decrypt rekey batch-export batch-load health refresh proxy sessions share unshare sync validate-rules diff plugin completion launch refresh-browser accounts patch-chrome"
81
+
82
+ if [[ ${cur} == -* ]] ; then
83
+ COMPREPLY=( $(compgen -W "--help --version --verbose" -- ${cur}) )
84
+ return 0
85
+ fi
86
+
87
+ COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) )
88
+ return 0
89
+ }
90
+ complete -F _tokenade tokenade
91
+ ''')
92
+ elif shell == "zsh":
93
+ print('''# Tokenade zsh completion
94
+ _tokenade() {
95
+ local commands
96
+ commands=(
97
+ 'setup:Setup accounts'
98
+ 'config:Manage configuration'
99
+ 'extract:Extract tokens'
100
+ 'export:Export session from browser'
101
+ 'load:Load session file'
102
+ 'inject-profile:Inject cookies into profile'
103
+ 'proxy:Start proxy server'
104
+ 'health:Check session health'
105
+ 'refresh:Refresh session'
106
+ 'encrypt:Encrypt session file'
107
+ 'decrypt:Decrypt session file'
108
+ 'sessions:Manage sessions'
109
+ 'share:Share session'
110
+ 'batch-export:Batch export'
111
+ 'batch-load:Batch load'
112
+ 'diff:Compare sessions'
113
+ 'completion:Generate shell completion'
114
+ )
115
+ _describe 'tokenade' commands
116
+ }
117
+ compdef _tokenade tokenade
118
+ ''')
119
+ elif shell == "fish":
120
+ print('''# Tokenade fish completion
121
+ complete -c tokenade -f
122
+ complete -c tokenade -n "__fish_use_subcommand" -a "setup" -d "Setup accounts"
123
+ complete -c tokenade -n "__fish_use_subcommand" -a "config" -d "Manage configuration"
124
+ complete -c tokenade -n "__fish_use_subcommand" -a "extract" -d "Extract tokens"
125
+ complete -c tokenade -n "__fish_use_subcommand" -a "export" -d "Export session"
126
+ complete -c tokenade -n "__fish_use_subcommand" -a "load" -d "Load session"
127
+ complete -c tokenade -n "__fish_use_subcommand" -a "proxy" -d "Start proxy"
128
+ complete -c tokenade -n "__fish_use_subcommand" -a "health" -d "Check health"
129
+ complete -c tokenade -n "__fish_use_subcommand" -a "encrypt" -d "Encrypt session"
130
+ complete -c tokenade -n "__fish_use_subcommand" -a "decrypt" -d "Decrypt session"
131
+ complete -c tokenade -n "__fish_use_subcommand" -a "sessions" -d "Manage sessions"
132
+ complete -c tokenade -n "__fish_use_subcommand" -a "share" -d "Share session"
133
+ complete -c tokenade -n "__fish_use_subcommand" -a "completion" -d "Shell completion"
134
+ ''')
135
+ else:
136
+ print(f"Unsupported shell: {shell}. Use bash, zsh, or fish.")
137
+ sys.exit(1)
138
+
139
+
140
+ def cmd_plugin(args):
141
+ """Manage plugins."""
142
+ from tokenade.core.integration.plugin_registry import PluginRegistry
143
+ from tokenade.core.integration.plugin_loader import PluginLoader
144
+
145
+ registry = PluginRegistry()
146
+ loader = PluginLoader()
147
+
148
+ if args.plugin_command == "list":
149
+ if args.available:
150
+ installed_names = {p["name"] for p in loader.discover()}
151
+ print("\n🌐 Available plugins from registry:")
152
+ plugins = registry.search()
153
+ if not plugins:
154
+ print(" No plugins found in registry")
155
+ for p in plugins:
156
+ status = " ✓ installed" if p["name"] in installed_names else ""
157
+ print(f" • {p['name']} v{p.get('version', '?')} — {p.get('description', '')}{status}")
158
+ else:
159
+ print("\n📦 Installed plugins:")
160
+ installed = loader.discover()
161
+ if not installed:
162
+ print(" No plugins installed. Use 'tokenade plugin install <name>' to install.")
163
+ for p in installed:
164
+ enabled = " ✓" if p.get("enabled", True) else " (disabled)"
165
+ print(f" • {p['name']} v{p.get('version', '?')} ({p.get('type', '?')}){enabled} — {p.get('description', '')}")
166
+
167
+ elif args.plugin_command == "install":
168
+ print(f"\n📥 Installing plugin: {args.name}")
169
+ if registry.install(args.name):
170
+ from tokenade.core.integration.plugin_verifier import PluginVerifier
171
+ verifier = PluginVerifier()
172
+ verifier.register_plugin(args.name)
173
+ installed = loader.discover()
174
+ deps_installed = [p["name"] for p in installed if p["name"] != args.name]
175
+ if deps_installed:
176
+ print(f" 📦 Dependencies installed: {', '.join(deps_installed)}")
177
+ print(" ✅ Plugin installed successfully")
178
+ else:
179
+ print(" ❌ Failed to install plugin")
180
+
181
+ elif args.plugin_command == "uninstall":
182
+ print(f"\n🗑️ Uninstalling plugin: {args.name}")
183
+ if registry.uninstall(args.name):
184
+ print(" ✅ Plugin uninstalled successfully")
185
+ else:
186
+ print(" ❌ Failed to uninstall plugin (not installed or dependency conflict)")
187
+
188
+ elif args.plugin_command == "info":
189
+ installed = loader.discover()
190
+ plugin = None
191
+ for p in installed:
192
+ if p["name"] == args.name:
193
+ plugin = p
194
+ break
195
+ if not plugin:
196
+ registry_details = registry.get_plugin_details(args.name)
197
+ if registry_details:
198
+ print(f"\n📋 Plugin: {args.name} (not installed)")
199
+ print(f" Version: {registry_details.get('version', '?')}")
200
+ print(f" Type: {registry_details.get('type', '?')}")
201
+ print(f" Author: {registry_details.get('author', '?')}")
202
+ print(f" Description: {registry_details.get('description', '')}")
203
+ if registry_details.get("dependencies"):
204
+ print(f" Dependencies: {', '.join(registry_details['dependencies'])}")
205
+ print(f" Install: tokenade plugin install {args.name}")
206
+ else:
207
+ print(f"❌ Plugin not found: {args.name}")
208
+ return
209
+ installed_ver = plugin.get("version", "?")
210
+ enabled = plugin.get("enabled", True)
211
+ print(f"\n📋 Plugin: {plugin['name']}")
212
+ print(f" Version: {installed_ver}")
213
+ print(f" Type: {plugin.get('type', '?')}")
214
+ print(f" Author: {plugin.get('author', '?')}")
215
+ print(f" Description: {plugin.get('description', '')}")
216
+ print(f" Status: {'enabled' if enabled else 'disabled'}")
217
+ if plugin.get("dependencies"):
218
+ print(f" Dependencies: {', '.join(plugin['dependencies'])}")
219
+ registry_details = registry.get_plugin_details(args.name)
220
+ if registry_details and registry_details.get("version") != installed_ver:
221
+ print(f" Registry version: {registry_details['version']} (update available)")
222
+ from tokenade.core.integration.plugin_verifier import PluginVerifier
223
+ verifier = PluginVerifier()
224
+ if verifier._local_checksums.get(args.name):
225
+ result = verifier.verify(args.name)
226
+ print(f" Integrity: {'✓ verified' if result.verified else '✗ tampered'}")
227
+ else:
228
+ print(f" Integrity: unregistered (run 'tokenade plugin verify' to register)")
229
+
230
+ elif args.plugin_command == "enable":
231
+ if loader.enable(args.name):
232
+ print(f"✅ Plugin enabled: {args.name}")
233
+ else:
234
+ print(f"❌ Plugin not found: {args.name}")
235
+
236
+ elif args.plugin_command == "disable":
237
+ if loader.disable(args.name):
238
+ print(f"✅ Plugin disabled: {args.name}")
239
+ else:
240
+ print(f"❌ Plugin not found: {args.name}")
241
+
242
+ elif args.plugin_command == "update":
243
+ if args.name:
244
+ print(f"\n🔄 Updating {args.name}...")
245
+ if registry.update(args.name):
246
+ print(f" ✅ Updated: {args.name}")
247
+ else:
248
+ print(f" ❌ Failed to update: {args.name}")
249
+ else:
250
+ outdated = registry.get_outdated()
251
+ if not outdated:
252
+ print("\n✅ All plugins are up to date.")
253
+ else:
254
+ print(f"\n🔄 Updating {len(outdated)} plugin(s)...")
255
+ results = registry.update()
256
+ updated = sum(1 for v in results.values() if v)
257
+ failed = len(results) - updated
258
+ print(f" ✅ {updated} updated, ❌ {failed} failed")
259
+ for name, success in results.items():
260
+ if not success:
261
+ print(f" Failed: {name}")
262
+
263
+ elif args.plugin_command == "sync":
264
+ print("\n🔄 Syncing plugins from registry...")
265
+ plugins = registry.get_popular(limit=100)
266
+ installed = {p.name for p in loader.list_all()}
267
+ to_install = [p for p in plugins if p.get("name") not in installed]
268
+
269
+ if not to_install:
270
+ print("✅ All available plugins already installed.")
271
+ else:
272
+ print(f" Installing {len(to_install)} plugin(s)...")
273
+ for p in to_install:
274
+ name = p.get("name", "")
275
+ success = registry.install(name)
276
+ if success:
277
+ print(f" ✅ {name}")
278
+ else:
279
+ print(f" ❌ {name}")
280
+ loader.load_all()
281
+ print(f"\n Done. {len(loader.list_all())} plugins installed.")
282
+
283
+ elif args.plugin_command == "reload":
284
+ loaded = loader.reload(args.name)
285
+ if loaded:
286
+ print(f"✅ Plugin reloaded: {args.name} v{loaded.version}")
287
+ else:
288
+ print(f"❌ Failed to reload: {args.name}")
289
+
290
+ elif args.plugin_command == "search":
291
+ _plugin_search(registry, args)
292
+
293
+ elif args.plugin_command == "categories":
294
+ _plugin_categories(registry)
295
+
296
+ elif args.plugin_command == "popular":
297
+ _plugin_popular(registry, args)
298
+
299
+ elif args.plugin_command == "recent":
300
+ _plugin_recent(registry, args)
301
+
302
+ elif args.plugin_command == "rate":
303
+ _plugin_rate(registry, args)
304
+
305
+ elif args.plugin_command == "ratings":
306
+ _plugin_ratings(registry, args)
307
+
308
+ elif args.plugin_command == "verify":
309
+ _plugin_verify(args)
310
+
311
+ elif args.plugin_command == "outdated":
312
+ _plugin_outdated(registry)
313
+
314
+ elif args.plugin_command == "browse":
315
+ _plugin_browse(registry, args)
316
+
317
+ elif args.plugin_command == "test":
318
+ _plugin_test(args)
319
+
320
+ else:
321
+ print("Usage: tokenade plugin {list|install|uninstall|info|enable|disable|update|reload|search|categories|popular|recent|rate|verify|outdated|browse|test}")
322
+
323
+
324
+ def _plugin_search(registry, args):
325
+ """Search plugins in marketplace."""
326
+ tags = None
327
+ if args.tags:
328
+ tags = [t.strip() for t in args.tags.split(",")]
329
+
330
+ results = registry.search(
331
+ query=args.query or "",
332
+ plugin_type=args.plugin_type or "",
333
+ category=args.category or "",
334
+ tags=tags,
335
+ sort_by=args.sort,
336
+ )
337
+
338
+ if not results:
339
+ print("No plugins found matching your search.")
340
+ return
341
+
342
+ print(f"\n{'=' * 70}")
343
+ print(f"TOKENADE - Plugin Search ({len(results)} results)")
344
+ print(f"{'=' * 70}")
345
+
346
+ for p in results:
347
+ stars = f"★ {p.get('rating', 0):.1f}" if p.get('rating') else ""
348
+ verified = " ✓" if p.get('verified') else ""
349
+ downloads = f"↓ {p.get('downloads', 0)}" if p.get('downloads') else ""
350
+ print(f"\n {p['name']}{verified} v{p.get('version', '?')}")
351
+ print(f" {p.get('description', '')}")
352
+ meta = " | ".join(filter(None, [stars, downloads, f"by {p.get('author', '')}"]))
353
+ if meta:
354
+ print(f" {meta}")
355
+ if p.get('tags'):
356
+ print(f" Tags: {', '.join(p['tags'])}")
357
+
358
+ print(f"\n{'=' * 70}\n")
359
+
360
+
361
+ def _plugin_categories(registry):
362
+ """List plugin categories."""
363
+ categories = registry.get_categories()
364
+
365
+ print(f"\n{'=' * 60}")
366
+ print("TOKENADE - Plugin Categories")
367
+ print(f"{'=' * 60}")
368
+
369
+ for cat in categories:
370
+ print(f"\n {cat.get('icon', '')} {cat['name']}")
371
+ print(f" {cat.get('description', '')}")
372
+ print(f" {cat.get('plugin_count', 0)} plugin(s)")
373
+
374
+ print(f"\n{'=' * 60}\n")
375
+
376
+
377
+ def _plugin_popular(registry, args):
378
+ """Show popular plugins."""
379
+ plugins = registry.get_popular(limit=args.limit)
380
+
381
+ if not plugins:
382
+ print("No plugins found in registry.")
383
+ return
384
+
385
+ print(f"\n{'=' * 60}")
386
+ print(f"TOKENADE - Popular Plugins (top {len(plugins)})")
387
+ print(f"{'=' * 60}")
388
+
389
+ for i, p in enumerate(plugins, 1):
390
+ print(f"\n {i}. {p['name']} v{p.get('version', '?')}")
391
+ print(f" {p.get('description', '')}")
392
+ dl = p.get("downloads")
393
+ rt = p.get("rating")
394
+ if dl is None and rt is None:
395
+ print(" metrics: n/a (downloads/ratings not tracked yet)")
396
+ else:
397
+ dl_s = f"↓ {dl} downloads" if dl is not None else "↓ n/a"
398
+ rt_s = f"★ {rt:.1f}" if rt is not None else "★ n/a"
399
+ print(f" {dl_s} | {rt_s}")
400
+
401
+ print(f"\n{'=' * 60}\n")
402
+
403
+
404
+ def _plugin_recent(registry, args):
405
+ """Show recent plugins."""
406
+ plugins = registry.get_recent(limit=args.limit)
407
+
408
+ if not plugins:
409
+ print("No plugins found in registry.")
410
+ return
411
+
412
+ print(f"\n{'=' * 60}")
413
+ print(f"TOKENADE - Recent Plugins ({len(plugins)} newest)")
414
+ print(f"{'=' * 60}")
415
+
416
+ for i, p in enumerate(plugins, 1):
417
+ print(f"\n {i}. {p['name']} v{p.get('version', '?')}")
418
+ print(f" {p.get('description', '')}")
419
+ print(f" by {p.get('author', 'unknown')}")
420
+
421
+ print(f"\n{'=' * 60}\n")
422
+
423
+
424
+ def _plugin_rate(registry, args):
425
+ """Rate a plugin."""
426
+ if registry.rate_plugin(args.name, args.rating, args.review or ""):
427
+ print(f"✅ Rated {args.name}: {args.rating}/5")
428
+ if args.review:
429
+ print(f" Review: {args.review}")
430
+
431
+ # Sync to GitHub if PAT is set
432
+ from tokenade.core.integration.rating_sync import RatingSync
433
+ sync = RatingSync()
434
+ if sync.has_pat():
435
+ if sync.submit_rating(args.name, args.rating, args.review or ""):
436
+ print(" 🌐 Synced to GitHub")
437
+ else:
438
+ print(" ⚠️ GitHub sync failed (local rating saved)")
439
+ else:
440
+ print(" 💡 Set github-token to sync globally: tokenade config set github-token <PAT>")
441
+ else:
442
+ print(f"❌ Failed to rate {args.name} (rating must be 1.0-5.0)")
443
+
444
+
445
+ def _plugin_ratings(registry, args):
446
+ """View global ratings from GitHub."""
447
+ from tokenade.core.integration.rating_sync import RatingSync
448
+ sync = RatingSync()
449
+
450
+ global_ratings = sync.get_global_ratings()
451
+
452
+ if not global_ratings:
453
+ print("\n No global ratings found.")
454
+ if not sync.has_pat():
455
+ print(" 💡 Set github-token to sync ratings: tokenade config set github-token <PAT>")
456
+ return
457
+
458
+ name = getattr(args, "name", None)
459
+
460
+ if name:
461
+ # Show specific plugin
462
+ if name in global_ratings:
463
+ r = global_ratings[name]
464
+ stars = "★" * int(r.get("rating", 0)) + "☆" * (5 - int(r.get("rating", 0)))
465
+ print(f"\n {name} — {stars} ({r.get('rating', 0):.1f}/5)")
466
+ print(f" Reviews: {r.get('review_count', 0)}")
467
+ for rev in r.get("reviews", []):
468
+ print(f" ⭐ {rev.get('rating', 0)}: {rev.get('review', '')[:80]}")
469
+ else:
470
+ print(f"\n No global ratings for: {name}")
471
+ else:
472
+ # Show all
473
+ print(f"\n 🌐 Global Ratings ({len(global_ratings)} plugins)\n")
474
+ for name, r in sorted(global_ratings.items()):
475
+ stars = "★" * int(r.get("rating", 0)) + "☆" * (5 - int(r.get("rating", 0)))
476
+ print(f" {stars} {r.get('rating', 0):.1f} {name} ({r.get('review_count', 0)} reviews)")
477
+ if not sync.has_pat():
478
+ print(f"\n 💡 Set github-token to sync: tokenade config set github-token <PAT>")
479
+
480
+
481
+ def _plugin_verify(args):
482
+ """Verify plugin checksums. Auto-registers on first run."""
483
+ from tokenade.core.integration.plugin_verifier import PluginVerifier
484
+
485
+ verifier = PluginVerifier()
486
+ plugin_names = [args.name] if args.name else [
487
+ d.name for d in sorted(verifier.plugins_dir.iterdir())
488
+ if d.is_dir() and not d.name.startswith(".") and (d / "plugin.json").exists()
489
+ ]
490
+
491
+ if not plugin_names:
492
+ print("No installed plugins to verify.")
493
+ return
494
+
495
+ results = []
496
+ registered = []
497
+ for name in plugin_names:
498
+ if not verifier._local_checksums.get(name):
499
+ verifier.register_plugin(name)
500
+ registered.append(name)
501
+ results.append(verifier.verify(name))
502
+ else:
503
+ results.append(verifier.verify(name))
504
+
505
+ print(f"\n{'=' * 60}")
506
+ print("TOKENADE - Plugin Verification")
507
+ print(f"{'=' * 60}")
508
+
509
+ all_ok = True
510
+ for r in results:
511
+ if r.plugin_name in registered:
512
+ icon = "📝"
513
+ status = f"registered ({r.files_checked} files checksummed)"
514
+ elif r.verified:
515
+ icon = "✅"
516
+ status = r.summary
517
+ else:
518
+ icon = "❌"
519
+ status = r.summary
520
+ all_ok = False
521
+ print(f"\n {icon} {r.plugin_name}: {status}")
522
+ for err in r.errors:
523
+ print(f" ⚠️ {err}")
524
+
525
+ print(f"\n{'=' * 60}")
526
+ if all_ok:
527
+ print("All plugins verified successfully.")
528
+ else:
529
+ print("Some plugins failed verification!")
530
+ print(f"{'=' * 60}\n")
531
+ print(f"{'=' * 60}\n")
532
+
533
+
534
+ def _plugin_outdated(registry):
535
+ """Show outdated plugins."""
536
+ outdated = registry.get_outdated()
537
+
538
+ if not outdated:
539
+ print("All installed plugins are up to date.")
540
+ return
541
+
542
+ print(f"\n{'=' * 60}")
543
+ print(f"TOKENADE - Outdated Plugins ({len(outdated)})")
544
+ print(f"{'=' * 60}")
545
+
546
+ for p in outdated:
547
+ print(f"\n {p['name']}")
548
+ print(f" Installed: {p['installed_version']} → Available: {p['available_version']}")
549
+ if p.get('description'):
550
+ print(f" {p['description']}")
551
+
552
+ print(f"\n Run 'tokenade plugin update' to update all.")
553
+ print(f"{'=' * 60}\n")
554
+
555
+
556
+ def _plugin_browse(registry, args):
557
+ """Generate static HTML marketplace page."""
558
+ from pathlib import Path
559
+ from tokenade.core.integration.plugin_browser import generate_marketplace_html
560
+
561
+ plugins = registry.search()
562
+ categories = registry.get_categories()
563
+
564
+ output_path = args.output or str(Path.home() / ".tokenade" / "marketplace.html")
565
+
566
+ path = generate_marketplace_html(plugins, categories, output_path)
567
+ print(f"✅ Marketplace page generated: {path}")
568
+ print(f" Open in browser: file://{path}")
569
+
570
+
571
+ def _plugin_test(args):
572
+ """Run tests on installed plugins."""
573
+ from tokenade.core.integration.plugin_testing import PluginTestRunner
574
+
575
+ runner = PluginTestRunner()
576
+
577
+ if args.name:
578
+ suites = [runner.test_plugin(args.name)]
579
+ else:
580
+ suites = runner.test_all()
581
+
582
+ if not suites:
583
+ print("No plugins found to test.")
584
+ return
585
+
586
+ print(f"\n{'=' * 60}")
587
+ print("TOKENADE - Plugin Tests")
588
+ print(f"{'=' * 60}")
589
+
590
+ total_passed = 0
591
+ total_failed = 0
592
+
593
+ for suite in suites:
594
+ status = "✅" if suite.passed else "❌"
595
+ print(f"\n{status} {suite.summary()}")
596
+ for result in suite.results:
597
+ icon = " ✓" if result.passed else " ✗"
598
+ msg = f" — {result.message}" if result.message and not result.passed else ""
599
+ print(f"{icon} {result.test_name}{msg}")
600
+ total_passed += suite.passed_count
601
+ total_failed += suite.failed_count
602
+
603
+ print(f"\n{'=' * 60}")
604
+ print(f"Results: {total_passed} passed, {total_failed} failed")
605
+ print(f"{'=' * 60}\n")
606
+
607
+
608
+ def cmd_profile(args):
609
+ """Manage browser profiles."""
610
+ from tokenade.core.browser.profiles import ProfileManager
611
+
612
+ manager = ProfileManager()
613
+
614
+ if args.profile_command == "create":
615
+ proxy = None
616
+ if args.proxy:
617
+ proxy = {"url": args.proxy}
618
+ tags = [t.strip() for t in args.tags.split(",")] if args.tags else []
619
+ try:
620
+ profile = manager.create_profile(
621
+ name=args.name,
622
+ browser=args.browser,
623
+ os_name=args.os_name,
624
+ proxy=proxy,
625
+ tags=tags,
626
+ notes=args.notes or "",
627
+ )
628
+ print(f"\n✅ Created profile: {profile.name}")
629
+ print(f" Browser: {profile.browser} | OS: {profile.os}")
630
+ print(f" ID: {profile.id}")
631
+ if profile.fingerprint.get("navigator"):
632
+ nav = profile.fingerprint["navigator"]
633
+ print(f" Hardware: {nav.get('hardwareConcurrency', '?')} cores, {nav.get('deviceMemory', '?')} GB RAM")
634
+ if profile.fingerprint.get("webgl"):
635
+ gl = profile.fingerprint["webgl"]
636
+ print(f" GPU: {gl.get('renderer', '?')}")
637
+ print()
638
+ except ValueError as e:
639
+ print(f"\n❌ {e}\n")
640
+
641
+ elif args.profile_command == "list":
642
+ profiles = manager.list_profiles(browser=args.browser, tag=args.tag)
643
+ if not profiles:
644
+ print("\nNo profiles found.\n")
645
+ return
646
+ print(f"\n{'=' * 60}")
647
+ print(f"TOKENADE - Browser Profiles ({len(profiles)} total)")
648
+ print(f"{'=' * 60}")
649
+ for p in profiles:
650
+ last_used = time.strftime("%Y-%m-%d %H:%M", time.localtime(p.last_used)) if p.last_used else "never"
651
+ tags = f" [{', '.join(p.tags)}]" if p.tags else ""
652
+ print(f"\n {p.name}{tags}")
653
+ print(f" {p.browser} / {p.os} | ID: {p.id}")
654
+ print(f" Last used: {last_used}")
655
+ if p.proxy:
656
+ print(f" Proxy: {p.proxy.get('url', 'configured')}")
657
+ print(f"\n{'=' * 60}\n")
658
+
659
+ elif args.profile_command == "get":
660
+ profile = manager.get_profile(args.name)
661
+ if not profile:
662
+ print(f"\n❌ Profile not found: {args.name}\n")
663
+ return
664
+ print(f"\n{'=' * 60}")
665
+ print(f"TOKENADE - Profile: {profile.name}")
666
+ print(f"{'=' * 60}")
667
+ print(f" ID: {profile.id}")
668
+ print(f" Browser: {profile.browser}")
669
+ print(f" OS: {profile.os}")
670
+ print(f" Created: {time.strftime('%Y-%m-%d %H:%M', time.localtime(profile.created_at))}")
671
+ print(f" Updated: {time.strftime('%Y-%m-%d %H:%M', time.localtime(profile.updated_at))}")
672
+ last_used = time.strftime('%Y-%m-%d %H:%M', time.localtime(profile.last_used)) if profile.last_used else "never"
673
+ print(f" Last used: {last_used}")
674
+ if profile.tags:
675
+ print(f" Tags: {', '.join(profile.tags)}")
676
+ if profile.notes:
677
+ print(f" Notes: {profile.notes}")
678
+ if profile.proxy:
679
+ print(f" Proxy: {profile.proxy.get('url', 'configured')}")
680
+ if profile.fingerprint:
681
+ nav = profile.fingerprint.get("navigator", {})
682
+ gl = profile.fingerprint.get("webgl", {})
683
+ scr = profile.fingerprint.get("screen", {})
684
+ print(f"\n Fingerprint:")
685
+ print(f" Platform: {nav.get('platform', '?')}")
686
+ print(f" Language: {nav.get('language', '?')}")
687
+ print(f" Cores: {nav.get('hardwareConcurrency', '?')} | RAM: {nav.get('deviceMemory', '?')} GB")
688
+ print(f" Screen: {scr.get('width', '?')}x{scr.get('height', '?')}")
689
+ print(f" GPU: {gl.get('renderer', '?')}")
690
+ print(f"\n{'=' * 60}\n")
691
+
692
+ elif args.profile_command == "delete":
693
+ if manager.delete_profile(args.name):
694
+ print(f"\n✅ Deleted profile: {args.name}\n")
695
+ else:
696
+ print(f"\n❌ Profile not found: {args.name}\n")
697
+
698
+ elif args.profile_command == "export":
699
+ try:
700
+ path = manager.export_profile(args.name, args.output or f"{args.name}.zip")
701
+ print(f"\n✅ Exported profile: {path}\n")
702
+ except FileNotFoundError as e:
703
+ print(f"\n❌ {e}\n")
704
+
705
+ elif args.profile_command == "import":
706
+ try:
707
+ profile = manager.import_profile(args.archive, name=args.name)
708
+ print(f"\n✅ Imported profile: {profile.name}\n")
709
+ except (FileNotFoundError, ValueError) as e:
710
+ print(f"\n❌ {e}\n")
711
+
712
+ elif args.profile_command == "recent":
713
+ profiles = manager.get_recent_profiles(limit=args.limit)
714
+ if not profiles:
715
+ print("\nNo recently used profiles.\n")
716
+ return
717
+ print(f"\nRecently used profiles:")
718
+ for i, p in enumerate(profiles, 1):
719
+ last_used = time.strftime("%Y-%m-%d %H:%M", time.localtime(p.last_used)) if p.last_used else "never"
720
+ print(f" {i}. {p.name} ({p.browser}/{p.os}) — last used: {last_used}")
721
+ print()
722
+
723
+ elif args.profile_command == "stats":
724
+ stats = manager.get_stats()
725
+ print(f"\nProfile Statistics:")
726
+ print(f" Total: {stats['total']}")
727
+ for browser, count in stats.get("by_browser", {}).items():
728
+ print(f" {browser}: {count}")
729
+ print()
730
+
731
+
732
+ def cmd_stealth(args):
733
+ """Browser stealth management."""
734
+ from pathlib import Path
735
+ from tokenade.core.browser.stealth import StealthManager
736
+ from tokenade.core.browser.dependencies import DependencyChecker
737
+
738
+ if args.stealth_action == "test":
739
+ from tokenade.core.browser.stealth_test import StealthTestSuite
740
+ from tokenade.core.browser.dashboard import generate_html_report, generate_json_report
741
+
742
+ browser = args.browser
743
+ print(f"\n🔍 Running stealth tests ({browser})...")
744
+ suite = StealthTestSuite(browser=browser, headless=True)
745
+ report = asyncio.run(suite.run_all(url=args.url))
746
+
747
+ print(f"\n{'=' * 60}")
748
+ print(f" STEALTH SCORE: {report.overall_score:.0f}/100 ({report._grade()})")
749
+ print(f"{'=' * 60}")
750
+ print(f" {report.passed} passed, {report.failed} failed, {report.warned} warned")
751
+ print()
752
+
753
+ for r in report.results:
754
+ icon = {"pass": "✅", "fail": "❌", "warn": "⚠️", "skip": "○"}[r.verdict.value]
755
+ print(f" {icon} {r.name} ({r.score:.0f})")
756
+
757
+ # Save reports
758
+ html_path = args.output or str(Path.home() / ".tokenade" / "stealth_report.html")
759
+ generate_html_report(report, html_path)
760
+ json_path = html_path.replace(".html", ".json")
761
+ generate_json_report(report, json_path)
762
+ print(f"\n 📄 HTML report: {html_path}")
763
+ print(f" 📄 JSON report: {json_path}")
764
+ print(f"{'=' * 60}\n")
765
+
766
+ elif args.stealth_action == "report":
767
+ import json
768
+ from pathlib import Path
769
+ print(f"\n📊 Stealth Report")
770
+ manager = StealthManager()
771
+ config = manager.get_config_dict()
772
+ enabled = [k for k, v in config.items() if v is True and k.startswith("enable_")]
773
+ print(f" Enabled patches: {len(enabled)}")
774
+ for patch in enabled:
775
+ print(f" ✓ {patch.replace('enable_', '')}")
776
+ print(f"\n WebGL vendor: {config['webgl_vendor']}")
777
+ print(f" WebGL renderer: {config['webgl_renderer']}")
778
+ print(f" Screen: {config['screen_width']}x{config['screen_height']}")
779
+ output_path = args.output or str(Path.home() / ".tokenade" / "stealth_report.json")
780
+ Path(output_path).parent.mkdir(parents=True, exist_ok=True)
781
+ with open(output_path, "w") as f:
782
+ json.dump(config, f, indent=2)
783
+ print(f" Report saved: {output_path}")
784
+
785
+ elif args.stealth_action == "deps":
786
+ checker = DependencyChecker()
787
+ report = checker.get_report("chromium")
788
+ print(f"\n📦 System Dependencies ({report['system']})")
789
+ print(f" Package manager: {report['package_manager'] or 'not found'}")
790
+ print(f" Installed: {report['installed']}/{report['total']}")
791
+ if report['missing_packages']:
792
+ print(f" Missing ({len(report['missing_packages'])}):")
793
+ for pkg in report['missing_packages']:
794
+ print(f" ❌ {pkg}")
795
+ else:
796
+ print(f" ✅ All dependencies installed")
797
+
798
+ elif args.stealth_action == "deps-install":
799
+ print(f"\n📦 Installing missing dependencies...")
800
+ checker = DependencyChecker()
801
+ results = checker.install_playwright_deps()
802
+ installed = sum(1 for r in results if r.success)
803
+ failed = sum(1 for r in results if not r.success)
804
+ print(f" Installed: {installed}, Failed: {failed}")
805
+ for r in results:
806
+ if r.success:
807
+ print(f" ✅ {r.name}")
808
+ else:
809
+ print(f" ❌ {r.name}: {r.message}")
810
+
811
+ elif args.stealth_action == "battle":
812
+ from tokenade.core.browser.battle import BattleTestSuite, DETECTION_SITES
813
+
814
+ browser = args.browser
815
+ sites = args.site if args.site else list(DETECTION_SITES.keys())
816
+ timeout_ms = args.timeout * 1000
817
+
818
+ print(f"\n⚔️ Running battle tests ({browser})...")
819
+ print(f" Sites: {len(sites)}")
820
+ for s in sites:
821
+ cfg = DETECTION_SITES.get(s, {})
822
+ print(f" • {cfg.get('name', s)}")
823
+ print()
824
+
825
+ suite = BattleTestSuite(
826
+ browser=browser,
827
+ headless=True,
828
+ sites=sites,
829
+ timeout_ms=timeout_ms,
830
+ )
831
+ report = suite.run_sync()
832
+
833
+ print(report.summary())
834
+ print(f"\n Duration: {report.duration_ms / 1000:.1f}s")
835
+
836
+ if args.output:
837
+ import json as json_mod
838
+ from pathlib import Path
839
+ out = Path(args.output)
840
+ out.parent.mkdir(parents=True, exist_ok=True)
841
+ data = {
842
+ "overall_score": report.overall_score,
843
+ "grade": report.grade,
844
+ "browser": report.browser,
845
+ "timestamp": report.timestamp,
846
+ "duration_ms": report.duration_ms,
847
+ "passed": report.passed,
848
+ "detected": report.detected,
849
+ "partial": report.partial,
850
+ "errors": report.errors,
851
+ "skipped": report.skipped,
852
+ "sites": [
853
+ {
854
+ "name": r.site_name,
855
+ "url": r.url,
856
+ "verdict": r.verdict.value,
857
+ "score": r.score,
858
+ "details": r.detection_details,
859
+ "error": r.error,
860
+ "duration_ms": r.duration_ms,
861
+ }
862
+ for r in report.site_results
863
+ ],
864
+ }
865
+ with open(out, "w") as f:
866
+ json_mod.dump(data, f, indent=2)
867
+ print(f"\n 📄 JSON report: {args.output}")
868
+
869
+ print(f"\n{'=' * 60}\n")
870
+
871
+ else:
872
+ print("Usage: tokenade stealth {test|report|battle|deps|deps-install}")
873
+
874
+
875
+ def cmd_deps(args):
876
+ """System dependency management."""
877
+ from tokenade.core.browser.dependencies import DependencyChecker
878
+
879
+ checker = DependencyChecker()
880
+
881
+ if args.deps_action == "check":
882
+ browser = getattr(args, "browser", "chromium")
883
+ report = checker.get_report(browser)
884
+ print(f"\n📦 System Dependencies ({report['system']})")
885
+ print(f" Package manager: {report['package_manager'] or 'not found'}")
886
+ print(f" Browser: {browser}")
887
+ print(f" Installed: {report['installed']}/{report['total']}")
888
+ if report['missing_packages']:
889
+ print(f" Missing packages:")
890
+ for pkg in report['missing_packages']:
891
+ print(f" ❌ {pkg}")
892
+ print(f"\n Install: tokenade deps install")
893
+ else:
894
+ print(f" ✅ All dependencies installed")
895
+
896
+ elif args.deps_action == "install":
897
+ browser = getattr(args, "browser", "chromium")
898
+ playwright_only = getattr(args, "playwright", False)
899
+
900
+ if playwright_only:
901
+ print(f"\n📦 Installing Playwright dependencies...")
902
+ results = checker.install_playwright_deps()
903
+ else:
904
+ print(f"\n📦 Installing {browser} dependencies...")
905
+ results = checker.install_missing(browser)
906
+
907
+ installed = sum(1 for r in results if r.success)
908
+ failed = sum(1 for r in results if not r.success)
909
+ print(f" Installed: {installed}, Failed: {failed}")
910
+ for r in results:
911
+ if r.success:
912
+ print(f" ✅ {r.name}")
913
+ else:
914
+ print(f" ❌ {r.name}: {r.message}")
915
+
916
+ else:
917
+ print("Usage: tokenade deps {check|install}")
918
+
919
+
920
+ def cmd_serve(args):
921
+ """Start the API server."""
922
+ import asyncio
923
+ from tokenade.core.api.server import TokenadeAPIServer, APIServerConfig
924
+
925
+ config = APIServerConfig(
926
+ host=args.host,
927
+ port=args.port,
928
+ api_key=args.api_key,
929
+ sessions_dir=args.sessions_dir or "~/.tokenade/sessions",
930
+ cors_origins=args.cors.split(",") if args.cors else None,
931
+ )
932
+
933
+ print(f"\n🚀 Starting Tokenade API server...")
934
+ print(f" Host: {config.host}")
935
+ print(f" Port: {config.port}")
936
+ print(f" Auth: {'API key required' if config.api_key else 'no authentication'}")
937
+ print(f" Sessions: {config.sessions_dir}")
938
+ print()
939
+
940
+ server = TokenadeAPIServer(config)
941
+
942
+ try:
943
+ asyncio.run(server.start())
944
+ except KeyboardInterrupt:
945
+ print("\n\n🛑 Server stopped")
946
+ except Exception as e:
947
+ print(f"\n❌ Server error: {e}")
948
+
949
+
950
+ logging.basicConfig(
951
+ level=logging.INFO,
952
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
953
+ )
954
+ logger = logging.getLogger("tokenade")
955
+
956
+
957
+ def setup_logging(verbose: bool = False):
958
+ """Configure logging level and structured output."""
959
+ from tokenade.core.logging.structured import LogManager
960
+ level = "DEBUG" if verbose else "INFO"
961
+ json_output = getattr(setup_logging, '_json_output', False)
962
+ LogManager.setup(level=level, json_output=json_output)
963
+
964
+
965
+ def _build_parser():
966
+ """Build and return the CLI argument parser (extracted for testability)."""
967
+ from tokenade import __version__
968
+ parser = argparse.ArgumentParser(
969
+ description="Tokenade - Browser session portability tool",
970
+ formatter_class=argparse.RawDescriptionHelpFormatter,
971
+ epilog="""
972
+ Quick Start:
973
+ 1. Export: tokenade export --browser-name firefox --domains "google.com,accounts.google.com" -o my_session.tokenade
974
+ 2. Proxy: tokenade proxy -s my_session.tokenade
975
+ 3. Browse: Open http://127.0.0.1:9222 and enter the target URL
976
+
977
+ Commands:
978
+ export Extract cookies from browser to .tokenade file
979
+ proxy Start CDP proxy server with donor session
980
+ load Load .tokenade session into a browser
981
+ inject-profile Inject cookies directly into browser profile
982
+ encrypt Encrypt a .tokenade file
983
+ decrypt Decrypt a .tokenade file
984
+ health Check session health
985
+ monitor Monitor session health in real-time
986
+ batch-export Export multiple sites at once
987
+ """,
988
+ )
989
+
990
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
991
+ parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose logging")
992
+ parser.add_argument("--json", dest="json_output", action="store_true", help="Output in JSON format")
993
+
994
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
995
+
996
+ # Setup
997
+ subparsers.add_parser("setup", help="Setup accounts")
998
+
999
+ # Config
1000
+ config_parser = subparsers.add_parser("config", help="Manage configuration")
1001
+ config_parser.add_argument("config_command", choices=["show", "set", "get", "path"],
1002
+ help="Config action")
1003
+ config_parser.add_argument("key", nargs="?", help="Config key")
1004
+ config_parser.add_argument("value", nargs="?", help="Config value")
1005
+
1006
+ # Extract
1007
+ extract_parser = subparsers.add_parser("extract", help="Extract tokens")
1008
+ extract_parser.add_argument("--visible", action="store_true", help="Show browser window")
1009
+
1010
+ # Transfer
1011
+ transfer_parser = subparsers.add_parser("transfer", help="Transfer session")
1012
+ transfer_parser.add_argument("-s", "--session", required=True, help="Session file path")
1013
+ transfer_parser.add_argument("-", "--fingerprint", help="Target fingerprint name")
1014
+ transfer_parser.add_argument("-p", "--profile-dir", default="browser_data/transfer", help="Profile directory")
1015
+ transfer_parser.add_argument("--visible", action="store_true", help="Show browser window")
1016
+ transfer_parser.add_argument("--stealth-level", choices=["basic", "advanced", "maximum"], default="maximum", help="Stealth injection level")
1017
+ transfer_parser.add_argument("--validate-stealth", action="store_true", help="Validate stealth injection after launch")
1018
+
1019
+ # Test
1020
+ test_parser = subparsers.add_parser("test", help="Test portability")
1021
+ test_parser.add_argument("-s", "--session", required=True, help="Session file path")
1022
+ test_parser.add_argument("--source-fp", default="default", help="Source fingerprint")
1023
+ test_parser.add_argument("--target-fp", default="default", help="Target fingerprint")
1024
+ test_parser.add_argument("--variations", action="store_true", help="Test fingerprint variations")
1025
+ test_parser.add_argument("--test-api", action="store_true", help="Test API calls")
1026
+ test_parser.add_argument("--stealth-level", choices=["basic", "advanced", "maximum"], default="maximum", help="Stealth injection level")
1027
+ test_parser.add_argument("--validate-stealth", action="store_true", help="Validate stealth injection")
1028
+ test_parser.add_argument("-o", "--output", help="Output report path")
1029
+
1030
+ # Fingerprint
1031
+ fp_parser = subparsers.add_parser("fingerprint", help="Manage fingerprints")
1032
+ fp_parser.add_argument("action", choices=["list", "collect", "show", "delete"], help="Action")
1033
+ fp_parser.add_argument("-n", "--name", help="Fingerprint name")
1034
+ fp_parser.add_argument("-p", "--profile-dir", help="Browser profile directory")
1035
+
1036
+ # Validate
1037
+ validate_parser = subparsers.add_parser("validate", help="Validate sessions")
1038
+ validate_parser.add_argument("-d", "--sessions-dir", default="sessions", help="Sessions directory")
1039
+
1040
+ # Export
1041
+ export_parser = subparsers.add_parser("export", help="Export session from existing browser")
1042
+ export_parser.add_argument("--browser-name", choices=["chrome", "firefox", "edge", "brave"], help="Browser name")
1043
+ export_parser.add_argument("--browser-path", help="Custom path to browser profile")
1044
+ export_parser.add_argument("--profile", help="Profile name within browser")
1045
+ export_parser.add_argument("--site-config", help="Path to JSON site config file for filtering")
1046
+ export_parser.add_argument("--domains", help="Comma-separated domains to filter (e.g. 'google.com,accounts.google.com')")
1047
+ export_parser.add_argument("--cdp-port", type=int, help="Extract via CDP from running browser (bypasses SQLite decryption)")
1048
+ export_parser.add_argument("--file-path", help="Export from cookies file")
1049
+ export_parser.add_argument("--format", choices=["netscape", "json", "curl"], default="netscape", help="File format")
1050
+ export_parser.add_argument("--collect-fingerprint", action="store_true", help="Collect source browser fingerprint")
1051
+ export_parser.add_argument("--output", "-o", help="Output file path (any extension)")
1052
+ export_parser.add_argument("--list-profiles", action="store_true", help="List available profiles")
1053
+ export_parser.add_argument("--decrypt", action="store_true", help="Decrypt cookies (auto-detected)")
1054
+ export_parser.add_argument("--extract-local-storage", action="store_true", help="Also extract localStorage data")
1055
+ export_parser.add_argument("--local-storage-origin", help="Origin to extract localStorage from")
1056
+ export_parser.add_argument("--full", action="store_true", help="Extract cookies + localStorage + sessionStorage (v3.0 format)")
1057
+ export_parser.add_argument("--no-storage", action="store_true", help="Extract only cookies (backward compat)")
1058
+ export_parser.add_argument("--encrypt-password", help="Encrypt .tokenade file with this password at export time")
1059
+ export_parser.add_argument("--plugin", help="Force specific site handler plugin (e.g., google-handler)")
1060
+ export_parser.add_argument("--no-plugin", action="store_true", help="Skip plugin, use default extraction")
1061
+ export_parser.add_argument("--list-handlers", action="store_true", help="List available site handler plugins")
1062
+
1063
+ # Load
1064
+ load_parser = subparsers.add_parser("load", help="Load session file into browser")
1065
+ load_parser.add_argument("--file", "-", required=True, help="Path to session file")
1066
+ load_parser.add_argument("--site-config", help="Path to JSON site config file for validation")
1067
+ load_parser.add_argument("--fingerprint", help="Target fingerprint name")
1068
+ load_parser.add_argument("--stealth-level", choices=["basic", "advanced", "maximum"], default="maximum", help="Stealth level")
1069
+ load_parser.add_argument("--validate", action="store_true", help="Validate session after injection")
1070
+ load_parser.add_argument("--runtime", action="store_true", help="Load into RuntimeEngine")
1071
+ load_parser.add_argument("--test-api", action="store_true", help="Test API after loading")
1072
+ load_parser.add_argument("--visible", action="store_true", help="Show browser window")
1073
+ load_parser.add_argument("--profile-dir", help="Browser profile directory")
1074
+ load_parser.add_argument("--no-local-storage", action="store_true", help="Skip localStorage injection if present")
1075
+
1076
+ # Inject Profile
1077
+ inject_parser = subparsers.add_parser("inject-profile", help="Inject cookies directly into browser profile")
1078
+ inject_parser.add_argument("--session", "-s", required=True, help="Path to session file")
1079
+ inject_parser.add_argument("--profile", "-p", required=True, help="Browser profile path")
1080
+ inject_parser.add_argument("--browser", "-b", choices=["chrome", "brave", "edge", "firefox", "opera", "vivaldi"],
1081
+ default="chrome", help="Browser name")
1082
+ inject_parser.add_argument("--no-backup", action="store_true", help="Skip backup creation")
1083
+ inject_parser.add_argument("--dry-run", action="store_true", help="Show what would be injected without making changes")
1084
+
1085
+ # Encrypt
1086
+ encrypt_parser = subparsers.add_parser("encrypt", help="Encrypt session file")
1087
+ encrypt_parser.add_argument("--input", "-i", required=True, help="Input file path")
1088
+ encrypt_parser.add_argument("--output", "-o", help="Output file path")
1089
+ encrypt_parser.add_argument("--password", "-p", help="Encryption password")
1090
+ encrypt_parser.add_argument("--key-file", "-k", help="Password file")
1091
+
1092
+ # Decrypt
1093
+ decrypt_parser = subparsers.add_parser("decrypt", help="Decrypt session file")
1094
+ decrypt_parser.add_argument("--input", "-i", required=True, help="Encrypted file path")
1095
+ decrypt_parser.add_argument("--output", "-o", help="Output file path")
1096
+ decrypt_parser.add_argument("--password", "-p", help="Decryption password")
1097
+ decrypt_parser.add_argument("--key-file", "-k", help="Password file")
1098
+
1099
+ # Rekey
1100
+ rekey_parser = subparsers.add_parser("rekey", help="Change encryption password")
1101
+ rekey_parser.add_argument("--input", "-i", required=True, help="Encrypted file path")
1102
+ rekey_parser.add_argument("--output", "-o", help="Output file path")
1103
+ rekey_parser.add_argument("--old-password", help="Old password")
1104
+ rekey_parser.add_argument("--new-password", help="New password")
1105
+ rekey_parser.add_argument("--old-key-file", help="Old password file")
1106
+ rekey_parser.add_argument("--new-key-file", help="New password file")
1107
+
1108
+ # Batch Export
1109
+ batch_export_parser = subparsers.add_parser("batch-export", help="Batch export multiple sites")
1110
+ batch_export_parser.add_argument("--site-config", "-s", required=True, help="Site config JSON file")
1111
+ batch_export_parser.add_argument("--browser", "-b", choices=["chrome", "firefox", "edge", "brave"],
1112
+ default="firefox", help="Browser name")
1113
+ batch_export_parser.add_argument("--browser-path", help="Custom browser profile path")
1114
+ batch_export_parser.add_argument("--profile", "-p", help="Profile name")
1115
+ batch_export_parser.add_argument("--output", "-o", help="Output directory")
1116
+ batch_export_parser.add_argument("--extract-local-storage", action="store_true", help="Extract localStorage")
1117
+
1118
+ # Batch Load
1119
+ batch_load_parser = subparsers.add_parser("batch-load", help="Batch load multiple sessions")
1120
+ batch_load_parser.add_argument("--sessions-dir", "-d", required=True, help="Sessions directory")
1121
+ batch_load_parser.add_argument("--target-browser", "-t", choices=["chrome", "firefox", "edge", "brave"],
1122
+ default="chrome", help="Target browser")
1123
+ batch_load_parser.add_argument("--site-config", "-s", help="Site config JSON file for validation")
1124
+ batch_load_parser.add_argument("--profile-dir", help="Target profile directory")
1125
+ batch_load_parser.add_argument("--validate", action="store_true", help="Validate sessions")
1126
+ batch_load_parser.add_argument("--visible", action="store_true", help="Show browser window")
1127
+
1128
+ # Health Check
1129
+ health_parser = subparsers.add_parser("health", help="Check session health")
1130
+ health_parser.add_argument("--session", "-s", help="Single session file to check")
1131
+ health_parser.add_argument("--sessions-dir", "-d", help="Directory of sessions to check")
1132
+
1133
+ # Health Report
1134
+ health_report_parser = subparsers.add_parser("health-report", help="Batch health report for CI/CD")
1135
+ health_report_parser.add_argument("--session", "-s", help="Single session file to check")
1136
+ health_report_parser.add_argument("--sessions-dir", "-d", help="Directory of sessions to check")
1137
+ health_report_parser.add_argument("--json", dest="json_output", action="store_true", help="Output as JSON")
1138
+ health_report_parser.add_argument("--min-health", type=float, default=0.5, help="Minimum health score (default: 0.5)")
1139
+ health_report_parser.add_argument("--max-expired", type=int, default=0, help="Max allowed expired cookies (default: 0)")
1140
+ health_report_parser.add_argument("--webhook", help="Send report to webhook URL")
1141
+
1142
+ # Refresh
1143
+ refresh_parser = subparsers.add_parser("refresh", help="Refresh session from source browser")
1144
+ refresh_parser.add_argument("--session", "-s", required=True, help="Session file to refresh")
1145
+ refresh_parser.add_argument("--source-browser", "-b", choices=["chrome", "firefox", "edge", "brave"],
1146
+ required=True, help="Source browser name")
1147
+ refresh_parser.add_argument("--source-browser-path", help="Custom source browser profile path")
1148
+ refresh_parser.add_argument("--source-profile", help="Source profile name")
1149
+ refresh_parser.add_argument("--site-config", help="Site config JSON file for filtering")
1150
+
1151
+ # Proxy
1152
+ proxy_parser = subparsers.add_parser("proxy", help="Start fingerprint-matched proxy server")
1153
+ proxy_parser.add_argument("--session", "-s", help="Path to .tokenade session file (single mode)")
1154
+ proxy_parser.add_argument("--all", action="store_true", help="Serve all sessions (multi-site mode)")
1155
+ proxy_parser.add_argument("--sessions-dir", "-d", help="Directory of .tokenade files (for --all)")
1156
+ proxy_parser.add_argument("--decrypt-password", help="Decrypt .tokenade file with this password")
1157
+ proxy_parser.add_argument("--mode", choices=["gui", "forward"], default="gui",
1158
+ help="Proxy mode: gui (browser GUI) or forward (HTTP_PROXY)")
1159
+ proxy_parser.add_argument("--port", "-p", type=int, default=9222, help="Port to listen on (default: 9222)")
1160
+ proxy_parser.add_argument("--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)")
1161
+ proxy_parser.add_argument("--legacy", action="store_true", help="Use legacy service-worker proxy (default: CDP)")
1162
+ proxy_parser.add_argument("--visible", action="store_true", help="Show browser window (CDP mode only)")
1163
+ proxy_parser.add_argument("--fingerprint", action="store_true", help="Enable TLS fingerprint matching via curl-cffi (breaks cf_clearance)")
1164
+ proxy_parser.add_argument("--impersonate", help="Browser to impersonate (e.g., chrome120, chrome131, firefox128, safari17_0)")
1165
+ proxy_parser.add_argument("--no-open-browser", action="store_true", help="Don't open browser automatically")
1166
+ proxy_parser.add_argument("--no-gui", action="store_true", help="Disable GUI mode (legacy proxy only)")
1167
+ proxy_parser.add_argument("--timeout", type=int, default=30, help="Request timeout in seconds (default: 30)")
1168
+ proxy_parser.add_argument("--auto-refresh", action="store_true", help="Auto-refresh session from source browser when cookies expire")
1169
+ proxy_parser.add_argument("--source-browser", help="Source browser for auto-refresh (e.g., firefox, chrome)")
1170
+ proxy_parser.add_argument("--source-profile", help="Source profile for auto-refresh (e.g., default, Profile 1)")
1171
+ proxy_parser.add_argument("--auto-navigate", action="store_true", help="Auto-navigate to site URL when proxy starts")
1172
+ proxy_parser.add_argument("--target-url", help="Override default navigation URL")
1173
+ proxy_parser.add_argument("--rotate", action="store_true", help="Enable session rotation across multiple sessions")
1174
+ proxy_parser.add_argument("--rotate-strategy", choices=["health-weighted", "round-robin", "random", "least-recently-used"],
1175
+ default="health-weighted", help="Rotation strategy (default: health-weighted)")
1176
+ proxy_parser.add_argument("--rotate-interval", type=int, default=300, help="Rotation interval in seconds (default: 300)")
1177
+
1178
+ # Sessions (subcommand group)
1179
+ sessions_parser = subparsers.add_parser("sessions", help="Manage multiple sessions")
1180
+ sessions_sub = sessions_parser.add_subparsers(dest="sessions_command", help="Session management commands")
1181
+
1182
+ # sessions list
1183
+ sessions_list_parser = sessions_sub.add_parser("list", help="List all sessions")
1184
+ sessions_list_parser.add_argument("--dir", "-d", default=".", help="Directory to search")
1185
+ sessions_list_parser.add_argument("--pattern", "-p", default="*.tokenade", help="File pattern")
1186
+ sessions_list_parser.add_argument("--recursive", "-r", action="store_true", help="Search subdirectories")
1187
+ sessions_list_parser.add_argument("--site", "-s", help="Filter by site name")
1188
+ sessions_list_parser.add_argument("--browser", "-b", help="Filter by source browser")
1189
+
1190
+ # sessions merge
1191
+ sessions_merge_parser = sessions_sub.add_parser("merge", help="Merge multiple sessions")
1192
+ sessions_merge_parser.add_argument("files", nargs="+", help="Session files to merge")
1193
+ sessions_merge_parser.add_argument("--output", "-o", required=True, help="Output file path")
1194
+ sessions_merge_parser.add_argument("--site-name", help="Site name for merged session")
1195
+
1196
+ # sessions rotate
1197
+ sessions_rotate_parser = sessions_sub.add_parser("rotate", help="Select next session (rotation)")
1198
+ sessions_rotate_parser.add_argument("files", nargs="+", help="Session files to rotate through")
1199
+ sessions_rotate_parser.add_argument("--strategy", choices=["round-robin", "random"], default="round-robin",
1200
+ help="Rotation strategy")
1201
+ sessions_rotate_parser.add_argument("--state-file", help="State file for round-robin")
1202
+
1203
+ # sessions stats
1204
+ sessions_stats_parser = sessions_sub.add_parser("stats", help="Show aggregate session statistics")
1205
+ sessions_stats_parser.add_argument("files", nargs="+", help="Session files to analyze")
1206
+
1207
+ # Share
1208
+ share_parser = subparsers.add_parser("share", help="Create shareable session link or QR code")
1209
+ share_parser.add_argument("--session", "-s", required=True, help="Session file to share")
1210
+ share_parser.add_argument("--output", "-o", help="Output file path (HTML or QR image)")
1211
+ share_parser.add_argument("--format", choices=["url", "html", "qr"], default="url",
1212
+ help="Output format: url (default), html, qr")
1213
+ share_parser.add_argument("--expiry", type=int, default=24, help="Link expiry in hours (default: 24)")
1214
+ share_parser.add_argument("--max-uses", type=int, default=0, help="Max uses (0 = unlimited)")
1215
+ share_parser.add_argument("--password", "-p", help="Password protect the link")
1216
+ share_parser.add_argument("--email-to", help="Comma-separated email recipients")
1217
+ share_parser.add_argument("--smtp-host", help="SMTP server host")
1218
+ share_parser.add_argument("--smtp-port", type=int, default=587, help="SMTP server port")
1219
+ share_parser.add_argument("--smtp-user", help="SMTP username")
1220
+ share_parser.add_argument("--smtp-password", help="SMTP password")
1221
+ share_parser.add_argument("--webhook-url", help="Webhook URL to notify")
1222
+
1223
+ # Unshare
1224
+ unshare_parser = subparsers.add_parser("unshare", help="Revoke a shared session")
1225
+ unshare_parser.add_argument("session_id", help="Session ID to revoke")
1226
+ unshare_parser.add_argument("--list", action="store_true", help="List all active shares")
1227
+
1228
+ # Import shared session
1229
+ import_parser = subparsers.add_parser("import", help="Import a shared session from URL")
1230
+ import_parser.add_argument("url", help="tokenade://share/ URL")
1231
+ import_parser.add_argument("--password", "-p", help="Decryption password")
1232
+ import_parser.add_argument("--output", "-o", help="Output file path")
1233
+
1234
+ # Validate Rules
1235
+ validate_rules_parser = subparsers.add_parser("validate-rules", help="Validate session with custom rules")
1236
+ validate_rules_parser.add_argument("--session", "-s", required=True, help="Session file to validate")
1237
+ validate_rules_parser.add_argument("--rules", "-r", required=True, help="Validation rules JSON file")
1238
+ validate_rules_parser.add_argument("--url", "-u", help="Target site URL")
1239
+ validate_rules_parser.add_argument("--update-baselines", action="store_true", help="Update screenshot baselines")
1240
+
1241
+ # Diff
1242
+ diff_parser = subparsers.add_parser("diff", help="Compare two session files")
1243
+ diff_parser.add_argument("session_a", help="First .tokenade file")
1244
+ diff_parser.add_argument("session_b", help="Second .tokenade file")
1245
+ diff_parser.add_argument("--verbose", "-v", action="store_true", help="Show detailed differences")
1246
+
1247
+ # Plugin
1248
+ plugin_parser = subparsers.add_parser("plugin", help="Manage plugins")
1249
+ plugin_sub = plugin_parser.add_subparsers(dest="plugin_command", help="Plugin commands")
1250
+
1251
+ plugin_list_parser = plugin_sub.add_parser("list", help="List installed plugins")
1252
+ plugin_list_parser.add_argument("--available", action="store_true", help="Show available plugins from registry")
1253
+
1254
+ plugin_install_parser = plugin_sub.add_parser("install", help="Install a plugin")
1255
+ plugin_install_parser.add_argument("name", help="Plugin name to install")
1256
+
1257
+ plugin_uninstall_parser = plugin_sub.add_parser("uninstall", help="Uninstall a plugin")
1258
+ plugin_uninstall_parser.add_argument("name", help="Plugin name to uninstall")
1259
+
1260
+ plugin_info_parser = plugin_sub.add_parser("info", help="Show plugin details")
1261
+ plugin_info_parser.add_argument("name", help="Plugin name")
1262
+
1263
+ plugin_enable_parser = plugin_sub.add_parser("enable", help="Enable a disabled plugin")
1264
+ plugin_enable_parser.add_argument("name", help="Plugin name to enable")
1265
+
1266
+ plugin_disable_parser = plugin_sub.add_parser("disable", help="Disable a plugin without uninstalling")
1267
+ plugin_disable_parser.add_argument("name", help="Plugin name to disable")
1268
+
1269
+ plugin_update_parser = plugin_sub.add_parser("update", help="Update plugins from registry")
1270
+ plugin_update_parser.add_argument("name", nargs="?", default=None, help="Plugin name to update (all if omitted)")
1271
+
1272
+ plugin_sync_parser = plugin_sub.add_parser("sync", help="Install all available plugins from registry")
1273
+ plugin_sync_parser.add_argument("--force", action="store_true", help="Reinstall even if already installed")
1274
+
1275
+ plugin_reload_parser = plugin_sub.add_parser("reload", help="Reload a plugin")
1276
+ plugin_reload_parser.add_argument("name", help="Plugin name to reload")
1277
+
1278
+ # plugin search
1279
+ plugin_search_parser = plugin_sub.add_parser("search", help="Search plugins in marketplace")
1280
+ plugin_search_parser.add_argument("query", nargs="?", default="", help="Search query")
1281
+ plugin_search_parser.add_argument("--type", dest="plugin_type", help="Filter by plugin type")
1282
+ plugin_search_parser.add_argument("--category", help="Filter by category")
1283
+ plugin_search_parser.add_argument("--tags", help="Comma-separated tags to filter")
1284
+ plugin_search_parser.add_argument(
1285
+ "--sort",
1286
+ choices=["rating", "downloads", "name", "recent", "trending"],
1287
+ default="name",
1288
+ help="Sort order (default: name — registry has no fake download/rating metrics)",
1289
+ )
1290
+
1291
+ # plugin categories
1292
+ plugin_sub.add_parser("categories", help="List plugin categories")
1293
+
1294
+ # plugin popular
1295
+ plugin_popular_parser = plugin_sub.add_parser("popular", help="Show most popular plugins")
1296
+ plugin_popular_parser.add_argument("--limit", "-n", type=int, default=10, help="Number of plugins to show")
1297
+
1298
+ # plugin recent
1299
+ plugin_recent_parser = plugin_sub.add_parser("recent", help="Show newest plugins")
1300
+ plugin_recent_parser.add_argument("--limit", "-n", type=int, default=10, help="Number of plugins to show")
1301
+
1302
+ # plugin rate
1303
+ plugin_rate_parser = plugin_sub.add_parser("rate", help="Rate a plugin (1-5 stars)")
1304
+ plugin_rate_parser.add_argument("name", help="Plugin name")
1305
+ plugin_rate_parser.add_argument("rating", type=float, help="Rating (1.0-5.0)")
1306
+ plugin_rate_parser.add_argument("--review", help="Written review")
1307
+
1308
+ # plugin ratings
1309
+ plugin_ratings_parser = plugin_sub.add_parser("ratings", help="View global ratings from GitHub")
1310
+ plugin_ratings_parser.add_argument("name", nargs="?", help="Plugin name (all if omitted)")
1311
+
1312
+ # plugin verify
1313
+ plugin_verify_parser = plugin_sub.add_parser("verify", help="Verify plugin integrity via checksums")
1314
+ plugin_verify_parser.add_argument("name", nargs="?", help="Plugin name (all if omitted)")
1315
+
1316
+ # plugin outdated
1317
+ plugin_sub.add_parser("outdated", help="Show plugins with available updates")
1318
+
1319
+ # plugin browse
1320
+ plugin_browse_parser = plugin_sub.add_parser("browse", help="Generate static HTML marketplace page")
1321
+ plugin_browse_parser.add_argument("--output", "-o", help="Output HTML file path")
1322
+
1323
+ # plugin test
1324
+ plugin_test_parser = plugin_sub.add_parser("test", help="Run tests on installed plugins")
1325
+ plugin_test_parser.add_argument("name", nargs="?", help="Plugin name (all if omitted)")
1326
+
1327
+ # Sync
1328
+ sync_parser = subparsers.add_parser("sync", help="Sync sessions from browser cookies")
1329
+ sync_sub = sync_parser.add_subparsers(dest="sync_command", help="Sync commands")
1330
+
1331
+ sync_add_parser = sync_sub.add_parser("add", help="Add a sync target")
1332
+ sync_add_parser.add_argument("--name", "-n", required=True, help="Target name (e.g., gmail)")
1333
+ sync_add_parser.add_argument("--domains", "-d", required=True, help="Comma-separated domains")
1334
+ sync_add_parser.add_argument("--browser", "-b", default="firefox", help="Browser (default: firefox)")
1335
+ sync_add_parser.add_argument("--profile", "-p", help="Browser profile name")
1336
+ sync_add_parser.add_argument("--output-dir", "-o", help="Output directory (default: ~/.tokenade/synced)")
1337
+
1338
+ sync_remove_parser = sync_sub.add_parser("remove", help="Remove a sync target")
1339
+ sync_remove_parser.add_argument("--name", "-n", required=True, help="Target name")
1340
+
1341
+ sync_sub.add_parser("list", help="List sync targets")
1342
+ sync_sub.add_parser("once", help="Run one-time sync")
1343
+
1344
+ sync_start_parser = sync_sub.add_parser("start", help="Start sync daemon")
1345
+ sync_start_parser.add_argument("--interval", "-i", type=int, default=60, help="Check interval in seconds")
1346
+
1347
+ # Monitor
1348
+ monitor_parser = subparsers.add_parser("monitor", help="Monitor session health in real-time")
1349
+ monitor_sub = monitor_parser.add_subparsers(dest="monitor_command", help="Monitor commands")
1350
+
1351
+ monitor_status_parser = monitor_sub.add_parser("status", help="Show monitoring status")
1352
+ monitor_status_parser.add_argument("--sessions-dir", "-d", help="Directory of sessions to check")
1353
+ monitor_status_parser.add_argument("--session", "-s", help="Single session file to check")
1354
+
1355
+ monitor_start_parser = monitor_sub.add_parser("start", help="Start background monitoring")
1356
+ monitor_start_parser.add_argument("--sessions-dir", "-d", help="Directory of sessions to monitor")
1357
+ monitor_start_parser.add_argument("--session", "-s", help="Single session file to monitor")
1358
+ monitor_start_parser.add_argument("--interval", "-i", type=int, default=60, help="Check interval in seconds")
1359
+ monitor_start_parser.add_argument("--auto-refresh", action="store_true", help="Auto-refresh expiring sessions")
1360
+
1361
+ monitor_stop_parser = monitor_sub.add_parser("stop", help="Stop background monitoring")
1362
+
1363
+ monitor_history_parser = monitor_sub.add_parser("history", help="Show monitor event history")
1364
+ monitor_history_parser.add_argument("--sessions-dir", "-d", help="Sessions directory")
1365
+ monitor_history_parser.add_argument("--limit", "-l", type=int, default=50, help="Max events to show")
1366
+
1367
+ monitor_predict_parser = monitor_sub.add_parser("predict", help="Predict session expiry")
1368
+ monitor_predict_parser.add_argument("--sessions-dir", "-d", help="Sessions directory")
1369
+ monitor_predict_parser.add_argument("--session", "-s", help="Single session file")
1370
+
1371
+ # Analytics
1372
+ analytics_parser = subparsers.add_parser("analytics", help="Session usage analytics")
1373
+ analytics_sub = analytics_parser.add_subparsers(dest="analytics_command", help="Analytics commands")
1374
+
1375
+ analytics_report_parser = analytics_sub.add_parser("report", help="Show usage report")
1376
+ analytics_report_parser.add_argument("--days", "-d", type=int, default=30, help="Report period in days")
1377
+ analytics_report_parser.add_argument("--json", dest="json_output", action="store_true", help="Output as JSON")
1378
+
1379
+ analytics_session_parser = analytics_sub.add_parser("session", help="Show analytics for a session")
1380
+ analytics_session_parser.add_argument("session_id", help="Session ID to analyze")
1381
+
1382
+ analytics_cleanup_parser = analytics_sub.add_parser("cleanup", help="Remove old analytics data")
1383
+ analytics_cleanup_parser.add_argument("--max-age", type=int, default=90, help="Max age in days")
1384
+
1385
+ # Shell Completion
1386
+ completion_parser = subparsers.add_parser("completion", help="Generate shell completion scripts")
1387
+ completion_parser.add_argument("shell", choices=["bash", "zsh", "fish"], help="Shell type")
1388
+
1389
+ # OAuth Refresh
1390
+ refresh_oauth_parser = subparsers.add_parser("refresh-oauth", help="Refresh OAuth tokens using stored refresh token")
1391
+ refresh_oauth_parser.add_argument("--session", "-s", required=True, help="Session file to refresh")
1392
+
1393
+ # OAuth Config
1394
+ oauth_config_parser = subparsers.add_parser("oauth-config", help="Configure OAuth settings for a session")
1395
+ oauth_config_parser.add_argument("--session", "-s", required=True, help="Session file to configure")
1396
+ oauth_config_parser.add_argument("--client-id", help="OAuth client ID")
1397
+ oauth_config_parser.add_argument("--client-secret", help="OAuth client secret")
1398
+ oauth_config_parser.add_argument("--token-endpoint", help="OAuth token endpoint URL")
1399
+ oauth_config_parser.add_argument("--scopes", help="Comma-separated OAuth scopes")
1400
+ oauth_config_parser.add_argument("--show", action="store_true", help="Show current OAuth config")
1401
+
1402
+ # Batch Refresh
1403
+ batch_refresh_parser = subparsers.add_parser("batch-refresh", help="Refresh multiple sessions with rate limiting")
1404
+ batch_refresh_parser.add_argument("--sessions-dir", "-d", default="sessions", help="Sessions directory")
1405
+ batch_refresh_parser.add_argument("--max-workers", "-w", type=int, default=3, help="Max parallel workers")
1406
+ batch_refresh_parser.add_argument("--delay", type=float, default=1.0, help="Delay between refreshes (seconds)")
1407
+ batch_refresh_parser.add_argument("--source-browser", "-b", default="firefox", help="Source browser for cookie refresh")
1408
+ batch_refresh_parser.add_argument("--source-profile", "-p", help="Source profile name")
1409
+ batch_refresh_parser.add_argument("--force", action="store_true", help="Force refresh even if not expired")
1410
+ batch_refresh_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation prompt")
1411
+
1412
+ # CI/CD
1413
+ cicd_parser = subparsers.add_parser("cicd", help="Generate CI/CD workflow files")
1414
+ cicd_parser.add_argument("--generate-all", action="store_true", help="Generate all workflow files")
1415
+ cicd_parser.add_argument("--workflow-type", choices=["github", "gitlab", "cron"], default="github", help="Workflow type")
1416
+ cicd_parser.add_argument("--sessions-dir", default="sessions", help="Sessions directory")
1417
+ cicd_parser.add_argument("--interval-hours", type=int, default=6, help="Refresh interval in hours")
1418
+ cicd_parser.add_argument("--source-browser", default="firefox", help="Source browser for cookie refresh")
1419
+ cicd_parser.add_argument("--output", "-o", help="Output file path")
1420
+ cicd_parser.add_argument("--output-dir", default=".tokenade/ci", help="Output directory for --generate-all")
1421
+
1422
+ # CI Runner
1423
+ ci_parser = subparsers.add_parser("ci", help="Session CI runner")
1424
+ ci_sub = ci_parser.add_subparsers(dest="ci_action")
1425
+
1426
+ ci_run = ci_sub.add_parser("run", help="Run CI pipeline from tokenade.yml")
1427
+ ci_run.add_argument("--config", default="tokenade.yml", help="Config file path")
1428
+ ci_run.add_argument("--format", choices=["text", "json", "junit"], help="Override output format")
1429
+
1430
+ ci_init = ci_sub.add_parser("init", help="Create a starter tokenade.yml")
1431
+ ci_init.add_argument("--config", default="tokenade.yml", help="Config file path")
1432
+
1433
+ ci_validate = ci_sub.add_parser("validate", help="Validate tokenade.yml schema")
1434
+ ci_validate.add_argument("--config", default="tokenade.yml", help="Config file path")
1435
+
1436
+ ci_lint = ci_sub.add_parser("lint", help="Lint tokenade.yml for common issues")
1437
+ ci_lint.add_argument("--config", default="tokenade.yml", help="Config file path")
1438
+
1439
+ # Fleet Management
1440
+ fleet_parser = subparsers.add_parser("fleet", help="Fleet status across containers/pods")
1441
+ fleet_sub = fleet_parser.add_subparsers(dest="fleet_action")
1442
+
1443
+ fleet_status = fleet_sub.add_parser("status", help="Show all sessions across containers")
1444
+ fleet_status.add_argument("--format", choices=["text", "json"], default="text", help="Output format")
1445
+
1446
+ fleet_health = fleet_sub.add_parser("health", help="Run health checks across the fleet")
1447
+ fleet_health.add_argument("--format", choices=["text", "json"], default="text", help="Output format")
1448
+
1449
+ fleet_sub.add_parser("refresh", help="Trigger refresh in all running containers")
1450
+
1451
+ fleet_logs = fleet_sub.add_parser("logs", help="Get logs from a container")
1452
+ fleet_logs.add_argument("container", nargs="?", help="Container name")
1453
+ fleet_logs.add_argument("--lines", "-n", type=int, default=50, help="Number of log lines")
1454
+
1455
+ # Autopsy (Forensics)
1456
+ autopsy_parser = subparsers.add_parser("autopsy", help="Analyze why a session died")
1457
+ autopsy_parser.add_argument("--session", "-s", required=True, help="Session file to analyze")
1458
+ autopsy_parser.add_argument("--compare", "-c", help="Compare with another session file")
1459
+ autopsy_parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format")
1460
+
1461
+ # TUI
1462
+ tui_parser = subparsers.add_parser("tui", help="Launch interactive terminal UI")
1463
+ tui_parser.add_argument("tui_mode", nargs="?", default="full",
1464
+ choices=["full", "marketplace", "sessions"],
1465
+ help="TUI mode")
1466
+
1467
+ # Validate Session
1468
+ validate_session_parser = subparsers.add_parser("validate-session", help="Validate session files for CI/CD")
1469
+ validate_session_parser.add_argument("--session", "-s", help="Single session file to validate")
1470
+ validate_session_parser.add_argument("--sessions-dir", "-d", help="Directory of sessions to validate")
1471
+ validate_session_parser.add_argument("--min-health", type=float, default=0.5, help="Minimum health score (0.0-1.0)")
1472
+ validate_session_parser.add_argument("--max-expired", type=int, default=0, help="Max allowed expired cookies")
1473
+ validate_session_parser.add_argument("--require-oauth", action="store_true", help="Require OAuth config")
1474
+ validate_session_parser.add_argument("--max-age-hours", type=float, help="Max session age in hours")
1475
+ validate_session_parser.add_argument("--json", dest="json_output", action="store_true", help="Output as JSON")
1476
+
1477
+ # Encrypted Refresh
1478
+ encrypted_refresh_parser = subparsers.add_parser("encrypted-refresh", help="Refresh encrypted session files")
1479
+ encrypted_refresh_parser.add_argument("--session", "-s", help="Single session file to refresh")
1480
+ encrypted_refresh_parser.add_argument("--sessions-dir", "-d", help="Directory of sessions to refresh")
1481
+ encrypted_refresh_parser.add_argument("--password", "-p", help="Decryption password")
1482
+ encrypted_refresh_parser.add_argument("--key-file", "-k", help="Key file path")
1483
+ encrypted_refresh_parser.add_argument("--source-browser", "-b", default="firefox", help="Source browser for cookie refresh")
1484
+ encrypted_refresh_parser.add_argument("--force", action="store_true", help="Force refresh even if not expired")
1485
+
1486
+ # CloakBrowser
1487
+ cloak_parser = subparsers.add_parser("cloak", help="CloakBrowser stealth browser management")
1488
+ cloak_sub = cloak_parser.add_subparsers(dest="cloak_action")
1489
+
1490
+ cloak_info = cloak_sub.add_parser("info", help="Show CloakBrowser status and binary info")
1491
+ cloak_info.add_argument("--json", dest="json_output", action="store_true", help="Output as JSON")
1492
+ cloak_sub.add_parser("install", help="Download/update CloakBrowser binary")
1493
+
1494
+ cloak_serve = cloak_sub.add_parser("serve", help="Start CDP server (cloakserve)")
1495
+ cloak_serve.add_argument("--port", "-p", type=int, default=9222, help="Port to listen on")
1496
+ cloak_serve.add_argument("--proxy", help="Upstream proxy URL")
1497
+ cloak_serve.add_argument("--headless", action="store_true", default=True, help="Run headless")
1498
+ cloak_serve.add_argument("--visible", action="store_true", help="Run headed")
1499
+ cloak_serve.add_argument("--idle-timeout", type=int, help="Idle timeout in seconds")
1500
+
1501
+ # Launch Undetectable Browser
1502
+ launch_parser = subparsers.add_parser("launch", help="Launch undetectable system browser with CDP")
1503
+ launch_parser.add_argument("--browser", "-b", default="chrome", help="Browser to launch (chrome, firefox, brave, edge)")
1504
+ launch_parser.add_argument("--session", "-s", help="Session file to inject cookies from")
1505
+ launch_parser.add_argument("--url", "-u", help="URL to navigate to after injection")
1506
+ launch_parser.add_argument("--port", "-p", type=int, default=9222, help="CDP debugging port")
1507
+ launch_parser.add_argument("--profile-dir", help="Custom profile directory")
1508
+ launch_parser.add_argument("--visible", action="store_true", help="Show browser window (default unless --headless)")
1509
+ launch_parser.add_argument("--headless", action="store_true", help="Run headless (no window)")
1510
+ launch_parser.add_argument("--extra-args", help="Extra browser args (comma-separated)")
1511
+ launch_parser.add_argument("--browser-path", help="Path to browser executable")
1512
+ launch_parser.add_argument("--proxy", help="Upstream proxy URL (e.g. socks5://user:pass@host:port)")
1513
+ launch_parser.add_argument("--proxy-file", help="Proxy list file for rotation (one proxy per line)")
1514
+ launch_parser.add_argument("--proxy-rotate", action="store_true", help="Enable proxy rotation")
1515
+ launch_parser.add_argument(
1516
+ "--proxy-strategy", choices=["round-robin", "random", "health-weighted", "sticky"],
1517
+ default="health-weighted", help="Rotation strategy (default: health-weighted)"
1518
+ )
1519
+ launch_parser.add_argument("--humanize", action="store_true", help="Human-like mouse/keyboard/scroll (CloakBrowser)")
1520
+ launch_parser.add_argument("--geoip", action="store_true", help="Auto-detect timezone/locale from proxy IP (CloakBrowser)")
1521
+ launch_parser.add_argument("--no-cloak", action="store_true", help="Force Playwright + JS patches (skip CloakBrowser)")
1522
+ launch_parser.add_argument("--profile", help="Persistent profile directory (CloakBrowser)")
1523
+ launch_parser.add_argument("--decrypt-password", help="Decrypt .tokenade file with this password")
1524
+ launch_parser.add_argument(
1525
+ "--plugin",
1526
+ help="Force site handler plugin for launch (e.g. google-handler); auto-discovers when omitted",
1527
+ )
1528
+
1529
+ # Refresh Browser (cookie-based session refresh)
1530
+ refresh_browser_parser = subparsers.add_parser("refresh-browser", help="Refresh session via undetectable browser (no OAuth needed)")
1531
+ refresh_browser_parser.add_argument("--session", "-s", required=True, help="Session file to refresh")
1532
+ refresh_browser_parser.add_argument("--browser", "-b", default="chrome", help="Browser to use (chrome, firefox, brave, edge)")
1533
+ refresh_browser_parser.add_argument("--url", "-u", help="Target URL (auto-detected from cookies if not specified)")
1534
+ refresh_browser_parser.add_argument("--port", "-p", type=int, default=9222, help="CDP debugging port")
1535
+ refresh_browser_parser.add_argument("--headless", action="store_true", help="Run headless (no window)")
1536
+ refresh_browser_parser.add_argument("--wait", "-w", type=int, default=8, help="Seconds to wait for session refresh (default: 8)")
1537
+ refresh_browser_parser.add_argument("--output", "-o", help="Output file (default: overwrite original)")
1538
+ refresh_browser_parser.add_argument("--plugin", help="Plugin to use for refresh (e.g., oauth2)")
1539
+ refresh_browser_parser.add_argument(
1540
+ "--plugin-arg", action="append", default=[], nargs=2, metavar=("KEY", "VALUE"),
1541
+ help="Plugin credential (repeatable): --plugin-arg client_id XXX"
1542
+ )
1543
+ refresh_browser_parser.add_argument("--proxy", help="Upstream proxy URL (e.g. socks5://user:pass@host:port)")
1544
+ refresh_browser_parser.add_argument("--proxy-file", help="Proxy list file for rotation (one proxy per line)")
1545
+ refresh_browser_parser.add_argument("--proxy-rotate", action="store_true", help="Enable proxy rotation")
1546
+ refresh_browser_parser.add_argument(
1547
+ "--proxy-strategy", choices=["round-robin", "random", "health-weighted", "sticky"],
1548
+ default="health-weighted", help="Rotation strategy (default: health-weighted)"
1549
+ )
1550
+
1551
+ # Multi-Account Orchestration
1552
+ accounts_parser = subparsers.add_parser("accounts", help="Multi-account orchestration (list/status/refresh)")
1553
+ accounts_subparsers = accounts_parser.add_subparsers(dest="accounts_action")
1554
+
1555
+ # accounts list
1556
+ accounts_list = accounts_subparsers.add_parser("list", help="List all sessions")
1557
+ accounts_list.add_argument("--sessions-dir", "-d", default=".", help="Directory to scan (default: current)")
1558
+ accounts_list.add_argument("--site", "-s", help="Filter by site name")
1559
+ accounts_list.add_argument("--browser", "-b", help="Filter by browser")
1560
+
1561
+ # accounts status
1562
+ accounts_status = accounts_subparsers.add_parser("status", help="Show health/status of all sessions")
1563
+ accounts_status.add_argument("--sessions-dir", "-d", default=".", help="Directory to scan (default: current)")
1564
+ accounts_status.add_argument("--site", "-s", help="Filter by site name")
1565
+
1566
+ # accounts refresh
1567
+ accounts_refresh = accounts_subparsers.add_parser("refresh", help="Refresh all/specific sessions")
1568
+ accounts_refresh.add_argument("--sessions-dir", "-d", default=".", help="Directory to scan (default: current)")
1569
+ accounts_refresh.add_argument("--site", "-s", help="Filter by site name")
1570
+ accounts_refresh.add_argument("--browser", "-b", default="chrome", help="Browser to use for refresh")
1571
+ accounts_refresh.add_argument("--files", nargs="*", help="Specific session files to refresh")
1572
+ accounts_refresh.add_argument("--port", "-p", type=int, default=9222, help="Starting CDP port")
1573
+ accounts_refresh.add_argument("--visible", action="store_true", help="Show browser window")
1574
+ accounts_refresh.add_argument("--headless", action="store_true", default=True, help="Run headless (default)")
1575
+ accounts_refresh.add_argument("--wait", "-w", type=int, default=8, help="Seconds to wait per session")
1576
+ accounts_refresh.add_argument("--yes", "-y", action="store_true", help="Skip confirmation")
1577
+ accounts_refresh.add_argument("--plugin", help="Plugin to use for refresh (e.g., oauth2)")
1578
+ accounts_refresh.add_argument("--plugin-arg", action="append", default=[], nargs=2, metavar=("KEY", "VALUE"),
1579
+ help="Plugin credential (repeatable): --plugin-arg client_id XXX")
1580
+ accounts_refresh.add_argument("--proxy", help="Upstream proxy URL (e.g. socks5://user:pass@host:port)")
1581
+ accounts_refresh.add_argument("--proxy-file", help="Proxy list file for rotation (one proxy per line)")
1582
+ accounts_refresh.add_argument("--proxy-rotate", action="store_true", help="Enable proxy rotation")
1583
+ accounts_refresh.add_argument("--proxy-strategy", choices=["round-robin", "random", "health-weighted", "sticky"],
1584
+ default="health-weighted", help="Rotation strategy (default: health-weighted)")
1585
+
1586
+ # Patch Chrome Binary (remove cdc_ artifacts)
1587
+ patch_parser = subparsers.add_parser("patch-chrome", help="Patch Chrome/Chromium binary to remove cdc_ artifacts")
1588
+ patch_subparsers = patch_parser.add_subparsers(dest="patch_action")
1589
+
1590
+ # patch-chrome scan
1591
+ patch_scan = patch_subparsers.add_parser("scan", help="Scan binary for cdc_ artifacts")
1592
+ patch_scan.add_argument("--browser", "-b", default="chrome", help="Browser to scan (chrome, chromium, brave, edge)")
1593
+ patch_scan.add_argument("--binary", help="Path to browser binary (auto-detected if omitted)")
1594
+
1595
+ # patch-chrome patch
1596
+ patch_do = patch_subparsers.add_parser("patch", help="Patch binary to remove cdc_ artifacts")
1597
+ patch_do.add_argument("--browser", "-b", default="chrome", help="Browser to patch")
1598
+ patch_do.add_argument("--binary", help="Path to browser binary")
1599
+ patch_do.add_argument("--output", "-o", help="Output path for patched binary (default: <binary>.patched)")
1600
+
1601
+ # patch-chrome restore
1602
+ patch_restore = patch_subparsers.add_parser("restore", help="Restore binary from backup")
1603
+ patch_restore.add_argument("--browser", "-b", default="chrome", help="Browser to restore")
1604
+ patch_restore.add_argument("--binary", help="Path to browser binary")
1605
+
1606
+ # patch-chrome verify
1607
+ patch_verify = patch_subparsers.add_parser("verify", help="Verify binary patch status")
1608
+ patch_verify.add_argument("--browser", "-b", default="chrome", help="Browser to verify")
1609
+ patch_verify.add_argument("--binary", help="Path to browser binary")
1610
+
1611
+ # ── Mobile Import ────────────────────────────────────────────
1612
+ mobile_import_parser = subparsers.add_parser("mobile-import", help="Import sessions from mobile devices (Android/iOS)")
1613
+ mobile_import_parser.add_argument("--auto", action="store_true", help="Auto-detect device and browser")
1614
+ mobile_import_parser.add_argument("--list-devices", action="store_true", help="List connected mobile devices")
1615
+ mobile_import_parser.add_argument("--device", "-d", help="Device serial number")
1616
+ mobile_import_parser.add_argument("--browser", "-b", default="auto", help="Browser to extract from (chrome/firefox/samsung/brave/edge/safari, default: auto)")
1617
+ mobile_import_parser.add_argument("--domains", help="Comma-separated domains to filter")
1618
+ mobile_import_parser.add_argument("--output", "-o", help="Output .tokenade file path")
1619
+ mobile_import_parser.add_argument("--site-name", help="Site name override")
1620
+ mobile_import_parser.add_argument("--ios", action="store_true", help="Target iOS device (macOS only)")
1621
+
1622
+ # ── Daemon ──────────────────────────────────────────────────
1623
+ daemon_parser = subparsers.add_parser("daemon", help="Auto-refresh daemon (background session refresh)")
1624
+ daemon_subparsers = daemon_parser.add_subparsers(dest="daemon_action")
1625
+
1626
+ # daemon start
1627
+ daemon_start = daemon_subparsers.add_parser("start", help="Start daemon in background")
1628
+ daemon_start.add_argument("--interval", type=float, help="Check interval in minutes (default: 30)")
1629
+ daemon_start.add_argument("--webhook", help="Webhook URL for notifications")
1630
+
1631
+ # daemon stop
1632
+ daemon_subparsers.add_parser("stop", help="Stop the daemon")
1633
+
1634
+ # daemon status
1635
+ daemon_subparsers.add_parser("status", help="Show daemon status")
1636
+
1637
+ # daemon run-once
1638
+ daemon_subparsers.add_parser("run-once", help="Run single refresh cycle (foreground)")
1639
+
1640
+ # daemon add
1641
+ daemon_add = daemon_subparsers.add_parser("add", help="Add session to watch list")
1642
+ daemon_add.add_argument("session", help="Session file to watch")
1643
+ daemon_add.add_argument("--browser", "-b", default="chrome", help="Browser for refresh")
1644
+ daemon_add.add_argument(
1645
+ "--refresh-before", type=float, default=2.0,
1646
+ help="Refresh this many hours before expiry (default: 2.0)"
1647
+ )
1648
+ daemon_add.add_argument("--url", "-u", help="Target URL (auto-detected if not set)")
1649
+ daemon_add.add_argument("--site-name", help="Site name (auto-detected from filename)")
1650
+
1651
+ # daemon remove
1652
+ daemon_remove = daemon_subparsers.add_parser("remove", help="Remove session from watch list")
1653
+ daemon_remove.add_argument("session", help="Session file to remove")
1654
+
1655
+ # daemon list
1656
+ daemon_subparsers.add_parser("list", help="List all watched sessions")
1657
+
1658
+ # daemon logs
1659
+ daemon_logs = daemon_subparsers.add_parser("logs", help="View daemon logs")
1660
+ daemon_logs.add_argument("--lines", "-n", type=int, default=50, help="Number of lines to show")
1661
+ daemon_logs.add_argument("--follow", "-f", action="store_true", help="Follow log output (like tail -f)")
1662
+
1663
+ # ── Versions ────────────────────────────────────────────────
1664
+ versions_parser = subparsers.add_parser("versions", help="Session versioning (list/create/delete)")
1665
+ versions_subparsers = versions_parser.add_subparsers(dest="version_action")
1666
+
1667
+ versions_list = versions_subparsers.add_parser("list", help="List versions for a session")
1668
+ versions_list.add_argument("session", help="Session file")
1669
+
1670
+ versions_create = versions_subparsers.add_parser("create", help="Create a new version")
1671
+ versions_create.add_argument("session", help="Session file")
1672
+ versions_create.add_argument("--description", "-d", help="Version description")
1673
+
1674
+ versions_delete = versions_subparsers.add_parser("delete", help="Delete a version")
1675
+ versions_delete.add_argument("session", help="Session file")
1676
+ versions_delete.add_argument("version", type=int, help="Version number to delete")
1677
+
1678
+ # ── Rollback ────────────────────────────────────────────────
1679
+ rollback_parser = subparsers.add_parser("rollback", help="Rollback session to a specific version")
1680
+ rollback_parser.add_argument("session", help="Session file")
1681
+ rollback_parser.add_argument("version", type=int, help="Version number to restore")
1682
+
1683
+ # ── Session Diff (version comparison) ───────────────────────
1684
+ session_diff_parser = subparsers.add_parser("session-diff", help="Compare two session versions")
1685
+ session_diff_parser.add_argument("session", help="Session file")
1686
+ session_diff_parser.add_argument("version_a", type=int, help="First version number")
1687
+ session_diff_parser.add_argument("version_b", type=int, help="Second version number")
1688
+
1689
+ # ── Logs ────────────────────────────────────────────────────
1690
+ logs_parser = subparsers.add_parser("logs", help="View structured logs")
1691
+ logs_parser.add_argument("--lines", "-n", type=int, default=50, help="Number of recent lines to show (default: 50)")
1692
+ logs_parser.add_argument("--follow", "-f", action="store_true", help="Follow log output (like tail -f)")
1693
+ logs_parser.add_argument("--search", "-s", help="Search for text in logs")
1694
+ logs_parser.add_argument("--json", dest="json_output", action="store_true", help="Output in JSON format")
1695
+ logs_parser.add_argument("--log-file", help="Path to specific log file (default: ~/.tokenade/logs/tokenade.log)")
1696
+ logs_parser.add_argument("--list-files", action="store_true", help="List all log files")
1697
+ logs_parser.add_argument("--cleanup", type=int, metavar="DAYS", help="Remove log files older than N days")
1698
+
1699
+ # ── Clone Profile ──────────────────────────────────────────
1700
+ clone_parser = subparsers.add_parser("clone-profile", help="Clone browser profile with optional session injection")
1701
+ clone_parser.add_argument("source", nargs="?", help="Source profile directory (omit to use system default)")
1702
+ clone_parser.add_argument("--dest", "-d", required=True, help="Destination directory for the clone")
1703
+ clone_parser.add_argument("--browser", "-b", default="chrome", help="Browser name (chrome, firefox, brave, edge)")
1704
+ clone_parser.add_argument("--session", "-s", help="Session file to inject into the clone")
1705
+ clone_parser.add_argument("--profile", "-p", help="Profile name to clone (default: system default)")
1706
+ clone_parser.add_argument("--list-profiles", action="store_true", help="List available browser profiles")
1707
+
1708
+ # Container management
1709
+ container_parser = subparsers.add_parser("container", help="Docker container management")
1710
+ container_sub = container_parser.add_subparsers(dest="container_action")
1711
+
1712
+ container_start = container_sub.add_parser("start", help="Start proxy/API containers")
1713
+ container_start.add_argument("--sessions-dir", "-d", default=".", help="Directory with session files")
1714
+ container_start.add_argument("--proxy-port", type=int, default=9222, help="Starting proxy port")
1715
+ container_start.add_argument("--api-port", type=int, default=9224, help="API server port")
1716
+ container_start.add_argument("--no-api", action="store_true", help="Don't start API server")
1717
+ container_start.add_argument("--restart", default="unless-stopped", help="Restart policy (default: unless-stopped)")
1718
+
1719
+ container_stop = container_sub.add_parser("stop", help="Stop containers")
1720
+ container_stop.add_argument("--name", help="Container name to stop (default: all tokenade)")
1721
+
1722
+ container_sub.add_parser("restart", help="Restart containers")
1723
+ container_sub.add_parser("status", help="Show container status")
1724
+
1725
+ container_logs = container_sub.add_parser("logs", help="Tail container logs")
1726
+ container_logs.add_argument("name", help="Container name")
1727
+ container_logs.add_argument("--tail", "-n", type=int, default=100, help="Number of lines to tail")
1728
+ container_logs.add_argument("--follow", "-f", action="store_true", help="Follow log output")
1729
+
1730
+ container_refresh = container_sub.add_parser("refresh", help="Refresh sessions inside containers")
1731
+ container_refresh.add_argument("name", help="Container name")
1732
+ container_refresh.add_argument("--sessions-dir", default="/app/sessions", help="Sessions dir in container")
1733
+
1734
+ container_scale = container_sub.add_parser("scale", help="Scale proxy containers")
1735
+ container_scale.add_argument("replicas", type=int, help="Number of replicas")
1736
+ container_scale.add_argument("--sessions-dir", "-d", default=".", help="Directory with session files")
1737
+
1738
+ container_sub.add_parser("cleanup", help="Stop and remove all tokenade containers")
1739
+
1740
+ container_health = container_sub.add_parser("health", help="Check container health")
1741
+ container_health.add_argument("--watch", action="store_true", help="Continuous health monitoring")
1742
+ container_health.add_argument("--interval", type=int, default=60, help="Check interval in seconds")
1743
+ container_health.add_argument("--max-restarts", type=int, default=3, help="Max restarts before giving up")
1744
+
1745
+ container_gen = container_sub.add_parser("generate", help="Generate docker-compose override")
1746
+ container_gen.add_argument("--sessions-dir", "-d", default=".", help="Directory with session files")
1747
+ container_gen.add_argument("--output", "-o", help="Output file (default: stdout)")
1748
+ container_gen.add_argument("--base-port", type=int, default=9222, help="Starting port")
1749
+
1750
+ # Kubernetes management
1751
+ k8s_parser = subparsers.add_parser("k8s", help="Kubernetes deployment management")
1752
+ k8s_sub = k8s_parser.add_subparsers(dest="k8s_action")
1753
+
1754
+ k8s_deploy = k8s_sub.add_parser("deploy", help="Generate and apply K8s manifests")
1755
+ k8s_deploy.add_argument("--namespace", "-n", default="default", help="Kubernetes namespace")
1756
+ k8s_deploy.add_argument("--replicas", "-r", type=int, default=1, help="Number of replicas")
1757
+ k8s_deploy.add_argument("--image", default="tokenade:latest", help="Container image")
1758
+ k8s_deploy.add_argument("--port", type=int, default=9222, help="Proxy port")
1759
+ k8s_deploy.add_argument("--dry-run", action="store_true", help="Only generate YAML, don't apply")
1760
+ k8s_deploy.add_argument("--output", "-o", help="Output file for generated YAML")
1761
+
1762
+ k8s_status = k8s_sub.add_parser("status", help="Show deployment status")
1763
+ k8s_status.add_argument("--namespace", "-n", default="default", help="Kubernetes namespace")
1764
+
1765
+ k8s_scale = k8s_sub.add_parser("scale", help="Scale deployment")
1766
+ k8s_scale.add_argument("replicas", type=int, help="Number of replicas")
1767
+ k8s_scale.add_argument("--namespace", "-n", default="default", help="Kubernetes namespace")
1768
+
1769
+ k8s_logs = k8s_sub.add_parser("logs", help="Tail pod logs")
1770
+ k8s_logs.add_argument("--namespace", "-n", default="default", help="Kubernetes namespace")
1771
+ k8s_logs.add_argument("--tail", type=int, default=100, help="Number of lines to tail")
1772
+
1773
+ k8s_delete = k8s_sub.add_parser("delete", help="Delete deployment and service")
1774
+ k8s_delete.add_argument("--namespace", "-n", default="default", help="Kubernetes namespace")
1775
+
1776
+ k8s_sub.add_parser("pods", help="List pods")
1777
+
1778
+ profile_parser = subparsers.add_parser("profile", help="Manage browser profiles")
1779
+ profile_sub = profile_parser.add_subparsers(dest="profile_command", help="Profile commands")
1780
+
1781
+ profile_create = profile_sub.add_parser("create", help="Create a new profile")
1782
+ profile_create.add_argument("name", help="Profile name")
1783
+ profile_create.add_argument("--browser", "-b", default="chromium", help="Browser type (default: chromium)")
1784
+ profile_create.add_argument("--os", dest="os_name", default="windows", choices=["windows", "macos", "linux"], help="Target OS")
1785
+ profile_create.add_argument("--proxy", help="Proxy URL (e.g., socks5://user:pass@host:port)")
1786
+ profile_create.add_argument("--tags", help="Comma-separated tags")
1787
+ profile_create.add_argument("--notes", help="Profile notes")
1788
+
1789
+ profile_list = profile_sub.add_parser("list", help="List all profiles")
1790
+ profile_list.add_argument("--browser", "-b", help="Filter by browser type")
1791
+ profile_list.add_argument("--tag", "-t", help="Filter by tag")
1792
+
1793
+ profile_get = profile_sub.add_parser("get", help="Show profile details")
1794
+ profile_get.add_argument("name", help="Profile name")
1795
+
1796
+ profile_delete = profile_sub.add_parser("delete", help="Delete a profile")
1797
+ profile_delete.add_argument("name", help="Profile name")
1798
+
1799
+ profile_export = profile_sub.add_parser("export", help="Export profile to zip")
1800
+ profile_export.add_argument("name", help="Profile name")
1801
+ profile_export.add_argument("--output", "-o", help="Output file path")
1802
+
1803
+ profile_import = profile_sub.add_parser("import", help="Import profile from zip")
1804
+ profile_import.add_argument("archive", help="Archive file path")
1805
+ profile_import.add_argument("--name", "-n", help="Override profile name")
1806
+
1807
+ profile_recent = profile_sub.add_parser("recent", help="Show recently used profiles")
1808
+ profile_recent.add_argument("--limit", "-n", type=int, default=5, help="Number of profiles")
1809
+
1810
+ profile_sub.add_parser("stats", help="Show profile statistics")
1811
+
1812
+ stealth_parser = subparsers.add_parser("stealth", help="Browser stealth management")
1813
+ stealth_sub = stealth_parser.add_subparsers(dest="stealth_action")
1814
+
1815
+ stealth_test = stealth_sub.add_parser("test", help="Test stealth against detection sites")
1816
+ stealth_test.add_argument("--url", "-u", help="Custom test URL")
1817
+ stealth_test.add_argument("--browser", "-b", choices=["chrome", "firefox"], default="chrome")
1818
+
1819
+ stealth_report = stealth_sub.add_parser("report", help="Generate stealth report")
1820
+ stealth_report.add_argument("--output", "-o", help="Output file for report")
1821
+ stealth_report.add_argument("--browser", "-b", choices=["chrome", "firefox"], default="chrome")
1822
+
1823
+ stealth_battle = stealth_sub.add_parser("battle", help="Battle test against real detection sites")
1824
+ stealth_battle.add_argument("--browser", "-b", choices=["chromium", "firefox"], default="chromium")
1825
+ stealth_battle.add_argument("--site", "-s", action="append", help="Specific site(s) to test (default: all)")
1826
+ stealth_battle.add_argument("--timeout", "-t", type=int, default=30, help="Per-site timeout in seconds")
1827
+ stealth_battle.add_argument("--output", "-o", help="Output file for JSON report")
1828
+
1829
+ stealth_sub.add_parser("deps", help="Check stealth system dependencies")
1830
+ stealth_sub.add_parser("deps-install", help="Install missing stealth dependencies")
1831
+
1832
+ deps_parser = subparsers.add_parser("deps", help="System dependency management")
1833
+ deps_sub = deps_parser.add_subparsers(dest="deps_action")
1834
+
1835
+ deps_sub.add_parser("check", help="Check system dependencies")
1836
+ deps_install = deps_sub.add_parser("install", help="Install missing dependencies")
1837
+ deps_install.add_argument("--browser", "-b", choices=["chrome", "firefox"], default="chrome")
1838
+ deps_install.add_argument("--playwright", action="store_true", help="Install Playwright dependencies")
1839
+
1840
+ serve_parser = subparsers.add_parser("serve", help="Start API server")
1841
+ serve_parser.add_argument("--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)")
1842
+ serve_parser.add_argument("--port", "-p", type=int, default=9224, help="Port to listen on (default: 9224)")
1843
+ serve_parser.add_argument("--api-key", help="API key for authentication")
1844
+ serve_parser.add_argument("--sessions-dir", "-d", help="Sessions directory")
1845
+ serve_parser.add_argument("--cors", help="Allowed CORS origins (comma-separated)")
1846
+
1847
+ return parser
1848
+
1849
+
1850
+ def main():
1851
+ """Main CLI entry point."""
1852
+ parser = _build_parser()
1853
+ args = parser.parse_args()
1854
+
1855
+ if not args.command:
1856
+ parser.print_help()
1857
+ sys.exit(1)
1858
+
1859
+ setup_logging._json_output = getattr(args, 'json_output', False)
1860
+ setup_logging(args.verbose)
1861
+
1862
+ commands = {
1863
+ "setup": cmd_setup,
1864
+ "config": cmd_config,
1865
+ "extract": cmd_extract,
1866
+ "transfer": cmd_transfer,
1867
+ "test": cmd_test,
1868
+ "fingerprint": cmd_fingerprint,
1869
+ "validate": cmd_validate,
1870
+ "export": cmd_export,
1871
+ "load": cmd_load,
1872
+ "inject-profile": cmd_inject_profile,
1873
+ "encrypt": cmd_encrypt,
1874
+ "decrypt": cmd_decrypt,
1875
+ "rekey": cmd_rekey,
1876
+ "batch-export": cmd_batch_export,
1877
+ "batch-load": cmd_batch_load,
1878
+ "health": cmd_health,
1879
+ "health-report": cmd_health_report,
1880
+ "refresh": cmd_refresh,
1881
+ "refresh-oauth": cmd_refresh_oauth,
1882
+ "oauth-config": cmd_oauth_config,
1883
+ "batch-refresh": cmd_batch_refresh,
1884
+ "encrypted-refresh": cmd_encrypted_refresh,
1885
+ "validate-session": cmd_validate_session,
1886
+ "cicd": cmd_cicd,
1887
+ "ci": cmd_ci,
1888
+ "launch": cmd_launch,
1889
+ "refresh-browser": cmd_refresh_browser,
1890
+ "accounts": cmd_accounts,
1891
+ "patch-chrome": cmd_patch_chrome,
1892
+ "mobile-import": cmd_mobile_import,
1893
+ "clone-profile": cmd_clone_profile,
1894
+ "daemon": cmd_daemon,
1895
+ "versions": cmd_versions,
1896
+ "rollback": cmd_rollback,
1897
+ "session-diff": cmd_session_diff,
1898
+ "logs": cmd_logs,
1899
+ "proxy": cmd_proxy,
1900
+ "sessions": cmd_sessions,
1901
+ "share": cmd_share,
1902
+ "unshare": cmd_unshare,
1903
+ "import": cmd_import,
1904
+ "sync": cmd_sync,
1905
+ "monitor": cmd_monitor,
1906
+ "analytics": cmd_analytics,
1907
+ "validate-rules": cmd_validate_rules,
1908
+ "diff": cmd_diff,
1909
+ "plugin": cmd_plugin,
1910
+ "completion": cmd_completion,
1911
+ "container": cmd_container,
1912
+ "k8s": cmd_k8s,
1913
+ "fleet": cmd_fleet,
1914
+ "cloak": cmd_cloak,
1915
+ "autopsy": cmd_autopsy,
1916
+ "tui": cmd_tui,
1917
+ "stealth": cmd_stealth,
1918
+ "deps": cmd_deps,
1919
+ "profile": cmd_profile,
1920
+ "serve": cmd_serve,
1921
+ }
1922
+
1923
+ try:
1924
+ commands[args.command](args)
1925
+ except KeyboardInterrupt:
1926
+ print("\n\n⚠️ Interrupted by user")
1927
+ sys.exit(130)
1928
+ except Exception as e:
1929
+ from tokenade.core.errors import TokenadeError
1930
+ logger.exception("Command failed")
1931
+ if isinstance(e, TokenadeError):
1932
+ print(f"\n❌ {e}")
1933
+ else:
1934
+ print(f"\n❌ Unexpected error: {e}")
1935
+ print(" Run with --verbose for full traceback")
1936
+ print(" Logs: ~/.tokenade/logs/")
1937
+ sys.exit(1)