@techwavedev/agi-agent-kit 1.1.7 β†’ 1.2.7

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 (56) hide show
  1. package/CHANGELOG.md +142 -1
  2. package/README.md +195 -15
  3. package/bin/init.js +154 -5
  4. package/package.json +6 -3
  5. package/templates/base/AGENTS.md +54 -23
  6. package/templates/base/README.md +327 -0
  7. package/templates/base/directives/memory_integration.md +95 -0
  8. package/templates/base/execution/memory_manager.py +309 -0
  9. package/templates/base/execution/session_boot.py +218 -0
  10. package/templates/base/execution/session_init.py +320 -0
  11. package/templates/base/requirements.txt +45 -6
  12. package/templates/base/skill-creator/SKILL_skillcreator.md +3 -3
  13. package/templates/skills/knowledge/design-md/README.md +0 -0
  14. package/templates/skills/knowledge/design-md/SKILL.md +0 -0
  15. package/templates/skills/knowledge/design-md/examples/DESIGN.md +0 -0
  16. package/templates/skills/knowledge/intelligent-routing/SKILL.md +237 -164
  17. package/templates/skills/knowledge/notebooklm-rag/SKILL.md +216 -0
  18. package/templates/skills/knowledge/notebooklm-rag/requirements.txt +9 -0
  19. package/templates/skills/knowledge/notebooklm-rag/scripts/ask_question.py +237 -0
  20. package/templates/skills/knowledge/notebooklm-rag/scripts/auth_manager.py +307 -0
  21. package/templates/skills/knowledge/notebooklm-rag/scripts/browser_utils.py +101 -0
  22. package/templates/skills/knowledge/notebooklm-rag/scripts/cleanup_manager.py +87 -0
  23. package/templates/skills/knowledge/notebooklm-rag/scripts/config.py +45 -0
  24. package/templates/skills/knowledge/notebooklm-rag/scripts/notebook_manager.py +334 -0
  25. package/templates/skills/knowledge/notebooklm-rag/scripts/run.py +92 -0
  26. package/templates/skills/knowledge/notebooklm-rag/scripts/setup_environment.py +68 -0
  27. package/templates/skills/knowledge/parallel-agents/SKILL.md +345 -73
  28. package/templates/skills/knowledge/plugin-discovery/SKILL.md +581 -0
  29. package/templates/skills/knowledge/plugin-discovery/scripts/platform_setup.py +1083 -0
  30. package/templates/skills/knowledge/react-components/README.md +0 -0
  31. package/templates/skills/knowledge/react-components/SKILL.md +0 -0
  32. package/templates/skills/knowledge/react-components/examples/gold-standard-card.tsx +0 -0
  33. package/templates/skills/knowledge/react-components/package-lock.json +0 -0
  34. package/templates/skills/knowledge/react-components/package.json +0 -0
  35. package/templates/skills/knowledge/react-components/resources/architecture-checklist.md +0 -0
  36. package/templates/skills/knowledge/react-components/resources/component-template.tsx +0 -0
  37. package/templates/skills/knowledge/react-components/resources/stitch-api-reference.md +0 -0
  38. package/templates/skills/knowledge/react-components/resources/style-guide.json +0 -0
  39. package/templates/skills/knowledge/react-components/scripts/validate.js +0 -0
  40. package/templates/skills/knowledge/self-update/SKILL.md +0 -0
  41. package/templates/skills/knowledge/self-update/scripts/update_kit.py +0 -0
  42. package/templates/skills/knowledge/stitch-loop/README.md +0 -0
  43. package/templates/skills/knowledge/stitch-loop/SKILL.md +3 -3
  44. package/templates/skills/knowledge/stitch-loop/examples/SITE.md +0 -0
  45. package/templates/skills/knowledge/stitch-loop/examples/next-prompt.md +0 -0
  46. package/templates/skills/knowledge/stitch-loop/resources/baton-schema.md +0 -0
  47. package/templates/skills/knowledge/stitch-loop/resources/site-template.md +0 -0
  48. package/templates/skills/stitch-loop/SKILL.md +3 -3
  49. package/templates/skills/core/qdrant-memory/scripts/__pycache__/embedding_utils.cpython-314.pyc +0 -0
  50. package/templates/skills/core/qdrant-memory/scripts/__pycache__/init_collection.cpython-314.pyc +0 -0
  51. package/templates/skills/knowledge/SKILLS_CATALOG.md +0 -796
  52. package/templates/skills/knowledge/jira/scripts/__pycache__/jira_client.cpython-314.pyc +0 -0
  53. package/templates/skills/knowledge/notebooklm-mcp/SKILL.md +0 -71
  54. package/templates/skills/knowledge/notebooklm-mcp/assets/example_asset.txt +0 -24
  55. package/templates/skills/knowledge/notebooklm-mcp/references/api_reference.md +0 -34
  56. package/templates/skills/knowledge/notebooklm-mcp/scripts/example.py +0 -19
@@ -0,0 +1,307 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Authentication Manager for NotebookLM RAG Skill
4
+ Handles Google login and browser state persistence
5
+ Adapted from PleasePrompto/notebooklm-skill (MIT License)
6
+
7
+ Implements hybrid auth approach:
8
+ - Persistent browser profile (user_data_dir) for fingerprint consistency
9
+ - Manual cookie injection from state.json for session cookies (Playwright bug workaround)
10
+ See: https://github.com/microsoft/playwright/issues/36139
11
+ """
12
+
13
+ import json
14
+ import time
15
+ import argparse
16
+ import shutil
17
+ import re
18
+ import sys
19
+ from pathlib import Path
20
+ from typing import Optional, Dict, Any
21
+
22
+ from patchright.sync_api import sync_playwright, BrowserContext
23
+
24
+ # Add parent directory to path
25
+ sys.path.insert(0, str(Path(__file__).parent))
26
+
27
+ from config import BROWSER_STATE_DIR, STATE_FILE, AUTH_INFO_FILE, DATA_DIR
28
+ from browser_utils import BrowserFactory
29
+
30
+
31
+ class AuthManager:
32
+ """
33
+ Manages authentication and browser state for NotebookLM
34
+
35
+ Features:
36
+ - Interactive Google login
37
+ - Browser state persistence
38
+ - Session restoration
39
+ - Account switching
40
+ """
41
+
42
+ def __init__(self):
43
+ """Initialize the authentication manager"""
44
+ DATA_DIR.mkdir(parents=True, exist_ok=True)
45
+ BROWSER_STATE_DIR.mkdir(parents=True, exist_ok=True)
46
+
47
+ self.state_file = STATE_FILE
48
+ self.auth_info_file = AUTH_INFO_FILE
49
+ self.browser_state_dir = BROWSER_STATE_DIR
50
+
51
+ def is_authenticated(self) -> bool:
52
+ """Check if valid authentication exists"""
53
+ if not self.state_file.exists():
54
+ return False
55
+
56
+ age_days = (time.time() - self.state_file.stat().st_mtime) / 86400
57
+ if age_days > 7:
58
+ print(f"⚠️ Browser state is {age_days:.1f} days old, may need re-authentication")
59
+
60
+ return True
61
+
62
+ def get_auth_info(self) -> Dict[str, Any]:
63
+ """Get authentication information"""
64
+ info = {
65
+ 'authenticated': self.is_authenticated(),
66
+ 'state_file': str(self.state_file),
67
+ 'state_exists': self.state_file.exists()
68
+ }
69
+
70
+ if self.auth_info_file.exists():
71
+ try:
72
+ with open(self.auth_info_file, 'r') as f:
73
+ saved_info = json.load(f)
74
+ info.update(saved_info)
75
+ except Exception:
76
+ pass
77
+
78
+ if info['state_exists']:
79
+ age_hours = (time.time() - self.state_file.stat().st_mtime) / 3600
80
+ info['state_age_hours'] = age_hours
81
+
82
+ return info
83
+
84
+ def setup_auth(self, headless: bool = False, timeout_minutes: int = 10) -> bool:
85
+ """
86
+ Perform interactive authentication setup
87
+
88
+ Args:
89
+ headless: Run browser in headless mode (False for login)
90
+ timeout_minutes: Maximum time to wait for login
91
+
92
+ Returns:
93
+ True if authentication successful
94
+ """
95
+ print("πŸ” Starting authentication setup...")
96
+ print(f" Timeout: {timeout_minutes} minutes")
97
+
98
+ playwright = None
99
+ context = None
100
+
101
+ try:
102
+ playwright = sync_playwright().start()
103
+
104
+ context = BrowserFactory.launch_persistent_context(
105
+ playwright,
106
+ headless=headless
107
+ )
108
+
109
+ page = context.new_page()
110
+ page.goto("https://notebooklm.google.com", wait_until="domcontentloaded")
111
+
112
+ if "notebooklm.google.com" in page.url and "accounts.google.com" not in page.url:
113
+ print(" βœ… Already authenticated!")
114
+ self._save_browser_state(context)
115
+ return True
116
+
117
+ print("\n ⏳ Please log in to your Google account...")
118
+ print(f" ⏱️ Waiting up to {timeout_minutes} minutes for login...")
119
+
120
+ try:
121
+ timeout_ms = int(timeout_minutes * 60 * 1000)
122
+ page.wait_for_url(re.compile(r"^https://notebooklm\.google\.com/"), timeout=timeout_ms)
123
+
124
+ print(f" βœ… Login successful!")
125
+
126
+ self._save_browser_state(context)
127
+ self._save_auth_info()
128
+ return True
129
+
130
+ except Exception as e:
131
+ print(f" ❌ Authentication timeout: {e}")
132
+ return False
133
+
134
+ except Exception as e:
135
+ print(f" ❌ Error: {e}")
136
+ return False
137
+
138
+ finally:
139
+ if context:
140
+ try:
141
+ context.close()
142
+ except Exception:
143
+ pass
144
+ if playwright:
145
+ try:
146
+ playwright.stop()
147
+ except Exception:
148
+ pass
149
+
150
+ def _save_browser_state(self, context: BrowserContext):
151
+ """Save browser state to disk"""
152
+ try:
153
+ context.storage_state(path=str(self.state_file))
154
+ print(f" πŸ’Ύ Saved browser state to: {self.state_file}")
155
+ except Exception as e:
156
+ print(f" ❌ Failed to save browser state: {e}")
157
+ raise
158
+
159
+ def _save_auth_info(self):
160
+ """Save authentication metadata"""
161
+ try:
162
+ info = {
163
+ 'authenticated_at': time.time(),
164
+ 'authenticated_at_iso': time.strftime('%Y-%m-%d %H:%M:%S')
165
+ }
166
+ with open(self.auth_info_file, 'w') as f:
167
+ json.dump(info, f, indent=2)
168
+ except Exception:
169
+ pass
170
+
171
+ def clear_auth(self) -> bool:
172
+ """Clear all authentication data"""
173
+ print("πŸ—‘οΈ Clearing authentication data...")
174
+
175
+ try:
176
+ if self.state_file.exists():
177
+ self.state_file.unlink()
178
+ print(" βœ… Removed browser state")
179
+
180
+ if self.auth_info_file.exists():
181
+ self.auth_info_file.unlink()
182
+ print(" βœ… Removed auth info")
183
+
184
+ if self.browser_state_dir.exists():
185
+ shutil.rmtree(self.browser_state_dir)
186
+ self.browser_state_dir.mkdir(parents=True, exist_ok=True)
187
+ print(" βœ… Cleared browser data")
188
+
189
+ return True
190
+ except Exception as e:
191
+ print(f" ❌ Error clearing auth: {e}")
192
+ return False
193
+
194
+ def re_auth(self, headless: bool = False, timeout_minutes: int = 10) -> bool:
195
+ """Perform re-authentication (clear and setup)"""
196
+ print("πŸ”„ Starting re-authentication...")
197
+ self.clear_auth()
198
+ return self.setup_auth(headless, timeout_minutes)
199
+
200
+ def validate_auth(self) -> bool:
201
+ """Validate that stored authentication works"""
202
+ if not self.is_authenticated():
203
+ return False
204
+
205
+ print("πŸ” Validating authentication...")
206
+
207
+ playwright = None
208
+ context = None
209
+
210
+ try:
211
+ playwright = sync_playwright().start()
212
+
213
+ context = BrowserFactory.launch_persistent_context(
214
+ playwright,
215
+ headless=True
216
+ )
217
+
218
+ page = context.new_page()
219
+ page.goto("https://notebooklm.google.com", wait_until="domcontentloaded", timeout=30000)
220
+
221
+ if "notebooklm.google.com" in page.url and "accounts.google.com" not in page.url:
222
+ print(" βœ… Authentication is valid")
223
+ return True
224
+ else:
225
+ print(" ❌ Authentication is invalid (redirected to login)")
226
+ return False
227
+
228
+ except Exception as e:
229
+ print(f" ❌ Validation failed: {e}")
230
+ return False
231
+
232
+ finally:
233
+ if context:
234
+ try:
235
+ context.close()
236
+ except Exception:
237
+ pass
238
+ if playwright:
239
+ try:
240
+ playwright.stop()
241
+ except Exception:
242
+ pass
243
+
244
+
245
+ def main():
246
+ """Command-line interface for authentication management"""
247
+ parser = argparse.ArgumentParser(description='Manage NotebookLM authentication')
248
+
249
+ subparsers = parser.add_subparsers(dest='command', help='Commands')
250
+
251
+ setup_parser = subparsers.add_parser('setup', help='Setup authentication')
252
+ setup_parser.add_argument('--headless', action='store_true', help='Run in headless mode')
253
+ setup_parser.add_argument('--timeout', type=float, default=10, help='Login timeout in minutes (default: 10)')
254
+
255
+ subparsers.add_parser('status', help='Check authentication status')
256
+ subparsers.add_parser('validate', help='Validate authentication')
257
+ subparsers.add_parser('clear', help='Clear authentication')
258
+
259
+ reauth_parser = subparsers.add_parser('reauth', help='Re-authenticate (clear + setup)')
260
+ reauth_parser.add_argument('--timeout', type=float, default=10, help='Login timeout in minutes (default: 10)')
261
+
262
+ args = parser.parse_args()
263
+
264
+ auth = AuthManager()
265
+
266
+ if args.command == 'setup':
267
+ if auth.setup_auth(headless=args.headless, timeout_minutes=args.timeout):
268
+ print("\nβœ… Authentication setup complete!")
269
+ print("You can now use ask_question.py to query NotebookLM")
270
+ else:
271
+ print("\n❌ Authentication setup failed")
272
+ exit(1)
273
+
274
+ elif args.command == 'status':
275
+ info = auth.get_auth_info()
276
+ print("\nπŸ” Authentication Status:")
277
+ print(f" Authenticated: {'Yes' if info['authenticated'] else 'No'}")
278
+ if info.get('state_age_hours'):
279
+ print(f" State age: {info['state_age_hours']:.1f} hours")
280
+ if info.get('authenticated_at_iso'):
281
+ print(f" Last auth: {info['authenticated_at_iso']}")
282
+ print(f" State file: {info['state_file']}")
283
+
284
+ elif args.command == 'validate':
285
+ if auth.validate_auth():
286
+ print("Authentication is valid and working")
287
+ else:
288
+ print("Authentication is invalid or expired")
289
+ print("Run: auth_manager.py setup")
290
+
291
+ elif args.command == 'clear':
292
+ if auth.clear_auth():
293
+ print("Authentication cleared")
294
+
295
+ elif args.command == 'reauth':
296
+ if auth.re_auth(timeout_minutes=args.timeout):
297
+ print("\nβœ… Re-authentication complete!")
298
+ else:
299
+ print("\n❌ Re-authentication failed")
300
+ exit(1)
301
+
302
+ else:
303
+ parser.print_help()
304
+
305
+
306
+ if __name__ == "__main__":
307
+ main()
@@ -0,0 +1,101 @@
1
+ """
2
+ Browser Utilities for NotebookLM RAG Skill
3
+ Handles browser launching, stealth features, and common interactions
4
+ Adapted from PleasePrompto/notebooklm-skill (MIT License)
5
+ """
6
+
7
+ import json
8
+ import time
9
+ import random
10
+ from typing import Optional, List
11
+
12
+ from patchright.sync_api import Playwright, BrowserContext, Page
13
+ from config import BROWSER_PROFILE_DIR, STATE_FILE, BROWSER_ARGS, USER_AGENT
14
+
15
+
16
+ class BrowserFactory:
17
+ """Factory for creating configured browser contexts"""
18
+
19
+ @staticmethod
20
+ def launch_persistent_context(
21
+ playwright: Playwright,
22
+ headless: bool = True,
23
+ user_data_dir: str = str(BROWSER_PROFILE_DIR)
24
+ ) -> BrowserContext:
25
+ """
26
+ Launch a persistent browser context with anti-detection features
27
+ and cookie workaround.
28
+ """
29
+ context = playwright.chromium.launch_persistent_context(
30
+ user_data_dir=user_data_dir,
31
+ channel="chrome",
32
+ headless=headless,
33
+ no_viewport=True,
34
+ ignore_default_args=["--enable-automation"],
35
+ user_agent=USER_AGENT,
36
+ args=BROWSER_ARGS
37
+ )
38
+
39
+ # Cookie Workaround for Playwright bug #36139
40
+ BrowserFactory._inject_cookies(context)
41
+
42
+ return context
43
+
44
+ @staticmethod
45
+ def _inject_cookies(context: BrowserContext):
46
+ """Inject cookies from state.json if available"""
47
+ if STATE_FILE.exists():
48
+ try:
49
+ with open(STATE_FILE, 'r') as f:
50
+ state = json.load(f)
51
+ if 'cookies' in state and len(state['cookies']) > 0:
52
+ context.add_cookies(state['cookies'])
53
+ except Exception as e:
54
+ print(f" ⚠️ Could not load state.json: {e}")
55
+
56
+
57
+ class StealthUtils:
58
+ """Human-like interaction utilities"""
59
+
60
+ @staticmethod
61
+ def random_delay(min_ms: int = 100, max_ms: int = 500):
62
+ """Add random delay"""
63
+ time.sleep(random.uniform(min_ms / 1000, max_ms / 1000))
64
+
65
+ @staticmethod
66
+ def human_type(page: Page, selector: str, text: str, wpm_min: int = 320, wpm_max: int = 480):
67
+ """Type with human-like speed"""
68
+ element = page.query_selector(selector)
69
+ if not element:
70
+ try:
71
+ element = page.wait_for_selector(selector, timeout=2000)
72
+ except:
73
+ pass
74
+
75
+ if not element:
76
+ print(f"⚠️ Element not found for typing: {selector}")
77
+ return
78
+
79
+ element.click()
80
+
81
+ for char in text:
82
+ element.type(char, delay=random.uniform(25, 75))
83
+ if random.random() < 0.05:
84
+ time.sleep(random.uniform(0.15, 0.4))
85
+
86
+ @staticmethod
87
+ def realistic_click(page: Page, selector: str):
88
+ """Click with realistic movement"""
89
+ element = page.query_selector(selector)
90
+ if not element:
91
+ return
92
+
93
+ box = element.bounding_box()
94
+ if box:
95
+ x = box['x'] + box['width'] / 2
96
+ y = box['y'] + box['height'] / 2
97
+ page.mouse.move(x, y, steps=5)
98
+
99
+ StealthUtils.random_delay(100, 300)
100
+ element.click()
101
+ StealthUtils.random_delay(100, 300)
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Data Cleanup Manager for NotebookLM RAG Skill
4
+ Handles cleaning browser data, auth state, and optionally library
5
+ """
6
+
7
+ import argparse
8
+ import shutil
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ sys.path.insert(0, str(Path(__file__).parent))
13
+ from config import DATA_DIR, BROWSER_STATE_DIR, STATE_FILE, AUTH_INFO_FILE, LIBRARY_FILE
14
+
15
+
16
+ def preview_cleanup(preserve_library: bool = False):
17
+ """Show what would be cleaned up"""
18
+ print("πŸ” Cleanup Preview:")
19
+ items = []
20
+
21
+ if BROWSER_STATE_DIR.exists():
22
+ size = sum(f.stat().st_size for f in BROWSER_STATE_DIR.rglob('*') if f.is_file())
23
+ items.append(f" Browser state: {size / 1024:.1f} KB")
24
+
25
+ if STATE_FILE.exists():
26
+ items.append(f" State file: {STATE_FILE}")
27
+
28
+ if AUTH_INFO_FILE.exists():
29
+ items.append(f" Auth info: {AUTH_INFO_FILE}")
30
+
31
+ if not preserve_library and LIBRARY_FILE.exists():
32
+ items.append(f" Library: {LIBRARY_FILE}")
33
+ elif preserve_library and LIBRARY_FILE.exists():
34
+ items.append(f" Library: PRESERVED βœ“")
35
+
36
+ if items:
37
+ for item in items:
38
+ print(item)
39
+ else:
40
+ print(" Nothing to clean up")
41
+
42
+ return len(items) > 0
43
+
44
+
45
+ def execute_cleanup(preserve_library: bool = False):
46
+ """Execute cleanup"""
47
+ print("πŸ—‘οΈ Cleaning up...")
48
+
49
+ if BROWSER_STATE_DIR.exists():
50
+ shutil.rmtree(BROWSER_STATE_DIR)
51
+ BROWSER_STATE_DIR.mkdir(parents=True, exist_ok=True)
52
+ print(" βœ… Cleared browser state")
53
+
54
+ if STATE_FILE.exists():
55
+ STATE_FILE.unlink()
56
+ print(" βœ… Removed state file")
57
+
58
+ if AUTH_INFO_FILE.exists():
59
+ AUTH_INFO_FILE.unlink()
60
+ print(" βœ… Removed auth info")
61
+
62
+ if not preserve_library and LIBRARY_FILE.exists():
63
+ LIBRARY_FILE.unlink()
64
+ print(" βœ… Removed library")
65
+ elif preserve_library:
66
+ print(" πŸ“š Library preserved")
67
+
68
+ print(" βœ… Cleanup complete!")
69
+
70
+
71
+ def main():
72
+ parser = argparse.ArgumentParser(description='Clean up NotebookLM RAG skill data')
73
+ parser.add_argument('--confirm', action='store_true', help='Execute cleanup (default: preview only)')
74
+ parser.add_argument('--preserve-library', action='store_true', help='Keep notebook library')
75
+
76
+ args = parser.parse_args()
77
+
78
+ if args.confirm:
79
+ execute_cleanup(preserve_library=args.preserve_library)
80
+ else:
81
+ has_items = preview_cleanup(preserve_library=args.preserve_library)
82
+ if has_items:
83
+ print("\nRun with --confirm to execute cleanup")
84
+
85
+
86
+ if __name__ == "__main__":
87
+ main()
@@ -0,0 +1,45 @@
1
+ """
2
+ Configuration for NotebookLM RAG Skill
3
+ Centralizes constants, selectors, and paths
4
+ Adapted from PleasePrompto/notebooklm-skill (MIT License)
5
+ """
6
+
7
+ from pathlib import Path
8
+
9
+ # Paths
10
+ SKILL_DIR = Path(__file__).parent.parent
11
+ DATA_DIR = SKILL_DIR / "data"
12
+ BROWSER_STATE_DIR = DATA_DIR / "browser_state"
13
+ BROWSER_PROFILE_DIR = BROWSER_STATE_DIR / "browser_profile"
14
+ STATE_FILE = BROWSER_STATE_DIR / "state.json"
15
+ AUTH_INFO_FILE = DATA_DIR / "auth_info.json"
16
+ LIBRARY_FILE = DATA_DIR / "library.json"
17
+
18
+ # NotebookLM Selectors
19
+ QUERY_INPUT_SELECTORS = [
20
+ "textarea.query-box-input", # Primary
21
+ 'textarea[aria-label="Feld fΓΌr Anfragen"]', # Fallback German
22
+ 'textarea[aria-label="Input for queries"]', # Fallback English
23
+ ]
24
+
25
+ RESPONSE_SELECTORS = [
26
+ ".to-user-container .message-text-content", # Primary
27
+ "[data-message-author='bot']",
28
+ "[data-message-author='assistant']",
29
+ ]
30
+
31
+ # Browser Configuration
32
+ BROWSER_ARGS = [
33
+ '--disable-blink-features=AutomationControlled',
34
+ '--disable-dev-shm-usage',
35
+ '--no-sandbox',
36
+ '--no-first-run',
37
+ '--no-default-browser-check'
38
+ ]
39
+
40
+ USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
41
+
42
+ # Timeouts
43
+ LOGIN_TIMEOUT_MINUTES = 10
44
+ QUERY_TIMEOUT_SECONDS = 120
45
+ PAGE_LOAD_TIMEOUT = 30000