tokenade 3.5.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 (150) hide show
  1. tokenade/__init__.py +84 -0
  2. tokenade/cli/__init__.py +306 -0
  3. tokenade/cli/__main__.py +5 -0
  4. tokenade/cli/advanced.py +458 -0
  5. tokenade/cli/completions.py +73 -0
  6. tokenade/cli/config.py +47 -0
  7. tokenade/cli/management.py +264 -0
  8. tokenade/cli/output.py +31 -0
  9. tokenade/cli/proxy.py +118 -0
  10. tokenade/cli/security.py +156 -0
  11. tokenade/cli/session.py +471 -0
  12. tokenade/core/__init__.py +0 -0
  13. tokenade/core/antidetection/__init__.py +4 -0
  14. tokenade/core/antidetection/behavioral.py +140 -0
  15. tokenade/core/antidetection/cdp_cleaner.py +60 -0
  16. tokenade/core/api/__init__.py +3 -0
  17. tokenade/core/api/server.py +251 -0
  18. tokenade/core/batch/__init__.py +23 -0
  19. tokenade/core/batch/operations.py +399 -0
  20. tokenade/core/browser/__init__.py +0 -0
  21. tokenade/core/browser/manager.py +311 -0
  22. tokenade/core/crypto/__init__.py +19 -0
  23. tokenade/core/crypto/cookie_crypto.py +506 -0
  24. tokenade/core/crypto/encryptor.py +299 -0
  25. tokenade/core/extractor/__init__.py +0 -0
  26. tokenade/core/fingerprint/__init__.py +0 -0
  27. tokenade/core/fingerprint/collectors/__init__.py +30 -0
  28. tokenade/core/fingerprint/collectors/audio.py +124 -0
  29. tokenade/core/fingerprint/collectors/base.py +69 -0
  30. tokenade/core/fingerprint/collectors/battery.py +59 -0
  31. tokenade/core/fingerprint/collectors/canvas.py +81 -0
  32. tokenade/core/fingerprint/collectors/fonts.py +52 -0
  33. tokenade/core/fingerprint/collectors/navigator.py +99 -0
  34. tokenade/core/fingerprint/collectors/plugins.py +92 -0
  35. tokenade/core/fingerprint/collectors/screen.py +81 -0
  36. tokenade/core/fingerprint/collectors/webgl.py +104 -0
  37. tokenade/core/fingerprint/collectors/webrtc.py +91 -0
  38. tokenade/core/fingerprint/injector.py +94 -0
  39. tokenade/core/fingerprint/manager.py +278 -0
  40. tokenade/core/fingerprint/stealth.py +357 -0
  41. tokenade/core/importer/__init__.py +23 -0
  42. tokenade/core/importer/adb_extractor.py +261 -0
  43. tokenade/core/importer/advanced_validator.py +666 -0
  44. tokenade/core/importer/browser_discovery.py +314 -0
  45. tokenade/core/importer/chromium_forks.py +175 -0
  46. tokenade/core/importer/cookie_extractor.py +517 -0
  47. tokenade/core/importer/db_utils.py +66 -0
  48. tokenade/core/importer/format_exporter.py +213 -0
  49. tokenade/core/importer/format_importer.py +278 -0
  50. tokenade/core/importer/local_storage_extractor.py +272 -0
  51. tokenade/core/importer/mobile_extractor.py +264 -0
  52. tokenade/core/importer/safari_extractor.py +318 -0
  53. tokenade/core/importer/session_comparator.py +124 -0
  54. tokenade/core/importer/session_loader.py +470 -0
  55. tokenade/core/importer/session_manager.py +295 -0
  56. tokenade/core/importer/session_packager.py +354 -0
  57. tokenade/core/importer/session_refresher.py +354 -0
  58. tokenade/core/importer/session_rotation.py +150 -0
  59. tokenade/core/importer/session_sharer.py +692 -0
  60. tokenade/core/importer/session_vault.py +282 -0
  61. tokenade/core/importer/tor_extractor.py +138 -0
  62. tokenade/core/importer/validator.py +498 -0
  63. tokenade/core/injector/__init__.py +15 -0
  64. tokenade/core/injector/profile_manager.py +375 -0
  65. tokenade/core/integration/__init__.py +8 -0
  66. tokenade/core/integration/docker_manager.py +349 -0
  67. tokenade/core/integration/kubernetes.py +395 -0
  68. tokenade/core/integration/plugin_registry.py +250 -0
  69. tokenade/core/integration/webhooks.py +219 -0
  70. tokenade/core/proxy/__init__.py +11 -0
  71. tokenade/core/proxy/cdp_proxy.py +1301 -0
  72. tokenade/core/proxy/extension_bridge.py +132 -0
  73. tokenade/core/proxy/forward_proxy.py +151 -0
  74. tokenade/core/proxy/multi_site_proxy.py +257 -0
  75. tokenade/core/proxy/server.py +1295 -0
  76. tokenade/core/refresh/__init__.py +19 -0
  77. tokenade/core/refresh/health_checker.py +356 -0
  78. tokenade/core/refresh/health_scorer.py +292 -0
  79. tokenade/core/runtime/__init__.py +30 -0
  80. tokenade/core/runtime/engine.py +786 -0
  81. tokenade/core/runtime/tls_matcher.py +251 -0
  82. tokenade/core/security/__init__.py +27 -0
  83. tokenade/core/security/audit.py +577 -0
  84. tokenade/core/security/credentials.py +418 -0
  85. tokenade/core/utils/__init__.py +0 -0
  86. tokenade/core/utils/performance.py +444 -0
  87. tokenade/handlers/__init__.py +0 -0
  88. tokenade/handlers/base.py +294 -0
  89. tokenade/handlers/generic_oauth.py +594 -0
  90. tokenade/handlers/github.py +349 -0
  91. tokenade/handlers/google.py +302 -0
  92. tokenade/sdk/__init__.py +257 -0
  93. tokenade/tests/__init__.py +0 -0
  94. tokenade/tests/portability.py +309 -0
  95. tokenade/tests/test_advanced_validator.py +237 -0
  96. tokenade/tests/test_antidetection.py +147 -0
  97. tokenade/tests/test_api_sdk.py +264 -0
  98. tokenade/tests/test_benchmarks.py +157 -0
  99. tokenade/tests/test_browser.py +351 -0
  100. tokenade/tests/test_browser_discovery.py +59 -0
  101. tokenade/tests/test_browser_improvements.py +276 -0
  102. tokenade/tests/test_browser_support.py +732 -0
  103. tokenade/tests/test_cli.py +204 -0
  104. tokenade/tests/test_cli_helpers.py +102 -0
  105. tokenade/tests/test_cli_refactor.py +458 -0
  106. tokenade/tests/test_collectors.py +286 -0
  107. tokenade/tests/test_comprehensive.py +314 -0
  108. tokenade/tests/test_cookie_extractor.py +156 -0
  109. tokenade/tests/test_crypto.py +189 -0
  110. tokenade/tests/test_db_utils.py +93 -0
  111. tokenade/tests/test_encryptor.py +126 -0
  112. tokenade/tests/test_encryptor_edge.py +66 -0
  113. tokenade/tests/test_enterprise.py +819 -0
  114. tokenade/tests/test_fingerprint.py +67 -0
  115. tokenade/tests/test_format_export.py +415 -0
  116. tokenade/tests/test_forward_proxy.py +52 -0
  117. tokenade/tests/test_handlers.py +523 -0
  118. tokenade/tests/test_health_scorer.py +538 -0
  119. tokenade/tests/test_importer_integration.py +273 -0
  120. tokenade/tests/test_integration.py +660 -0
  121. tokenade/tests/test_local_storage.py +57 -0
  122. tokenade/tests/test_local_storage_extractor.py +175 -0
  123. tokenade/tests/test_mac_crypto.py +74 -0
  124. tokenade/tests/test_multi_site_proxy.py +51 -0
  125. tokenade/tests/test_performance.py +302 -0
  126. tokenade/tests/test_property_based.py +215 -0
  127. tokenade/tests/test_proxy.py +645 -0
  128. tokenade/tests/test_proxy_config.py +86 -0
  129. tokenade/tests/test_proxy_gui.py +81 -0
  130. tokenade/tests/test_runtime.py +473 -0
  131. tokenade/tests/test_security.py +349 -0
  132. tokenade/tests/test_session_comparator.py +110 -0
  133. tokenade/tests/test_session_loader.py +53 -0
  134. tokenade/tests/test_session_manager.py +135 -0
  135. tokenade/tests/test_session_packager.py +104 -0
  136. tokenade/tests/test_session_refresher.py +558 -0
  137. tokenade/tests/test_session_rotation.py +408 -0
  138. tokenade/tests/test_session_sharer.py +588 -0
  139. tokenade/tests/test_session_vault.py +418 -0
  140. tokenade/tests/test_site_filter.py +66 -0
  141. tokenade/tests/test_ssrf.py +55 -0
  142. tokenade/tests/test_stealth.py +148 -0
  143. tokenade/tests/test_tls.py +40 -0
  144. tokenade/tests/test_tls_matcher.py +131 -0
  145. tokenade/utils/__init__.py +0 -0
  146. tokenade-3.5.0.dist-info/METADATA +567 -0
  147. tokenade-3.5.0.dist-info/RECORD +150 -0
  148. tokenade-3.5.0.dist-info/WHEEL +5 -0
  149. tokenade-3.5.0.dist-info/entry_points.txt +2 -0
  150. tokenade-3.5.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,458 @@
1
+ """Advanced CLI commands."""
2
+ import asyncio
3
+ import json
4
+ import logging
5
+ from pathlib import Path
6
+
7
+ logger = logging.getLogger("tokenade")
8
+
9
+
10
+ def cmd_batch_export(args):
11
+ """Batch export multiple sites."""
12
+ from tokenade.core.batch.operations import BatchExporter, load_batch_config, generate_batch_report
13
+
14
+ print("\n" + "=" * 80)
15
+ print("TOKENADE - Batch Export")
16
+ print("=" * 80)
17
+
18
+ try:
19
+ sites = load_batch_config(args.site_config)
20
+ print(f"\n📋 Loaded {len(sites)} site(s) from: {args.site_config}")
21
+ except Exception as e:
22
+ print(f"❌ Failed to load site config: {e}")
23
+ return
24
+
25
+ output_dir = args.output or "sessions_batch"
26
+
27
+ print(f"\n🌐 Browser: {args.browser}")
28
+ print(f"📁 Output: {output_dir}")
29
+
30
+ try:
31
+ exporter = BatchExporter()
32
+ result = exporter.export_batch(
33
+ browser=args.browser,
34
+ sites=sites,
35
+ output_dir=output_dir,
36
+ browser_path=args.browser_path,
37
+ profile=args.profile,
38
+ extract_local_storage=args.extract_local_storage
39
+ )
40
+
41
+ print("\n" + generate_batch_report(result))
42
+
43
+ if result.success:
44
+ print(f"\n✅ Batch export completed successfully")
45
+ else:
46
+ print(f"\n⚠️ Batch export completed with errors")
47
+
48
+ except Exception as e:
49
+ logger.error(f"Batch export failed: {e}")
50
+ print(f"❌ Failed: {e}")
51
+
52
+
53
+ def cmd_batch_load(args):
54
+ """Batch load multiple sessions."""
55
+ from tokenade.core.batch.operations import BatchLoader, load_batch_config, generate_batch_report
56
+
57
+ print("\n" + "=" * 80)
58
+ print("TOKENADE - Batch Load")
59
+ print("=" * 80)
60
+
61
+ sites = None
62
+ if args.site_config:
63
+ try:
64
+ sites = load_batch_config(args.site_config)
65
+ print(f"\n📋 Loaded {len(sites)} site(s) from: {args.site_config}")
66
+ except Exception as e:
67
+ print(f"⚠️ Failed to load site config: {e}")
68
+
69
+ print(f"\n📂 Sessions: {args.sessions_dir}")
70
+ print(f"🌐 Target: {args.target_browser}")
71
+
72
+ try:
73
+ loader = BatchLoader()
74
+ result = loader.load_batch(
75
+ sessions_dir=args.sessions_dir,
76
+ target_browser=args.target_browser,
77
+ site_configs=sites,
78
+ profile_dir=args.profile_dir,
79
+ validate=args.validate,
80
+ visible=args.visible
81
+ )
82
+
83
+ print("\n" + generate_batch_report(result))
84
+
85
+ if result.success:
86
+ print(f"\n✅ Batch load completed successfully")
87
+ else:
88
+ print(f"\n⚠️ Batch load completed with errors")
89
+
90
+ except Exception as e:
91
+ logger.error(f"Batch load failed: {e}")
92
+ print(f"❌ Failed: {e}")
93
+
94
+
95
+ def cmd_validate(args):
96
+ """Validate stored sessions."""
97
+ print("\n" + "=" * 80)
98
+ print("TOKENADE - Session Validation")
99
+ print("=" * 80)
100
+
101
+ sessions_dir = Path(args.sessions_dir)
102
+ if not sessions_dir.exists():
103
+ print(f"❌ Directory not found: {args.sessions_dir}")
104
+ return
105
+
106
+ valid = 0
107
+ invalid = 0
108
+
109
+ for session_file in sessions_dir.glob("*.json"):
110
+ print(f"\n📁 Checking: {session_file.name}")
111
+
112
+ try:
113
+ with open(session_file) as f:
114
+ data = json.load(f)
115
+
116
+ required = ["site_name", "auth_status", "cookies"]
117
+ missing = [f for f in required if f not in data]
118
+
119
+ if missing:
120
+ print(f" ❌ Missing fields: {missing}")
121
+ invalid += 1
122
+ continue
123
+
124
+ cookies = data.get("cookies", [])
125
+ if not cookies:
126
+ print(f" ⚠️ No cookies")
127
+ else:
128
+ print(f" ✅ {len(cookies)} cookies")
129
+
130
+ tokens = data.get("tokens", [])
131
+ if tokens:
132
+ print(f" ✅ {len(tokens)} tokens")
133
+
134
+ status = data.get("auth_status", "unknown")
135
+ if status == "logged_in":
136
+ print(f" ✅ Status: logged_in")
137
+ valid += 1
138
+ else:
139
+ print(f" ⚠️ Status: {status}")
140
+ invalid += 1
141
+
142
+ except Exception as e:
143
+ print(f" ❌ Error: {e}")
144
+ invalid += 1
145
+
146
+ print(f"\n📊 Summary: {valid} valid, {invalid} invalid")
147
+
148
+
149
+ def cmd_validate_rules(args):
150
+ """Validate session with custom rules."""
151
+ from tokenade.core.importer.advanced_validator import AdvancedValidator, load_validation_rules
152
+ from tokenade.core.importer.session_packager import SessionPackager
153
+
154
+ session_file = Path(args.session)
155
+ if not session_file.exists():
156
+ print(f"❌ Session file not found: {args.session}")
157
+ return
158
+
159
+ rules_file = Path(args.rules)
160
+ if not rules_file.exists():
161
+ print(f"❌ Rules file not found: {args.rules}")
162
+ return
163
+
164
+ packager = SessionPackager()
165
+ session = packager.load(str(session_file))
166
+
167
+ rules = load_validation_rules(str(rules_file))
168
+
169
+ print("\n" + "=" * 60)
170
+ print("TOKENADE - Advanced Validation")
171
+ print("=" * 60)
172
+ print(f"\n📂 Session: {args.session}")
173
+ print(f"📋 Rules: {len(rules)}")
174
+
175
+ validator = AdvancedValidator()
176
+
177
+ results = asyncio.run(validator.validate_rules(
178
+ session,
179
+ rules,
180
+ site_url=args.url,
181
+ ))
182
+
183
+ passed = sum(1 for r in results if r.passed)
184
+ failed = sum(1 for r in results if not r.passed)
185
+
186
+ print(f"\n{'='*60}")
187
+ for result in results:
188
+ status = "✅" if result.passed else "❌"
189
+ duration = f" ({result.duration_ms:.0f}ms)" if result.duration_ms else ""
190
+ print(f"{status} {result.rule_name}: {result.message}{duration}")
191
+ if result.details and not result.passed:
192
+ for k, v in result.details.items():
193
+ print(f" {k}: {v}")
194
+
195
+ print(f"\n{'='*60}")
196
+ print(f"Results: {passed} passed, {failed} failed")
197
+ print(f"{'='*60}\n")
198
+
199
+ if failed:
200
+ import sys
201
+ sys.exit(1)
202
+
203
+
204
+ def cmd_diff(args):
205
+ """Compare two session files."""
206
+ from tokenade.core.importer.session_comparator import SessionComparator
207
+
208
+ for path in (args.session_a, args.session_b):
209
+ if not Path(path).exists():
210
+ print(f"❌ File not found: {path}")
211
+ return
212
+
213
+ comparator = SessionComparator()
214
+ result = comparator.compare_files(args.session_a, args.session_b)
215
+
216
+ print("\n" + "=" * 70)
217
+ print("SESSION COMPARISON")
218
+ print("=" * 70)
219
+ print(f"\n A: {args.session_a}")
220
+ print(f" B: {args.session_b}")
221
+
222
+ if not result.has_changes:
223
+ print("\n ✅ Sessions are identical")
224
+ return
225
+
226
+ print(f"\n {'─' * 50}")
227
+ print(result.summary())
228
+
229
+ if args.verbose:
230
+ if result.cookies_only_in_a:
231
+ print(f"\n Cookies only in A:")
232
+ for c in result.cookies_only_in_a:
233
+ print(f" - {c.get('name')} ({c.get('domain')})")
234
+ if result.cookies_only_in_b:
235
+ print(f"\n Cookies only in B:")
236
+ for c in result.cookies_only_in_b:
237
+ print(f" - {c.get('name')} ({c.get('domain')})")
238
+ if result.cookies_modified:
239
+ print(f"\n Cookies modified:")
240
+ for m in result.cookies_modified:
241
+ print(f" - {m['key']}")
242
+ print(f" A: {m['a'].get('value', '')[:50]}...")
243
+ print(f" B: {m['b'].get('value', '')[:50]}...")
244
+ if result.localStorage_only_in_a:
245
+ print(f"\n localStorage only in A:")
246
+ for k in result.localStorage_only_in_a:
247
+ print(f" - {k}")
248
+ if result.localStorage_only_in_b:
249
+ print(f"\n localStorage only in B:")
250
+ for k in result.localStorage_only_in_b:
251
+ print(f" - {k}")
252
+ if result.localStorage_modified:
253
+ print(f"\n localStorage modified:")
254
+ for k, v in result.localStorage_modified.items():
255
+ print(f" - {k}")
256
+ print(f" A: {v['a'][:50]}...")
257
+ print(f" B: {v['b'][:50]}...")
258
+ if result.metadata_diffs:
259
+ print(f"\n Metadata differences:")
260
+ for field, vals in result.metadata_diffs.items():
261
+ print(f" - {field}: {vals['a']} -> {vals['b']}")
262
+
263
+
264
+ def cmd_fingerprint(args):
265
+ """Manage browser fingerprints."""
266
+ from tokenade.core.browser.manager import BrowserFactory, BrowserConfig
267
+ from tokenade.core.fingerprint.manager import FingerprintManager, FingerprintCollector
268
+
269
+ fp_manager = FingerprintManager()
270
+
271
+ if args.action == "list":
272
+ print("\n📋 Stored fingerprints:")
273
+ for name in fp_manager.list():
274
+ print(f" • {name}")
275
+
276
+ elif args.action == "collect":
277
+ print("\n🚀 Launching browser to collect fingerprint...")
278
+
279
+ config = BrowserConfig(
280
+ headless=False,
281
+ user_data_dir=args.profile_dir,
282
+ )
283
+
284
+ browser = BrowserFactory.create(**config.__dict__)
285
+
286
+ try:
287
+ browser.launch()
288
+ fp = FingerprintCollector.collect_from_browser(browser)
289
+ path = fp_manager.save(args.name, fp)
290
+ print(f"✅ Fingerprint saved: {path}")
291
+ print(f"\n User Agent: {fp.user_agent[:80]}...")
292
+ print(f" Screen: {fp.screen_width}x{fp.screen_height}")
293
+ print(f" Platform: {fp.platform}")
294
+ finally:
295
+ browser.close()
296
+
297
+ elif args.action == "show":
298
+ fp = fp_manager.load(args.name)
299
+ if fp:
300
+ print(f"\n🔍 Fingerprint: {args.name}")
301
+ print(f" User Agent: {fp.user_agent}")
302
+ print(f" Screen: {fp.screen_width}x{fp.screen_height}")
303
+ print(f" Viewport: {fp.viewport_width}x{fp.viewport_height}")
304
+ print(f" Platform: {fp.platform}")
305
+ print(f" Language: {fp.language}")
306
+ print(f" Timezone: {fp.timezone}")
307
+ print(f" Hardware: {fp.hardware_concurrency} cores, {fp.device_memory}GB RAM")
308
+ else:
309
+ print(f"❌ Fingerprint not found: {args.name}")
310
+
311
+ elif args.action == "delete":
312
+ if fp_manager.delete(args.name):
313
+ print(f"✅ Deleted: {args.name}")
314
+ else:
315
+ print(f"❌ Not found: {args.name}")
316
+
317
+
318
+ def cmd_test(args):
319
+ """Run portability tests with fingerprint spoofing."""
320
+ from tokenade.core.browser.manager import BrowserFactory, BrowserConfig
321
+ from tokenade.core.fingerprint.manager import FingerprintManager
322
+ from tokenade.core.fingerprint.injector import validate_injection
323
+ from tokenade.handlers.google import GoogleHandler
324
+ from tokenade.tests.portability import PortabilityTester
325
+
326
+ print("\n" + "=" * 80)
327
+ print("TOKENADE - Portability Test")
328
+ print("=" * 80)
329
+
330
+ session_file = Path(args.session)
331
+ if not session_file.exists():
332
+ print(f"❌ Session file not found: {args.session}")
333
+ return
334
+
335
+ with open(session_file) as f:
336
+ session_data = json.load(f)
337
+
338
+ fp_manager = FingerprintManager()
339
+ tester = PortabilityTester(BrowserFactory, fp_manager)
340
+
341
+ print(f"\n🛡️ Stealth level: {args.stealth_level}")
342
+
343
+ if args.variations:
344
+ print("\n🧪 Testing fingerprint variations...")
345
+ results = tester.test_fingerprint_variations(
346
+ session_data=session_data,
347
+ base_fp_name=args.source_fp or "default",
348
+ handler_class=GoogleHandler,
349
+ )
350
+ else:
351
+ print(f"\n🧪 Testing transfer to: {args.target_fp}")
352
+
353
+ fp = fp_manager.load(args.target_fp)
354
+ if fp:
355
+ print(f" User Agent: {fp.user_agent[:60]}...")
356
+ print(f" Screen: {fp.screen_width}x{fp.screen_height}")
357
+
358
+ result = tester.test_session_transfer(
359
+ session_data=session_data,
360
+ source_fp_name=args.source_fp or "default",
361
+ target_fp_name=args.target_fp,
362
+ handler_class=GoogleHandler,
363
+ test_api=args.test_api,
364
+ )
365
+ results = [result]
366
+
367
+ if args.validate_stealth and fp:
368
+ print("\n🔍 Validating stealth injection...")
369
+ config = BrowserConfig(
370
+ headless=True,
371
+ fingerprint=fp.to_dict(),
372
+ stealth_level=args.stealth_level,
373
+ )
374
+ browser = BrowserFactory.create(**config.__dict__)
375
+ try:
376
+ browser.launch()
377
+ result = validate_injection(browser)
378
+ if result["valid"]:
379
+ print(" ✅ Stealth injection verified")
380
+ else:
381
+ print(" ⚠️ Stealth injection may not be fully active")
382
+ finally:
383
+ browser.close()
384
+
385
+ report = tester.generate_report(args.output)
386
+ print(report)
387
+
388
+
389
+ def cmd_setup(args):
390
+ """Setup accounts with initial login."""
391
+ from datetime import datetime
392
+ import getpass
393
+ from tokenade.core.browser.manager import BrowserFactory, BrowserConfig
394
+ from tokenade.core.security.credentials import CredentialManager, AccountCredentials
395
+ from tokenade.handlers.google import GoogleHandler
396
+
397
+ print("\n" + "=" * 80)
398
+ print("TOKENADE - Account Setup")
399
+ print("=" * 80)
400
+
401
+ manager = CredentialManager()
402
+ accounts = manager.load_accounts()
403
+
404
+ if accounts:
405
+ print(f"\n📋 Found {len(accounts)} existing account(s)")
406
+
407
+ while True:
408
+ choice = input("\nAdd account? (yes/no): ").strip().lower()
409
+ if choice not in ("yes", "y"):
410
+ break
411
+
412
+ email = input("📧 Email: ").strip()
413
+ password = getpass.getpass("🔒 Password: ")
414
+
415
+ if not email or not password:
416
+ print("❌ Email and password required")
417
+ continue
418
+
419
+ account_num = len(accounts) + 1
420
+ profile_dir = f"browser_data/{account_num}"
421
+
422
+ print(f"\n🚀 Setting up account #{account_num}...")
423
+
424
+ config = BrowserConfig(
425
+ headless=False,
426
+ user_data_dir=profile_dir,
427
+ )
428
+
429
+ browser = BrowserFactory.create(**config.__dict__)
430
+ page = browser.launch()
431
+
432
+ try:
433
+ handler = GoogleHandler(browser)
434
+ status = handler.login(email, password, headless=False)
435
+
436
+ if status.value == "logged_in":
437
+ account = AccountCredentials(
438
+ number=account_num,
439
+ email=email,
440
+ password=password,
441
+ profile_dir=profile_dir,
442
+ site="google",
443
+ metadata={"created_at": datetime.now().isoformat()},
444
+ )
445
+ accounts.append(account)
446
+ manager.save_accounts(accounts, use_keyring=True, encrypt_file=False)
447
+
448
+ print(f"✅ Account #{account_num} setup complete")
449
+ print(f" Password stored in system keyring" if manager._keyring_available
450
+ else f" ⚠️ Keyring unavailable — run 'tokenade setup --encrypt' for secure storage")
451
+ else:
452
+ print(f"❌ Login failed for account #{account_num}")
453
+
454
+ finally:
455
+ browser.close()
456
+
457
+ print(f"\n📊 Total accounts: {len(accounts)}")
458
+ print("\nNext: run 'tokenade extract' to collect tokens")
@@ -0,0 +1,73 @@
1
+ """Shell completion scripts for bash, zsh, and fish."""
2
+ from textwrap import dedent
3
+
4
+ BASH_COMPLETION = dedent("""\
5
+ # Tokenade bash completion
6
+ _tokenade_completions() {
7
+ local cur prev commands
8
+ COMPREPLY=()
9
+ cur="${COMP_WORDS[COMP_CWORD]}"
10
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
11
+ commands="setup extract transfer test fingerprint export load inject-profile encrypt decrypt rekey batch-export batch-load health refresh proxy sessions share unshare validate-rules diff"
12
+
13
+ if [[ ${cur} == -* ]]; then
14
+ COMPREPLY=( $(compgen -W "--help --version --browser-name --domains --output --port --host --visible --session --format --password --expiry --profile --site-config --list-profiles" -- ${cur}) )
15
+ else
16
+ COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) )
17
+ fi
18
+ return 0
19
+ }
20
+ complete -F _tokenade_completions tokenade
21
+ """)
22
+
23
+ ZSH_COMPLETION = dedent("""\
24
+ #compdef tokenade
25
+ # Tokenade zsh completion
26
+
27
+ _tokenade() {
28
+ _arguments \
29
+ '1:command:(setup extract transfer test fingerprint export load inject-profile encrypt decrypt rekey batch-export batch-load health refresh proxy sessions share unshare validate-rules diff)' \
30
+ '*::arg:->args'
31
+ }
32
+
33
+ _tokenade "$@"
34
+ """)
35
+
36
+ FISH_COMPLETION = dedent("""\
37
+ # Tokenade fish completion
38
+ complete -c tokenade -f
39
+ complete -c tokenade -n '__fish_use_subcommand' -a setup -d 'Initial setup'
40
+ complete -c tokenade -n '__fish_use_subcommand' -a extract -d 'Extract cookies'
41
+ complete -c tokenade -n '__fish_use_subcommand' -a export -d 'Export session'
42
+ complete -c tokenade -n '__fish_use_subcommand' -a proxy -d 'Start proxy'
43
+ complete -c tokenade -n '__fish_use_subcommand' -a share -d 'Share session'
44
+ complete -c tokenade -n '__fish_use_subcommand' -a sessions -d 'Manage sessions'
45
+ complete -c tokenade -n '__fish_use_subcommand' -a health -d 'Check session health'
46
+ complete -c tokenade -n '__fish_use_subcommand' -a encrypt -d 'Encrypt session'
47
+ complete -c tokenade -n '__fish_use_subcommand' -a decrypt -d 'Decrypt session'
48
+ """)
49
+
50
+
51
+ def install_completion(shell: str = "bash"):
52
+ """Install shell completion."""
53
+ from pathlib import Path
54
+
55
+ if shell == "bash":
56
+ comp_dir = Path.home() / ".bash_completion.d"
57
+ comp_dir.mkdir(exist_ok=True)
58
+ comp_file = comp_dir / "tokenade"
59
+ comp_file.write_text(BASH_COMPLETION)
60
+ print(f"Installed bash completion to {comp_file}")
61
+ print("Restart your shell or run: source ~/.bash_completion.d/tokenade")
62
+ elif shell == "zsh":
63
+ comp_dir = Path.home() / ".zsh" / "completions"
64
+ comp_dir.mkdir(parents=True, exist_ok=True)
65
+ comp_file = comp_dir / "_tokenade"
66
+ comp_file.write_text(ZSH_COMPLETION)
67
+ print(f"Installed zsh completion to {comp_file}")
68
+ elif shell == "fish":
69
+ comp_dir = Path.home() / ".config" / "fish" / "completions"
70
+ comp_dir.mkdir(parents=True, exist_ok=True)
71
+ comp_file = comp_dir / "tokenade.fish"
72
+ comp_file.write_text(FISH_COMPLETION)
73
+ print(f"Installed fish completion to {comp_file}")
tokenade/cli/config.py ADDED
@@ -0,0 +1,47 @@
1
+ """Tokenade configuration file support."""
2
+ import json
3
+ from pathlib import Path
4
+ from typing import Any, Optional
5
+
6
+ DEFAULT_CONFIG_DIR = Path.home() / ".tokenade"
7
+ DEFAULT_CONFIG_FILE = DEFAULT_CONFIG_DIR / "config.json"
8
+
9
+
10
+ class TokenadeConfig:
11
+ def __init__(self, config_file: Path = DEFAULT_CONFIG_FILE):
12
+ self.config_file = config_file
13
+ self._defaults = {
14
+ "browser": "chrome",
15
+ "output_format": "tokenade",
16
+ "proxy_port": 9222,
17
+ "proxy_host": "127.0.0.1",
18
+ "sessions_dir": "~/.tokenade/sessions",
19
+ "auto_refresh": False,
20
+ "source_browser": None,
21
+ }
22
+ self._config = self._load()
23
+
24
+ def _load(self) -> dict:
25
+ if self.config_file.exists():
26
+ with open(self.config_file) as f:
27
+ return {**self._defaults, **json.load(f)}
28
+ return self._defaults.copy()
29
+
30
+ def get(self, key: str, default: Any = None) -> Any:
31
+ return self._config.get(key, default)
32
+
33
+ def set(self, key: str, value: Any):
34
+ self._config[key] = value
35
+ self._save()
36
+
37
+ def _save(self):
38
+ self.config_file.parent.mkdir(parents=True, exist_ok=True)
39
+ with open(self.config_file, "w") as f:
40
+ json.dump(self._config, f, indent=2)
41
+
42
+ def delete(self, key: str):
43
+ self._config.pop(key, None)
44
+ self._save()
45
+
46
+ def list_all(self) -> dict:
47
+ return self._config.copy()