nextog-cli 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 (51) hide show
  1. nextog/__init__.py +4 -0
  2. nextog/cli.py +545 -0
  3. nextog/config/__init__.py +1 -0
  4. nextog/config/settings.py +132 -0
  5. nextog/core/__init__.py +1 -0
  6. nextog/core/engine.py +193 -0
  7. nextog/core/permissions.py +129 -0
  8. nextog/core/privacy.py +130 -0
  9. nextog/core/reporter.py +204 -0
  10. nextog/core/runner.py +236 -0
  11. nextog/data/__init__.py +1 -0
  12. nextog/data/local_db.py +367 -0
  13. nextog/data/models.py +72 -0
  14. nextog/data/sync.py +65 -0
  15. nextog/engines/__init__.py +1 -0
  16. nextog/engines/api/__init__.py +1 -0
  17. nextog/engines/api/graphql.py +54 -0
  18. nextog/engines/api/rest.py +346 -0
  19. nextog/engines/api/websocket.py +59 -0
  20. nextog/engines/embedded/__init__.py +1 -0
  21. nextog/engines/embedded/firmware.py +53 -0
  22. nextog/engines/embedded/hardware.py +330 -0
  23. nextog/engines/mobile/__init__.py +1 -0
  24. nextog/engines/mobile/android.py +333 -0
  25. nextog/engines/mobile/cross.py +48 -0
  26. nextog/engines/mobile/ios.py +46 -0
  27. nextog/engines/system/__init__.py +1 -0
  28. nextog/engines/system/load.py +121 -0
  29. nextog/engines/system/performance.py +128 -0
  30. nextog/engines/system/security.py +170 -0
  31. nextog/engines/web/__init__.py +1 -0
  32. nextog/engines/web/accessibility.py +191 -0
  33. nextog/engines/web/browser.py +387 -0
  34. nextog/engines/web/elements.py +285 -0
  35. nextog/engines/web/responsive.py +79 -0
  36. nextog/live/__init__.py +1 -0
  37. nextog/live/dashboard.py +30 -0
  38. nextog/live/panel.py +325 -0
  39. nextog/reports/__init__.py +1359 -0
  40. nextog/training/__init__.py +1 -0
  41. nextog/training/learner.py +269 -0
  42. nextog/training/patterns.py +102 -0
  43. nextog/utils/__init__.py +1 -0
  44. nextog/utils/helpers.py +91 -0
  45. nextog/utils/logger.py +37 -0
  46. nextog/utils/validators.py +98 -0
  47. nextog_cli-1.0.0.dist-info/METADATA +344 -0
  48. nextog_cli-1.0.0.dist-info/RECORD +51 -0
  49. nextog_cli-1.0.0.dist-info/WHEEL +5 -0
  50. nextog_cli-1.0.0.dist-info/entry_points.txt +2 -0
  51. nextog_cli-1.0.0.dist-info/top_level.txt +1 -0
nextog/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """nextOG CLI - Full QA Testing Automation Tool"""
2
+
3
+ __version__ = "1.0.0"
4
+ __app_name__ = "nextOG CLI"
nextog/cli.py ADDED
@@ -0,0 +1,545 @@
1
+ """
2
+ nextOG CLI - Main Entry Point
3
+ Full QA Testing Automation CLI Tool
4
+ """
5
+
6
+ import typer
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.table import Table
10
+ from rich.progress import Progress, SpinnerColumn, TextColumn
11
+ from typing import Optional
12
+ from pathlib import Path
13
+
14
+ from nextog.core.engine import TestEngine
15
+ from nextog.core.permissions import PermissionManager
16
+ from nextog.core.privacy import PrivacyEngine
17
+ from nextog.config.settings import Settings
18
+ from nextog.data.local_db import LocalDatabase
19
+ from nextog.reports import IndustrialReportGenerator
20
+
21
+ app = typer.Typer(
22
+ name="nextog",
23
+ help="🚀 nextOG CLI - Full QA Testing Automation Tool",
24
+ add_completion=True,
25
+ rich_markup_mode="rich",
26
+ )
27
+ console = Console()
28
+ settings = Settings()
29
+ db = LocalDatabase()
30
+ permissions = PermissionManager(db)
31
+ privacy = PrivacyEngine(db)
32
+ report_generator = IndustrialReportGenerator(db, settings, privacy)
33
+
34
+
35
+ # ──────────────────────────────────────────────────────────────
36
+ # CLI Callback - Version & Config
37
+ # ──────────────────────────────────────────────────────────────
38
+ def version_callback(value: bool):
39
+ if value:
40
+ console.print(f"[bold green]nextOG CLI[/bold green] v{__import__('nextog').__version__}")
41
+ raise typer.Exit()
42
+
43
+
44
+ @app.callback()
45
+ def main(
46
+ version: Optional[bool] = typer.Option(
47
+ None, "--version", "-v", callback=version_callback, is_eager=True
48
+ ),
49
+ config: Optional[Path] = typer.Option(
50
+ None, "--config", "-c", help="Path to config file"
51
+ ),
52
+ verbose: bool = typer.Option(False, "--verbose", help="Enable verbose output"),
53
+ ):
54
+ """nextOG CLI - Full QA Testing Automation Tool"""
55
+ if config:
56
+ settings.load_from_file(config)
57
+ if verbose:
58
+ settings.set_verbose(True)
59
+
60
+
61
+ # ──────────────────────────────────────────────────────────────
62
+ # INIT Command - Initialize Project
63
+ # ──────────────────────────────────────────────────────────────
64
+ @app.command()
65
+ def init(
66
+ project_name: str = typer.Argument(..., help="Project name"),
67
+ template: str = typer.Option("full", help="Template: full, web, mobile, api, minimal"),
68
+ ):
69
+ """Initialize a new nextOG testing project"""
70
+ console.print(Panel(f"[bold]Initializing project: {project_name}[/bold]"))
71
+
72
+ from nextog.core.runner import ProjectInitializer
73
+ initializer = ProjectInitializer(settings, db)
74
+ initializer.create_project(project_name, template)
75
+
76
+ console.print("[green]✓[/green] Project initialized successfully!")
77
+ console.print(f" → Config: .nextog/config.yaml")
78
+ console.print(f" → Tests: .nextog/tests/")
79
+ console.print(f" → Run: [bold]nextog test web --url <url>[/bold]")
80
+
81
+
82
+ # ──────────────────────────────────────────────────────────────
83
+ # TEST Command - Run Tests
84
+ # ──────────────────────────────────────────────────────────────
85
+ test_app = typer.Typer(help="Run various types of tests")
86
+ app.add_typer(test_app, name="test")
87
+
88
+
89
+ @test_app.command("web")
90
+ def test_web(
91
+ url: str = typer.Option(..., "--url", "-u", help="URL to test"),
92
+ browser: str = typer.Option("chromium", help="Browser: chromium, firefox, webkit"),
93
+ responsive: bool = typer.Option(True, help="Test responsive design"),
94
+ accessibility: bool = typer.Option(True, help="Test accessibility"),
95
+ performance: bool = typer.Option(False, help="Run performance tests"),
96
+ coverage_target: int = typer.Option(90, help="Target coverage percentage"),
97
+ headless: bool = typer.Option(True, help="Run in headless mode"),
98
+ ):
99
+ """Run web application tests"""
100
+ console.print(Panel(f"[bold blue]Web Testing:[/bold blue] {url}"))
101
+
102
+ from nextog.engines.web.browser import WebTestEngine
103
+ engine = WebTestEngine(settings, db, privacy)
104
+
105
+ with Progress(
106
+ SpinnerColumn(),
107
+ TextColumn("[progress.description]{task.description}"),
108
+ console=console,
109
+ ) as progress:
110
+ task = progress.add_task("Running web tests...", total=None)
111
+ results = engine.run_tests(
112
+ url=url,
113
+ browser=browser,
114
+ responsive=responsive,
115
+ accessibility=accessibility,
116
+ performance=performance,
117
+ coverage_target=coverage_target,
118
+ headless=headless,
119
+ )
120
+ progress.update(task, completed=True)
121
+
122
+ _display_results(results, "Web Tests")
123
+
124
+ # Auto-generate industrial Excel report
125
+ _auto_generate_report(results, "web")
126
+
127
+
128
+ @test_app.command("mobile")
129
+ def test_mobile(
130
+ app_path: str = typer.Option(..., "--app", "-a", help="Path to app (.apk/.ipa)"),
131
+ platform: str = typer.Option("android", help="Platform: android, ios"),
132
+ device: str = typer.Option("emulator", help="Device or emulator"),
133
+ coverage_target: int = typer.Option(90, help="Target coverage percentage"),
134
+ ):
135
+ """Run mobile application tests"""
136
+ console.print(Panel(f"[bold magenta]Mobile Testing:[/bold magenta] {app_path}"))
137
+
138
+ from nextog.engines.mobile.android import MobileTestEngine
139
+ engine = MobileTestEngine(settings, db, privacy)
140
+
141
+ results = engine.run_tests(
142
+ app_path=app_path,
143
+ platform=platform,
144
+ device=device,
145
+ coverage_target=coverage_target,
146
+ )
147
+
148
+ _display_results(results, "Mobile Tests")
149
+
150
+ # Auto-generate industrial Excel report
151
+ _auto_generate_report(results, "mobile")
152
+
153
+
154
+ @test_app.command("api")
155
+ def test_api(
156
+ spec: str = typer.Option(..., "--spec", "-s", help="API spec file (OpenAPI/GraphQL)"),
157
+ base_url: str = typer.Option(..., "--base-url", "-b", help="Base URL for API"),
158
+ auth: Optional[str] = typer.Option(None, help="Auth token or credentials"),
159
+ coverage_target: int = typer.Option(90, help="Target coverage percentage"),
160
+ ):
161
+ """Run API tests"""
162
+ console.print(Panel(f"[bold yellow]API Testing:[/bold yellow] {base_url}"))
163
+
164
+ from nextog.engines.api.rest import APITestEngine
165
+ engine = APITestEngine(settings, db, privacy)
166
+
167
+ results = engine.run_tests(
168
+ spec_file=spec,
169
+ base_url=base_url,
170
+ auth=auth,
171
+ coverage_target=coverage_target,
172
+ )
173
+
174
+ _display_results(results, "API Tests")
175
+
176
+ # Auto-generate industrial Excel report
177
+ _auto_generate_report(results, "api")
178
+
179
+
180
+ @test_app.command("embedded")
181
+ def test_embedded(
182
+ target: str = typer.Option(..., "--target", "-t", help="Target device/IP"),
183
+ protocol: str = typer.Option("mqtt", help="Protocol: mqtt, coap, http, serial"),
184
+ firmware: Optional[str] = typer.Option(None, help="Firmware version"),
185
+ coverage_target: int = typer.Option(90, help="Target coverage percentage"),
186
+ ):
187
+ """Run embedded systems tests"""
188
+ console.print(Panel(f"[bold red]Embedded Systems Testing:[/bold red] {target}"))
189
+
190
+ from nextog.engines.embedded.hardware import EmbeddedTestEngine
191
+ engine = EmbeddedTestEngine(settings, db, privacy)
192
+
193
+ results = engine.run_tests(
194
+ target=target,
195
+ protocol=protocol,
196
+ firmware=firmware,
197
+ coverage_target=coverage_target,
198
+ )
199
+
200
+ _display_results(results, "Embedded Tests")
201
+
202
+ # Auto-generate industrial Excel report
203
+ _auto_generate_report(results, "embedded")
204
+
205
+
206
+ @test_app.command("all")
207
+ def test_all(
208
+ config: str = typer.Option(".nextog/config.yaml", help="Project config file"),
209
+ coverage_target: int = typer.Option(90, help="Target coverage percentage"),
210
+ parallel: bool = typer.Option(True, help="Run tests in parallel"),
211
+ ):
212
+ """Run all configured tests"""
213
+ console.print(Panel("[bold green]Running All Tests[/bold green]"))
214
+
215
+ from nextog.core.runner import TestRunner
216
+ runner = TestRunner(settings, db, privacy, permissions)
217
+
218
+ results = runner.run_all(
219
+ config_path=config,
220
+ coverage_target=coverage_target,
221
+ parallel=parallel,
222
+ )
223
+
224
+ _display_results(results, "All Tests")
225
+
226
+ # Auto-generate industrial Excel report
227
+ _auto_generate_report(results, "full")
228
+
229
+
230
+ # ──────────────────────────────────────────────────────────────
231
+ # COVERAGE Command - Coverage Tracking
232
+ # ──────────────────────────────────────────────────────────────
233
+ @app.command()
234
+ def coverage(
235
+ report: bool = typer.Option(False, help="Generate coverage report"),
236
+ format: str = typer.Option("table", help="Format: table, html, json, pdf"),
237
+ project: Optional[str] = typer.Option(None, help="Project name filter"),
238
+ ):
239
+ """View and manage test coverage"""
240
+ from nextog.core.reporter import CoverageReporter
241
+
242
+ reporter = CoverageReporter(db, settings)
243
+ data = reporter.get_coverage(project)
244
+
245
+ if report:
246
+ output = reporter.generate_report(data, format)
247
+ console.print(f"[green]✓[/green] Report generated: {output}")
248
+ else:
249
+ reporter.display_coverage(data)
250
+
251
+
252
+ # ──────────────────────────────────────────────────────────────
253
+ # LIVE Command - Live Testing Panel
254
+ # ──────────────────────────────────────────────────────────────
255
+ @app.command()
256
+ def live(
257
+ port: int = typer.Option(8080, help="Dashboard port"),
258
+ host: str = typer.Option("127.0.0.1", help="Dashboard host"),
259
+ open_browser: bool = typer.Option(True, help="Open browser automatically"),
260
+ ):
261
+ """Start live testing dashboard"""
262
+ console.print(Panel(f"[bold]Live Dashboard starting on http://{host}:{port}[/bold]"))
263
+
264
+ from nextog.live.panel import LivePanel
265
+ panel = LivePanel(settings, db, privacy)
266
+ panel.start(host=host, port=port, open_browser=open_browser)
267
+
268
+
269
+ # ──────────────────────────────────────────────────────────────
270
+ # USER Command - User & Permission Management
271
+ # ──────────────────────────────────────────────────────────────
272
+ user_app = typer.Typer(help="Manage users and permissions")
273
+ app.add_typer(user_app, name="user")
274
+
275
+
276
+ @user_app.command("create")
277
+ def user_create(
278
+ username: str = typer.Argument(..., help="Username"),
279
+ role: str = typer.Option("tester", help="Role: admin, tester, viewer, ci"),
280
+ email: Optional[str] = typer.Option(None, help="User email"),
281
+ ):
282
+ """Create a new user"""
283
+ permissions.create_user(username, role, email)
284
+ console.print(f"[green]✓[/green] User '{username}' created with role '{role}'")
285
+
286
+
287
+ @user_app.command("list")
288
+ def user_list():
289
+ """List all users"""
290
+ users = permissions.list_users()
291
+ table = Table(title="Users")
292
+ table.add_column("Username", style="cyan")
293
+ table.add_column("Role", style="green")
294
+ table.add_column("Email", style="white")
295
+ table.add_column("Status", style="yellow")
296
+
297
+ for user in users:
298
+ table.add_row(user["username"], user["role"], user["email"] or "-", user["status"])
299
+
300
+ console.print(table)
301
+
302
+
303
+ @user_app.command("role")
304
+ def user_role(
305
+ username: str = typer.Argument(..., help="Username"),
306
+ role: str = typer.Argument(..., help="New role"),
307
+ ):
308
+ """Change user role"""
309
+ permissions.update_role(username, role)
310
+ console.print(f"[green]✓[/green] Role updated for '{username}' → '{role}'")
311
+
312
+
313
+ # ──────────────────────────────────────────────────────────────
314
+ # TRAIN Command - Auto Training
315
+ # ──────────────────────────────────────────────────────────────
316
+ @app.command()
317
+ def train(
318
+ action: str = typer.Option("status", help="Action: status, start, stop, optimize"),
319
+ iterations: int = typer.Option(100, help="Training iterations"),
320
+ ):
321
+ """Manage auto-training from user patterns"""
322
+ from nextog.training.learner import TrainingEngine
323
+
324
+ trainer = TrainingEngine(db, settings)
325
+
326
+ if action == "status":
327
+ status = trainer.get_status()
328
+ console.print(Panel(f"[bold]Training Status[/bold]"))
329
+ console.print(f" Patterns learned: {status['patterns']}")
330
+ console.print(f" Test cases optimized: {status['optimized']}")
331
+ console.print(f" Last trained: {status['last_trained']}")
332
+ console.print(f" Model accuracy: {status['accuracy']}%")
333
+
334
+ elif action == "start":
335
+ console.print("[bold]Starting training...[/bold]")
336
+ trainer.train(iterations=iterations)
337
+ console.print("[green]✓[/green] Training complete!")
338
+
339
+ elif action == "optimize":
340
+ console.print("[bold]Optimizing test cases...[/bold]")
341
+ trainer.optimize_tests()
342
+ console.print("[green]✓[/green] Optimization complete!")
343
+
344
+
345
+ # ──────────────────────────────────────────────────────────────
346
+ # DATA Command - Privacy & Data Management
347
+ # ──────────────────────────────────────────────────────────────
348
+ data_app = typer.Typer(help="Manage local data & privacy")
349
+ app.add_typer(data_app, name="data")
350
+
351
+
352
+ @data_app.command("export")
353
+ def data_export(
354
+ output: str = typer.Option("nextog-export.json", help="Output file"),
355
+ encrypt: bool = typer.Option(True, help="Encrypt exported data"),
356
+ ):
357
+ """Export all local data (privacy-first)"""
358
+ privacy.export_data(output, encrypt)
359
+ console.print(f"[green]✓[/green] Data exported to: {output}")
360
+
361
+
362
+ @data_app.command("purge")
363
+ def data_purge(
364
+ confirm: bool = typer.Option(False, "--yes", help="Skip confirmation"),
365
+ ):
366
+ """Delete ALL local data permanently"""
367
+ if not confirm:
368
+ typer.confirm("⚠️ This will delete ALL local data. Continue?", abort=True)
369
+
370
+ privacy.purge_all_data()
371
+ console.print("[red]✓[/red] All data purged.")
372
+
373
+
374
+ @data_app.command("stats")
375
+ def data_stats():
376
+ """Show data storage statistics"""
377
+ stats = privacy.get_storage_stats()
378
+ console.print(Panel("[bold]Data Storage Stats[/bold]"))
379
+ console.print(f" Database size: {stats['db_size']}")
380
+ console.print(f" Test results: {stats['test_count']}")
381
+ console.print(f" Patterns: {stats['pattern_count']}")
382
+ console.print(f" Encryption: {'✓ AES-256' if stats['encrypted'] else '✗ None'}")
383
+ console.print(f" External sync: {'DISABLED' if not stats['external_sync'] else 'ENABLED'}")
384
+
385
+
386
+ # ──────────────────────────────────────────────────────────────
387
+ # ELEMENTS Command - System Element Detection
388
+ # ──────────────────────────────────────────────────────────────
389
+ @app.command()
390
+ def elements(
391
+ target: str = typer.Argument(..., help="Target URL or app path"),
392
+ explain: bool = typer.Option(True, help="Explain each element's use"),
393
+ output: Optional[str] = typer.Option(None, help="Save to file"),
394
+ ):
395
+ """Detect and explain system elements for testing"""
396
+ from nextog.engines.web.elements import ElementDetector
397
+
398
+ detector = ElementDetector(settings, db)
399
+ found = detector.detect(target, explain=explain)
400
+
401
+ table = Table(title=f"Detected Elements: {target}")
402
+ table.add_column("Element", style="cyan")
403
+ table.add_column("Type", style="green")
404
+ table.add_column("Description", style="white")
405
+ table.add_column("Test Priority", style="yellow")
406
+
407
+ for elem in found:
408
+ table.add_row(elem["name"], elem["type"], elem["description"], elem["priority"])
409
+
410
+ console.print(table)
411
+
412
+ if output:
413
+ detector.save_elements(found, output)
414
+ console.print(f"[green]✓[/green] Elements saved to: {output}")
415
+
416
+
417
+ # ──────────────────────────────────────────────────────────────
418
+ # REPORT Command - Generate Excel Reports
419
+ # ──────────────────────────────────────────────────────────────
420
+ @app.command()
421
+ def report(
422
+ test_type: str = typer.Option("auto", "--type", "-t", help="Test type: web, mobile, api, embedded, full, auto"),
423
+ last: bool = typer.Option(True, "--last", help="Generate report for last test run"),
424
+ output: Optional[str] = typer.Option(None, "--output", "-o", help="Custom output path"),
425
+ open_file: bool = typer.Option(True, "--open", help="Open report after generation"),
426
+ ):
427
+ """Generate industrial Excel report from test results"""
428
+ console.print(Panel("[bold blue]📊 Generating Industrial Excel Report...[/bold blue]"))
429
+
430
+ # Get latest results from DB
431
+ results = db.get_latest_results()
432
+
433
+ if not results:
434
+ console.print("[yellow]⚠ No test results found. Run tests first.[/yellow]")
435
+ console.print(" → nextog test web --url <url>")
436
+ console.print(" → nextog test mobile --app <app>")
437
+ console.print(" → nextog test api --spec <spec>")
438
+ raise typer.Exit(1)
439
+
440
+ with Progress(
441
+ SpinnerColumn(),
442
+ TextColumn("[progress.description]{task.description}"),
443
+ console=console,
444
+ ) as progress:
445
+ task = progress.add_task("Building Excel report...", total=None)
446
+
447
+ filepath = report_generator.generate_after_test(
448
+ test_results=results.get("results", results),
449
+ test_type=test_type if test_type != "auto" else results.get("engine", "auto"),
450
+ )
451
+ progress.update(task, completed=True)
452
+
453
+ console.print(f"\n[green]✅ Report Generated Successfully![/green]")
454
+ console.print(f" 📄 File: [bold]{filepath}[/bold]")
455
+ console.print(f" 📊 Sheets: 10 (Summary, Details, Coverage, Elements, Defects, Performance, Charts, Recommendations, Environment, Sign-Off)")
456
+ console.print(f" 🔐 Privacy: [green]LOCAL ONLY — No data transferred[/green]")
457
+
458
+ if open_file:
459
+ try:
460
+ import subprocess, platform
461
+ if platform.system() == "Darwin":
462
+ subprocess.run(["open", filepath])
463
+ elif platform.system() == "Windows":
464
+ subprocess.run(["start", filepath], shell=True)
465
+ else:
466
+ subprocess.run(["xdg-open", filepath])
467
+ except Exception:
468
+ pass
469
+
470
+
471
+ # ──────────────────────────────────────────────────────────────
472
+ # Helper Functions
473
+ # ──────────────────────────────────────────────────────────────
474
+ def _auto_generate_report(results: dict, test_type: str):
475
+ """Auto-generate industrial Excel report after any test"""
476
+ try:
477
+ with Progress(
478
+ SpinnerColumn(),
479
+ TextColumn("[progress.description]{task.description}"),
480
+ console=console,
481
+ ) as progress:
482
+ task = progress.add_task("📊 Generating Excel report...", total=None)
483
+
484
+ filepath = report_generator.generate_after_test(
485
+ test_results=results,
486
+ test_type=test_type,
487
+ )
488
+ progress.update(task, completed=True)
489
+
490
+ console.print(f"\n[green]✅ Excel Report Auto-Generated![/green]")
491
+ console.print(f" 📄 [bold]{filepath}[/bold]")
492
+ console.print(f" 📊 10 Sheets: Summary | Details | Coverage | Elements | Defects | Performance | Charts | Recommendations | Environment | Sign-Off")
493
+ console.print(f" 🔐 All data stored locally — Zero external transfer")
494
+
495
+ # Try to auto-open the report
496
+ try:
497
+ import subprocess, platform as plat
498
+ if plat.system() == "Darwin":
499
+ subprocess.run(["open", filepath], timeout=3)
500
+ elif plat.system() == "Windows":
501
+ subprocess.run(["start", filepath], shell=True, timeout=3)
502
+ else:
503
+ subprocess.run(["xdg-open", filepath], timeout=3)
504
+ except Exception:
505
+ pass
506
+
507
+ except Exception as e:
508
+ console.print(f"[yellow]⚠ Could not generate Excel report: {e}[/yellow]")
509
+ console.print("[dim]Install openpyxl: pip install openpyxl[/dim]")
510
+ def _display_results(results: dict, title: str):
511
+ """Display test results in a formatted table"""
512
+ table = Table(title=f"📊 {title} Results")
513
+ table.add_column("Category", style="cyan")
514
+ table.add_column("Total", style="white")
515
+ table.add_column("Passed", style="green")
516
+ table.add_column("Failed", style="red")
517
+ table.add_column("Skipped", style="yellow")
518
+ table.add_column("Coverage", style="bold blue")
519
+
520
+ for category, data in results.get("categories", {}).items():
521
+ table.add_row(
522
+ category,
523
+ str(data["total"]),
524
+ str(data["passed"]),
525
+ str(data["failed"]),
526
+ str(data["skipped"]),
527
+ f"{data['coverage']}%",
528
+ )
529
+
530
+ console.print(table)
531
+
532
+ # Summary
533
+ total_coverage = results.get("total_coverage", 0)
534
+ console.print(f"\n[bold]Total Coverage: {total_coverage}%[/bold]")
535
+
536
+ if total_coverage >= 80:
537
+ console.print("[green]✅ Excellent coverage![/green]")
538
+ elif total_coverage >= 60:
539
+ console.print("[yellow]⚠️ Good coverage, room for improvement[/yellow]")
540
+ else:
541
+ console.print("[red]❌ Low coverage - add more tests[/red]")
542
+
543
+
544
+ if __name__ == "__main__":
545
+ app()
@@ -0,0 +1 @@
1
+ """Configuration modules"""
@@ -0,0 +1,132 @@
1
+ """
2
+ Settings Manager - Configuration management for nextOG CLI
3
+ """
4
+
5
+ from typing import Dict, Any, Optional
6
+ from pathlib import Path
7
+ import yaml
8
+
9
+
10
+ class Settings:
11
+ """Application settings management"""
12
+
13
+ DEFAULTS = {
14
+ "project": "default",
15
+ "version": "1.0",
16
+ "verbose": False,
17
+ "parallel": True,
18
+ "coverage_target": 90,
19
+ "headless": True,
20
+ "default_browser": "chromium",
21
+ "default_platform": "android",
22
+ "default_protocol": "mqtt",
23
+ "live_panel": {
24
+ "host": "127.0.0.1",
25
+ "port": 8080,
26
+ },
27
+ "privacy": {
28
+ "encrypt_local": True,
29
+ "external_sync": False, # NEVER enable
30
+ "auto_backup": True,
31
+ "backup_count": 5,
32
+ },
33
+ "training": {
34
+ "auto_train": True,
35
+ "iterations": 100,
36
+ "pattern_threshold": 3,
37
+ },
38
+ "reporting": {
39
+ "format": "html",
40
+ "include_screenshots": True,
41
+ "include_logs": True,
42
+ },
43
+ "engines": {
44
+ "web": {
45
+ "browser": "chromium",
46
+ "responsive": True,
47
+ "accessibility": True,
48
+ "performance": False,
49
+ "viewports": ["mobile", "tablet", "desktop"],
50
+ },
51
+ "mobile": {
52
+ "platform": "android",
53
+ "device": "emulator",
54
+ "appium_url": "http://127.0.0.1:4723",
55
+ },
56
+ "api": {
57
+ "timeout": 30,
58
+ "retry_count": 3,
59
+ "verify_ssl": True,
60
+ },
61
+ "embedded": {
62
+ "protocol": "mqtt",
63
+ "timeout": 10,
64
+ },
65
+ },
66
+ }
67
+
68
+ def __init__(self):
69
+ self._config: Dict[str, Any] = dict(self.DEFAULTS)
70
+ self._load_global_config()
71
+
72
+ def _load_global_config(self):
73
+ """Load global config from ~/.nextog/config.yaml"""
74
+ global_config = Path.home() / ".nextog" / "config.yaml"
75
+ if global_config.exists():
76
+ with open(global_config) as f:
77
+ user_config = yaml.safe_load(f) or {}
78
+ self._deep_merge(self._config, user_config)
79
+
80
+ def load_from_file(self, path: Path):
81
+ """Load config from file"""
82
+ if path.exists():
83
+ with open(path) as f:
84
+ file_config = yaml.safe_load(f) or {}
85
+ self._deep_merge(self._config, file_config)
86
+
87
+ def save_to_file(self, path: Path):
88
+ """Save config to file"""
89
+ path.parent.mkdir(parents=True, exist_ok=True)
90
+ with open(path, "w") as f:
91
+ yaml.dump(self._config, f, default_flow_style=False)
92
+
93
+ def get(self, key: str, default: Any = None) -> Any:
94
+ """Get a config value by dot-separated key"""
95
+ keys = key.split(".")
96
+ value = self._config
97
+
98
+ for k in keys:
99
+ if isinstance(value, dict) and k in value:
100
+ value = value[k]
101
+ else:
102
+ return default
103
+
104
+ return value
105
+
106
+ def set(self, key: str, value: Any):
107
+ """Set a config value"""
108
+ keys = key.split(".")
109
+ config = self._config
110
+
111
+ for k in keys[:-1]:
112
+ if k not in config:
113
+ config[k] = {}
114
+ config = config[k]
115
+
116
+ config[keys[-1]] = value
117
+
118
+ def set_verbose(self, verbose: bool):
119
+ """Set verbose mode"""
120
+ self._config["verbose"] = verbose
121
+
122
+ def to_dict(self) -> Dict:
123
+ """Get all config as dict"""
124
+ return dict(self._config)
125
+
126
+ def _deep_merge(self, base: Dict, override: Dict):
127
+ """Deep merge override into base"""
128
+ for key, value in override.items():
129
+ if key in base and isinstance(base[key], dict) and isinstance(value, dict):
130
+ self._deep_merge(base[key], value)
131
+ else:
132
+ base[key] = value
@@ -0,0 +1 @@
1
+ """Core modules for nextOG CLI"""