fixos 2.2.8__tar.gz → 2.2.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. {fixos-2.2.8 → fixos-2.2.10}/CHANGELOG.md +19 -0
  2. {fixos-2.2.8 → fixos-2.2.10}/PKG-INFO +1 -1
  3. {fixos-2.2.8 → fixos-2.2.10}/fixos/__init__.py +1 -1
  4. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/cleanup_cmd.py +84 -71
  5. {fixos-2.2.8 → fixos-2.2.10}/fixos/config.py +10 -4
  6. {fixos-2.2.8 → fixos-2.2.10}/fixos/diagnostics/flatpak_analyzer.py +45 -33
  7. {fixos-2.2.8 → fixos-2.2.10}/fixos/diagnostics/storage_analyzer.py +73 -64
  8. {fixos-2.2.8 → fixos-2.2.10}/fixos.egg-info/PKG-INFO +1 -1
  9. {fixos-2.2.8 → fixos-2.2.10}/fixos.egg-info/SOURCES.txt +0 -1
  10. {fixos-2.2.8 → fixos-2.2.10}/fixos.egg-info/top_level.txt +0 -2
  11. {fixos-2.2.8 → fixos-2.2.10}/pyproject.toml +1 -1
  12. fixos-2.2.8/No changes needed for /home/tom/github/wronai/fixOS/agent/__init__.py +0 -8
  13. {fixos-2.2.8 → fixos-2.2.10}/.env.example +0 -0
  14. {fixos-2.2.8 → fixos-2.2.10}/LICENSE +0 -0
  15. {fixos-2.2.8 → fixos-2.2.10}/MANIFEST.in +0 -0
  16. {fixos-2.2.8 → fixos-2.2.10}/README.md +0 -0
  17. {fixos-2.2.8 → fixos-2.2.10}/docker/README.md +0 -0
  18. {fixos-2.2.8 → fixos-2.2.10}/docker/TEST_RESULTS.md +0 -0
  19. {fixos-2.2.8 → fixos-2.2.10}/docker/TEST_RESULTS_V2.md +0 -0
  20. {fixos-2.2.8 → fixos-2.2.10}/docker/alpine/Dockerfile +0 -0
  21. {fixos-2.2.8 → fixos-2.2.10}/docker/arch/Dockerfile +0 -0
  22. {fixos-2.2.8 → fixos-2.2.10}/docker/base/Dockerfile +0 -0
  23. {fixos-2.2.8 → fixos-2.2.10}/docker/broken-audio/Dockerfile +0 -0
  24. {fixos-2.2.8 → fixos-2.2.10}/docker/broken-full/Dockerfile +0 -0
  25. {fixos-2.2.8 → fixos-2.2.10}/docker/broken-network/Dockerfile +0 -0
  26. {fixos-2.2.8 → fixos-2.2.10}/docker/broken-thumbnails/Dockerfile +0 -0
  27. {fixos-2.2.8 → fixos-2.2.10}/docker/debian/Dockerfile +0 -0
  28. {fixos-2.2.8 → fixos-2.2.10}/docker/docker-compose.multi-system.yml +0 -0
  29. {fixos-2.2.8 → fixos-2.2.10}/docker/docker-compose.yml +0 -0
  30. {fixos-2.2.8 → fixos-2.2.10}/docker/fedora/Dockerfile +0 -0
  31. {fixos-2.2.8 → fixos-2.2.10}/docker/test-multi-system.sh +0 -0
  32. {fixos-2.2.8 → fixos-2.2.10}/docker/ubuntu/Dockerfile +0 -0
  33. {fixos-2.2.8 → fixos-2.2.10}/docs/examples/advanced_usage.py +0 -0
  34. {fixos-2.2.8 → fixos-2.2.10}/docs/examples/quickstart.py +0 -0
  35. {fixos-2.2.8 → fixos-2.2.10}/fixos/agent/__init__.py +0 -0
  36. {fixos-2.2.8 → fixos-2.2.10}/fixos/agent/autonomous.py +0 -0
  37. {fixos-2.2.8 → fixos-2.2.10}/fixos/agent/autonomous_session.py +0 -0
  38. {fixos-2.2.8 → fixos-2.2.10}/fixos/agent/hitl.py +0 -0
  39. {fixos-2.2.8 → fixos-2.2.10}/fixos/agent/hitl_session.py +0 -0
  40. {fixos-2.2.8 → fixos-2.2.10}/fixos/anonymizer.py +0 -0
  41. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/__init__.py +0 -0
  42. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/ask_cmd.py +0 -0
  43. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/config_cmd.py +0 -0
  44. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/features_cmd.py +0 -0
  45. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/fix_cmd.py +0 -0
  46. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/history_cmd.py +0 -0
  47. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/main.py +0 -0
  48. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/orchestrate_cmd.py +0 -0
  49. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/profile_cmd.py +0 -0
  50. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/provider_cmd.py +0 -0
  51. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/quickfix_cmd.py +0 -0
  52. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/report_cmd.py +0 -0
  53. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/rollback_cmd.py +0 -0
  54. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/scan_cmd.py +0 -0
  55. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/shared.py +0 -0
  56. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/token_cmd.py +0 -0
  57. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli/watch_cmd.py +0 -0
  58. {fixos-2.2.8 → fixos-2.2.10}/fixos/cli.py +0 -0
  59. {fixos-2.2.8 → fixos-2.2.10}/fixos/diagnostics/__init__.py +0 -0
  60. {fixos-2.2.8 → fixos-2.2.10}/fixos/diagnostics/dev_project_analyzer.py +0 -0
  61. {fixos-2.2.8 → fixos-2.2.10}/fixos/diagnostics/disk_analyzer.py +0 -0
  62. {fixos-2.2.8 → fixos-2.2.10}/fixos/diagnostics/service_cleanup.py +0 -0
  63. {fixos-2.2.8 → fixos-2.2.10}/fixos/diagnostics/service_details.py +0 -0
  64. {fixos-2.2.8 → fixos-2.2.10}/fixos/diagnostics/service_scanner.py +0 -0
  65. {fixos-2.2.8 → fixos-2.2.10}/fixos/diagnostics/system_checks.py +0 -0
  66. {fixos-2.2.8 → fixos-2.2.10}/fixos/features/__init__.py +0 -0
  67. {fixos-2.2.8 → fixos-2.2.10}/fixos/features/auditor.py +0 -0
  68. {fixos-2.2.8 → fixos-2.2.10}/fixos/features/catalog.py +0 -0
  69. {fixos-2.2.8 → fixos-2.2.10}/fixos/features/installer.py +0 -0
  70. {fixos-2.2.8 → fixos-2.2.10}/fixos/features/profiles.py +0 -0
  71. {fixos-2.2.8 → fixos-2.2.10}/fixos/features/renderer.py +0 -0
  72. {fixos-2.2.8 → fixos-2.2.10}/fixos/fixes/__init__.py +0 -0
  73. {fixos-2.2.8 → fixos-2.2.10}/fixos/interactive/__init__.py +0 -0
  74. {fixos-2.2.8 → fixos-2.2.10}/fixos/interactive/cleanup_planner.py +0 -0
  75. {fixos-2.2.8 → fixos-2.2.10}/fixos/llm_shell.py +0 -0
  76. {fixos-2.2.8 → fixos-2.2.10}/fixos/orchestrator/__init__.py +0 -0
  77. {fixos-2.2.8 → fixos-2.2.10}/fixos/orchestrator/executor.py +0 -0
  78. {fixos-2.2.8 → fixos-2.2.10}/fixos/orchestrator/graph.py +0 -0
  79. {fixos-2.2.8 → fixos-2.2.10}/fixos/orchestrator/orchestrator.py +0 -0
  80. {fixos-2.2.8 → fixos-2.2.10}/fixos/orchestrator/rollback.py +0 -0
  81. {fixos-2.2.8 → fixos-2.2.10}/fixos/platform_utils.py +0 -0
  82. {fixos-2.2.8 → fixos-2.2.10}/fixos/plugins/__init__.py +0 -0
  83. {fixos-2.2.8 → fixos-2.2.10}/fixos/plugins/base.py +0 -0
  84. {fixos-2.2.8 → fixos-2.2.10}/fixos/plugins/builtin/__init__.py +0 -0
  85. {fixos-2.2.8 → fixos-2.2.10}/fixos/plugins/builtin/audio.py +0 -0
  86. {fixos-2.2.8 → fixos-2.2.10}/fixos/plugins/builtin/disk.py +0 -0
  87. {fixos-2.2.8 → fixos-2.2.10}/fixos/plugins/builtin/hardware.py +0 -0
  88. {fixos-2.2.8 → fixos-2.2.10}/fixos/plugins/builtin/resources.py +0 -0
  89. {fixos-2.2.8 → fixos-2.2.10}/fixos/plugins/builtin/security.py +0 -0
  90. {fixos-2.2.8 → fixos-2.2.10}/fixos/plugins/builtin/thumbnails.py +0 -0
  91. {fixos-2.2.8 → fixos-2.2.10}/fixos/plugins/registry.py +0 -0
  92. {fixos-2.2.8 → fixos-2.2.10}/fixos/profiles/__init__.py +0 -0
  93. {fixos-2.2.8 → fixos-2.2.10}/fixos/providers/__init__.py +0 -0
  94. {fixos-2.2.8 → fixos-2.2.10}/fixos/providers/llm.py +0 -0
  95. {fixos-2.2.8 → fixos-2.2.10}/fixos/providers/llm_analyzer.py +0 -0
  96. {fixos-2.2.8 → fixos-2.2.10}/fixos/providers/schemas.py +0 -0
  97. {fixos-2.2.8 → fixos-2.2.10}/fixos/system_checks.py +0 -0
  98. {fixos-2.2.8 → fixos-2.2.10}/fixos/utils/__init__.py +0 -0
  99. {fixos-2.2.8 → fixos-2.2.10}/fixos/utils/anonymizer.py +0 -0
  100. {fixos-2.2.8 → fixos-2.2.10}/fixos/utils/terminal.py +0 -0
  101. {fixos-2.2.8 → fixos-2.2.10}/fixos/utils/timeout.py +0 -0
  102. {fixos-2.2.8 → fixos-2.2.10}/fixos/utils/web_search.py +0 -0
  103. {fixos-2.2.8 → fixos-2.2.10}/fixos/watch.py +0 -0
  104. {fixos-2.2.8 → fixos-2.2.10}/fixos.egg-info/dependency_links.txt +0 -0
  105. {fixos-2.2.8 → fixos-2.2.10}/fixos.egg-info/entry_points.txt +0 -0
  106. {fixos-2.2.8 → fixos-2.2.10}/fixos.egg-info/requires.txt +0 -0
  107. {fixos-2.2.8 → fixos-2.2.10}/pytest.ini +0 -0
  108. {fixos-2.2.8 → fixos-2.2.10}/requirements-dev.txt +0 -0
  109. {fixos-2.2.8 → fixos-2.2.10}/requirements.txt +0 -0
  110. {fixos-2.2.8 → fixos-2.2.10}/scripts/pyqual-calibrate.py +0 -0
  111. {fixos-2.2.8 → fixos-2.2.10}/setup.cfg +0 -0
  112. {fixos-2.2.8 → fixos-2.2.10}/setup.py +0 -0
  113. {fixos-2.2.8 → fixos-2.2.10}/tests/__init__.py +0 -0
  114. {fixos-2.2.8 → fixos-2.2.10}/tests/conftest.py +0 -0
  115. {fixos-2.2.8 → fixos-2.2.10}/tests/e2e/__init__.py +0 -0
  116. {fixos-2.2.8 → fixos-2.2.10}/tests/e2e/test_anonymization_layers.py +0 -0
  117. {fixos-2.2.8 → fixos-2.2.10}/tests/e2e/test_audio_broken.py +0 -0
  118. {fixos-2.2.8 → fixos-2.2.10}/tests/e2e/test_cli.py +0 -0
  119. {fixos-2.2.8 → fixos-2.2.10}/tests/e2e/test_executor.py +0 -0
  120. {fixos-2.2.8 → fixos-2.2.10}/tests/e2e/test_multi_system.py +0 -0
  121. {fixos-2.2.8 → fixos-2.2.10}/tests/e2e/test_network_broken.py +0 -0
  122. {fixos-2.2.8 → fixos-2.2.10}/tests/e2e/test_thumbnails_broken.py +0 -0
  123. {fixos-2.2.8 → fixos-2.2.10}/tests/test_fixos.py +0 -0
  124. {fixos-2.2.8 → fixos-2.2.10}/tests/unit/__init__.py +0 -0
  125. {fixos-2.2.8 → fixos-2.2.10}/tests/unit/test_anonymizer.py +0 -0
  126. {fixos-2.2.8 → fixos-2.2.10}/tests/unit/test_core.py +0 -0
  127. {fixos-2.2.8 → fixos-2.2.10}/tests/unit/test_executor.py +0 -0
  128. {fixos-2.2.8 → fixos-2.2.10}/tests/unit/test_orchestrator.py +0 -0
  129. {fixos-2.2.8 → fixos-2.2.10}/tests/unit/test_service_cleanup.py +0 -0
  130. {fixos-2.2.8 → fixos-2.2.10}/tests/unit/test_service_scanner.py +0 -0
@@ -150,6 +150,25 @@ fix(goal): code analysis engine
150
150
  - **refactor(cli):** Usunięto zduplikowany kod ujednolicając funkcje analizy dysku do wspólnego helpera `_run_disk_analysis`.
151
151
  - **refactor(ui):** Usunięto ikony Unicode z CLI i sformatowano wyjście `stderr` oraz standardowego logowania na czysty kod Markdown dla poprawy czytelności w oknach terminalowych.
152
152
 
153
+ ## [2.2.10] - 2026-04-09
154
+
155
+ ### Other
156
+ - Update No changes needed for /home/tom/github/wronai/fixOS/agent/__init__.py
157
+ - Update fixos/cli/cleanup_cmd.py
158
+ - Update fixos/config.py
159
+ - Update fixos/diagnostics/flatpak_analyzer.py
160
+ - Update fixos/diagnostics/storage_analyzer.py
161
+
162
+ ## [2.2.9] - 2026-04-04
163
+
164
+ ### Docs
165
+ - Update .pyqual/report.md
166
+
167
+ ### Other
168
+ - Update .pyqual/pipeline.db
169
+ - Update VERSION
170
+ - Update fixos/__init__.py
171
+
153
172
  ## [2.2.7] - 2026-04-04
154
173
 
155
174
  ### Docs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.8
3
+ Version: 2.2.10
4
4
  Summary: AI-powered Linux/Windows diagnostics and repair – audio, hardware, system issues
5
5
  Home-page: https://github.com/wronai/fixos
6
6
  Author: fixos contributors
@@ -1,2 +1,2 @@
1
1
  """fixos – AI-powered Linux/Windows diagnostics and repair."""
2
- __version__ = "2.2.8"
2
+ __version__ = "2.2.10"
@@ -3,13 +3,26 @@ Cleanup command for fixOS CLI - service data cleanup with detailed flatpak suppo
3
3
  """
4
4
  import click
5
5
  import subprocess
6
- from fixos.cli.shared import BANNER
7
6
  from fixos.diagnostics.service_scanner import ServiceDataScanner
8
- from fixos.config import FixOsConfig
7
+
8
+ CONSTANT_3 = 3
9
+ CONSTANT_4 = 4
10
+ CONSTANT_5 = 5
11
+ CONSTANT_7 = 7
12
+ CONSTANT_9 = 9
13
+ CONSTANT_20 = 20
14
+ CONSTANT_30 = 30
15
+ CONSTANT_50 = 50
16
+ CONSTANT_60 = 60
17
+ CONSTANT_90 = 90
18
+ CONSTANT_120 = 120
19
+ CONSTANT_300 = 300
20
+ CONSTANT_500 = 500
21
+ CONSTANT_1024 = 1024
9
22
 
10
23
 
11
24
  @click.command("cleanup")
12
- @click.option("--threshold", "-t", default=500, type=int,
25
+ @click.option("--threshold", "-t", default=CONSTANT_500, type=int,
13
26
  help="Próg wielkości w MB (domyślnie 500MB)")
14
27
  @click.option("--services", "-s", default=None,
15
28
  help="Usługi do przeskanowania: docker,ollama,npm,pip,... (domyślnie wszystkie)")
@@ -97,7 +110,7 @@ def cleanup_services(threshold, services, json_output, cleanup, dry_run, list_on
97
110
  def _display_cleanup_summary(plan: dict, threshold: int) -> None:
98
111
  """Display cleanup plan summary header."""
99
112
  click.echo(click.style(f"\nSkanowanie usług (próg: {threshold} MB)...", fg="cyan"))
100
- click.echo(click.style("═" * 60, fg="cyan"))
113
+ click.echo(click.style("═" * CONSTANT_60, fg="cyan"))
101
114
 
102
115
  if plan["services_found"] == 0:
103
116
  click.echo(click.style("\nNie znaleziono usług powyżej progu.", fg="green"))
@@ -128,7 +141,7 @@ def _display_service_item(svc: dict) -> None:
128
141
  elif svc["service_type"] == "ollama" and svc["details"].get("models"):
129
142
  models = svc["details"]["models"]
130
143
  if models:
131
- click.echo(f" Modele: {', '.join(models[:3])}{'...' if len(models) > 3 else ''}")
144
+ click.echo(f" Modele: {', '.join(models[:CONSTANT_3])}{'...' if len(models) > CONSTANT_3 else ''}")
132
145
  click.echo()
133
146
 
134
147
 
@@ -274,9 +287,9 @@ def _cleanup_flatpak_detailed(scanner, json_output: bool, dry_run: bool):
274
287
  return
275
288
 
276
289
  # Wyświetl menu z opcjami
277
- click.echo("\n" + click.style("="*60, fg="cyan"))
290
+ click.echo("\n" + click.style("="*CONSTANT_60, fg="cyan"))
278
291
  click.echo(click.style("📋 WYBIERZ OPCJE DO WYKONANIA", fg="cyan", bold=True))
279
- click.echo(click.style("="*60, fg="cyan"))
292
+ click.echo(click.style("="*CONSTANT_60, fg="cyan"))
280
293
 
281
294
  if dry_run:
282
295
  click.echo(click.style("\n[TRYB DRY-RUN] - brak faktycznych zmian\n", fg="yellow"))
@@ -302,9 +315,9 @@ def _cleanup_flatpak_detailed(scanner, json_output: bool, dry_run: bool):
302
315
  click.echo(f" {click.style(f'Elementów: {len(rec["items"])}', fg='white', dim=True)}")
303
316
 
304
317
  # Podsumowanie potencjalnych korzyści
305
- click.echo("\n" + click.style("-"*60, fg="cyan"))
318
+ click.echo("\n" + click.style("-"*CONSTANT_60, fg="cyan"))
306
319
  click.echo(f"💰 {click.style('ŁĄCZNA POTENCJALNA KORZYŚĆ:', fg='green', bold=True)} ~{_format_bytes(total_potential_savings)}")
307
- click.echo(click.style("-"*60, fg="cyan"))
320
+ click.echo(click.style("-"*CONSTANT_60, fg="cyan"))
308
321
 
309
322
  # Menu wyboru
310
323
  click.echo(f"\n{click.style('Dostępne opcje:', fg='white', bold=True)}")
@@ -346,9 +359,9 @@ def _cleanup_flatpak_detailed(scanner, json_output: bool, dry_run: bool):
346
359
  "space_reclaimed": 0,
347
360
  }
348
361
 
349
- click.echo("\n" + click.style("="*60, fg="cyan"))
362
+ click.echo("\n" + click.style("="*CONSTANT_60, fg="cyan"))
350
363
  click.echo(click.style("🚀 WYKONYWANIE WYBRANYCH AKCJI", fg="cyan", bold=True))
351
- click.echo(click.style("="*60, fg="cyan") + "\n")
364
+ click.echo(click.style("="*CONSTANT_60, fg="cyan") + "\n")
352
365
 
353
366
  for idx in selected_indices:
354
367
  rec = recommendations[idx]
@@ -378,27 +391,27 @@ def _cleanup_flatpak_detailed(scanner, json_output: bool, dry_run: bool):
378
391
  click.echo(click.style(f" ❌ Błąd: {result.get('error', 'Unknown error')}", fg="red"))
379
392
 
380
393
  # Podsumowanie końcowe
381
- click.echo("\n" + click.style("="*60, fg="cyan"))
394
+ click.echo("\n" + click.style("="*CONSTANT_60, fg="cyan"))
382
395
  click.echo(click.style("📊 PODSUMOWANIE", fg="cyan", bold=True))
383
- click.echo(click.style("="*60, fg="cyan"))
396
+ click.echo(click.style("="*CONSTANT_60, fg="cyan"))
384
397
  click.echo(f" ✅ Wykonano: {len(results['executed'])}")
385
398
  click.echo(f" ⏭️ Pominięto: {len(results['skipped'])}")
386
399
  click.echo(f" ❌ Błędy: {len(results['failed'])}")
387
400
 
388
- freed_gb = results['space_reclaimed'] / (1024**3)
401
+ freed_gb = results['space_reclaimed'] / (CONSTANT_1024**CONSTANT_3)
389
402
  if dry_run:
390
403
  click.echo(click.style(f"\n 💰 [DRY-RUN] Zwolniono by: {freed_gb:.2f} GB", fg="cyan"))
391
404
  else:
392
405
  click.echo(click.style(f"\n 💰 Odzyskano: {freed_gb:.2f} GB", fg="green"))
393
406
 
394
- click.echo(click.style("="*60 + "\n", fg="cyan"))
407
+ click.echo(click.style("="*CONSTANT_60 + "\n", fg="cyan"))
395
408
 
396
409
 
397
410
  def _display_flatpak_status(analysis: dict) -> None:
398
411
  """Wyświetl status Flatpak z rzeczywistymi danymi"""
399
- click.echo("\n" + click.style("="*60, fg="cyan"))
412
+ click.echo("\n" + click.style("="*CONSTANT_60, fg="cyan"))
400
413
  click.echo(click.style("📊 STATUS FLATPAK", fg="cyan", bold=True))
401
- click.echo(click.style("="*60, fg="cyan"))
414
+ click.echo(click.style("="*CONSTANT_60, fg="cyan"))
402
415
 
403
416
  # Aplikacje
404
417
  apps_count = len(analysis.get('installed_apps', []))
@@ -424,7 +437,7 @@ def _display_flatpak_status(analysis: dict) -> None:
424
437
  if duplicates:
425
438
  dup_size = sum(d.get('total_size', 0) for d in duplicates)
426
439
  click.echo(f"\n🔄 Duplikaty aplikacji: {click.style(str(len(duplicates)), fg='yellow')} ({_format_bytes(dup_size)})")
427
- for dup in duplicates[:3]:
440
+ for dup in duplicates[:CONSTANT_3]:
428
441
  click.echo(f" • {dup.get('name', '?')} ({dup.get('count', 0)} wersje)")
429
442
 
430
443
  # Nieużywane runtime'y
@@ -442,13 +455,13 @@ def _display_flatpak_status(analysis: dict) -> None:
442
455
 
443
456
  def _display_detailed_recommendations(recommendations: list) -> None:
444
457
  """Wyświetl szczegółowe informacje o każdej rekomendacji"""
445
- click.echo("\n" + click.style("="*60, fg="cyan"))
458
+ click.echo("\n" + click.style("="*CONSTANT_60, fg="cyan"))
446
459
  click.echo(click.style("📖 SZCZEGÓŁY REKOMENDACJI", fg="cyan", bold=True))
447
- click.echo(click.style("="*60, fg="cyan"))
460
+ click.echo(click.style("="*CONSTANT_60, fg="cyan"))
448
461
 
449
462
  for i, rec in enumerate(recommendations, 1):
450
463
  click.echo(f"\n{click.style(f'[{i}]', fg='cyan', bold=True)} {rec['description']}")
451
- click.echo(click.style("-"*50, fg="white", dim=True))
464
+ click.echo(click.style("-"*CONSTANT_50, fg="white", dim=True))
452
465
  click.echo(f"\n{rec['explanation']}")
453
466
 
454
467
  if rec.get('items'):
@@ -495,10 +508,10 @@ def _parse_size_to_bytes(size_str: str) -> int:
495
508
  size_str = size_str.strip().upper().replace(' ', '')
496
509
  multipliers = {
497
510
  'B': 1,
498
- 'KB': 1024,
499
- 'MB': 1024**2,
500
- 'GB': 1024**3,
501
- 'TB': 1024**4,
511
+ 'KB': CONSTANT_1024,
512
+ 'MB': CONSTANT_1024**2,
513
+ 'GB': CONSTANT_1024**CONSTANT_3,
514
+ 'TB': CONSTANT_1024**CONSTANT_4,
502
515
  }
503
516
 
504
517
  for suffix, mult in sorted(multipliers.items(), key=lambda x: -len(x[0])):
@@ -517,9 +530,9 @@ def _parse_size_to_bytes(size_str: str) -> int:
517
530
  def _format_bytes(size_bytes: int) -> str:
518
531
  """Format bytes to human-readable string"""
519
532
  for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
520
- if size_bytes < 1024:
533
+ if size_bytes < CONSTANT_1024:
521
534
  return f"{size_bytes:.1f} {unit}"
522
- size_bytes /= 1024
535
+ size_bytes /= CONSTANT_1024
523
536
  return f"{size_bytes:.1f} PB"
524
537
 
525
538
 
@@ -549,9 +562,9 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
549
562
  return
550
563
 
551
564
  # Show recommendations
552
- click.echo("\n" + click.style("="*60, fg="cyan"))
565
+ click.echo("\n" + click.style("="*CONSTANT_60, fg="cyan"))
553
566
  click.echo(click.style("📋 REKOMENDACJE", fg="cyan", bold=True))
554
- click.echo(click.style("="*60, fg="cyan"))
567
+ click.echo(click.style("="*CONSTANT_60, fg="cyan"))
555
568
 
556
569
  if dry_run:
557
570
  click.echo(click.style("\n[TRYB DRY-RUN] - brak faktycznych zmian\n", fg="yellow"))
@@ -573,15 +586,15 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
573
586
  if medium_items:
574
587
  total_medium = sum(item.size_bytes for item in medium_items)
575
588
  click.echo(f"\n{click.style('🟡 WYMAGA POTWIERDZENIA:', fg='yellow', bold=True)}")
576
- for item in medium_items[:5]:
589
+ for item in medium_items[:CONSTANT_5]:
577
590
  click.echo(f" • {item.name}: {_format_bytes(item.size_bytes)}")
578
591
  click.echo(f" → {click.style(item.cleanup_command, fg='cyan', dim=True)}")
579
592
  click.echo(f"\n 💰 Łącznie: {click.style(_format_bytes(total_medium), fg='yellow')}")
580
593
 
581
594
  # Menu
582
- click.echo("\n" + click.style("-"*60, fg="cyan"))
595
+ click.echo("\n" + click.style("-"*CONSTANT_60, fg="cyan"))
583
596
  click.echo(f"💰 {click.style('ŁĄCZNIE DO ODZYSKANIA:', fg='green', bold=True)} {analysis['total_reclaimable_human']}")
584
- click.echo(click.style("-"*60, fg="cyan"))
597
+ click.echo(click.style("-"*CONSTANT_60, fg="cyan"))
585
598
 
586
599
  # Show category breakdown for dev_projects
587
600
  dev_items = [item for item in analyzer.items if item.category == 'dev_projects']
@@ -639,10 +652,10 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
639
652
  count = len(data["items"])
640
653
  total = data["total"]
641
654
  click.echo(f"\n{click.style(dep_type, fg='yellow', bold=True)}: {count} folderów, {_format_bytes(total)}")
642
- for item in data["items"][:5]:
655
+ for item in data["items"][:CONSTANT_5]:
643
656
  click.echo(f" • {item.path}: {_format_bytes(item.size_bytes)}")
644
- if count > 5:
645
- click.echo(f" ... i {count - 5} więcej")
657
+ if count > CONSTANT_5:
658
+ click.echo(f" ... i {count - CONSTANT_5} więcej")
646
659
  return
647
660
 
648
661
  # Snap package management
@@ -660,7 +673,7 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
660
673
  snap_packages = []
661
674
  for line in lines:
662
675
  parts = line.split()
663
- if len(parts) >= 4:
676
+ if len(parts) >= CONSTANT_4:
664
677
  snap_packages.append({
665
678
  'name': parts[0],
666
679
  'version': parts[1],
@@ -759,7 +772,7 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
759
772
  ['sudo', 'snap', 'remove', pkg['name']],
760
773
  capture_output=True,
761
774
  text=True,
762
- timeout=120,
775
+ timeout=CONSTANT_120,
763
776
  )
764
777
  if result.returncode == 0:
765
778
  click.echo(click.style(" ✅ Odinstalowano", fg="green"))
@@ -786,21 +799,21 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
786
799
  # Show large files
787
800
  if large_files:
788
801
  click.echo(f"\n{click.style('📄 DUŻE PLIKI (>200MB):', fg='red', bold=True)}")
789
- for i, f in enumerate(large_files[:30], 1):
802
+ for i, f in enumerate(large_files[:CONSTANT_30], 1):
790
803
  click.echo(f" [{i:3d}] 📄 {click.style(f['path'], fg='cyan')}: {f['size_human']}")
791
804
 
792
- if len(large_files) > 30:
793
- click.echo(f" ... i {len(large_files) - 30} więcej")
805
+ if len(large_files) > CONSTANT_30:
806
+ click.echo(f" ... i {len(large_files) - CONSTANT_30} więcej")
794
807
 
795
808
  # Show large directories
796
809
  if large_dirs:
797
810
  click.echo(f"\n{click.style('📁 DUŻE FOLDERY (>500MB):', fg='magenta', bold=True)}")
798
811
  offset = len(large_files)
799
- for i, d in enumerate(large_dirs[:20], 1):
812
+ for i, d in enumerate(large_dirs[:CONSTANT_20], 1):
800
813
  click.echo(f" [{offset + i:3d}] 📁 {click.style(d['path'], fg='yellow')}: {d['size_human']}")
801
814
 
802
- if len(large_dirs) > 20:
803
- click.echo(f" ... i {len(large_dirs) - 20} więcej")
815
+ if len(large_dirs) > CONSTANT_20:
816
+ click.echo(f" ... i {len(large_dirs) - CONSTANT_20} więcej")
804
817
 
805
818
  # Get selection
806
819
  total_items = len(large_files) + len(large_dirs)
@@ -818,7 +831,7 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
818
831
  # Handle info:N
819
832
  if nums.startswith('info:'):
820
833
  try:
821
- idx = int(nums[5:])
834
+ idx = int(nums[CONSTANT_5:])
822
835
  if 1 <= idx <= len(large_files):
823
836
  f = large_files[idx - 1]
824
837
  click.echo(f"\n{click.style('📦 SZCZEGÓŁY PLIKU:', fg='yellow', bold=True)}")
@@ -920,12 +933,12 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
920
933
  click.echo(f" {click.style('filter:TYPE', fg='magenta')} - filtruj po typie (np. filter:venv)")
921
934
 
922
935
  # Show numbered list
923
- for i, item in enumerate(analyzer.items[:50], 1):
936
+ for i, item in enumerate(analyzer.items[:CONSTANT_50], 1):
924
937
  risk_icon = {"none": "✅", "low": "🟢", "medium": "🟡", "high": "🔴"}.get(item.risk, "•")
925
938
  click.echo(f" [{i:3d}] {risk_icon} {item.name}: {_format_bytes(item.size_bytes)}")
926
939
 
927
- if len(analyzer.items) > 50:
928
- click.echo(f" ... i {len(analyzer.items) - 50} więcej (użyj filter:TYPE lub top:N)")
940
+ if len(analyzer.items) > CONSTANT_50:
941
+ click.echo(f" ... i {len(analyzer.items) - CONSTANT_50} więcej (użyj filter:TYPE lub top:N)")
929
942
 
930
943
  # Interactive loop for selection
931
944
  items_to_clean = []
@@ -942,7 +955,7 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
942
955
  # Handle info:N
943
956
  if nums.startswith('info:'):
944
957
  try:
945
- idx = int(nums[5:])
958
+ idx = int(nums[CONSTANT_5:])
946
959
  if 1 <= idx <= len(analyzer.items):
947
960
  item = analyzer.items[idx - 1]
948
961
  click.echo(f"\n{click.style('📦 SZCZEGÓŁY ELEMENTU:', fg='yellow', bold=True)}")
@@ -962,7 +975,7 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
962
975
  # Handle path:N
963
976
  if nums.startswith('path:'):
964
977
  try:
965
- idx = int(nums[5:])
978
+ idx = int(nums[CONSTANT_5:])
966
979
  if 1 <= idx <= len(analyzer.items):
967
980
  item = analyzer.items[idx - 1]
968
981
  click.echo(f"\n{click.style('📁 ŚCIEŻKA:', fg='yellow')}")
@@ -982,7 +995,7 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
982
995
  # Handle cmd:N
983
996
  if nums.startswith('cmd:'):
984
997
  try:
985
- idx = int(nums[4:])
998
+ idx = int(nums[CONSTANT_4:])
986
999
  if 1 <= idx <= len(analyzer.items):
987
1000
  item = analyzer.items[idx - 1]
988
1001
  click.echo(f"\n{click.style('🔧 KOMENDA CZYSZCZENIA:', fg='yellow')}")
@@ -1000,7 +1013,7 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
1000
1013
 
1001
1014
  # Handle filter:TYPE
1002
1015
  if nums.startswith('filter:'):
1003
- filter_type = nums[7:].strip()
1016
+ filter_type = nums[CONSTANT_7:].strip()
1004
1017
  filtered_items = []
1005
1018
  for item in analyzer.items:
1006
1019
  item_type = item.name.split(' (')[0] if ' (' in item.name else item.name
@@ -1012,7 +1025,7 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
1012
1025
  continue
1013
1026
 
1014
1027
  click.echo(f"\n{click.style(f'🔍 FILTR: {filter_type}', fg='magenta', bold=True)}")
1015
- for i, item in enumerate(filtered_items[:50], 1):
1028
+ for i, item in enumerate(filtered_items[:CONSTANT_50], 1):
1016
1029
  risk_icon = {"none": "✅", "low": "🟢", "medium": "🟡", "high": "🔴"}.get(item.risk, "•")
1017
1030
  original_idx = analyzer.items.index(item) + 1
1018
1031
  click.echo(f" [{original_idx:3d}] {risk_icon} {item.name}: {_format_bytes(item.size_bytes)}")
@@ -1081,18 +1094,18 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
1081
1094
  items_to_clean = analyzer.items
1082
1095
  elif selection == 'large':
1083
1096
  # Items > 1 GB
1084
- items_to_clean = [item for item in analyzer.items if item.size_bytes > 1024**3]
1097
+ items_to_clean = [item for item in analyzer.items if item.size_bytes > CONSTANT_1024**CONSTANT_3]
1085
1098
  total_large = sum(item.size_bytes for item in items_to_clean)
1086
1099
  click.echo(click.style(f"\n🔴 Duże elementy (>1 GB): {len(items_to_clean)} sztuk, {_format_bytes(total_large)}", fg="red"))
1087
1100
  elif selection == 'huge':
1088
1101
  # Items > 5 GB
1089
- items_to_clean = [item for item in analyzer.items if item.size_bytes > 5 * 1024**3]
1102
+ items_to_clean = [item for item in analyzer.items if item.size_bytes > CONSTANT_5 * CONSTANT_1024**CONSTANT_3]
1090
1103
  total_huge = sum(item.size_bytes for item in items_to_clean)
1091
- click.echo(click.style(f"\n🔴 Bardzo duże elementy (>5 GB): {len(items_to_clean)} sztuk, {_format_bytes(total_huge)}", fg="red"))
1104
+ click.echo(click.style(f"\n🔴 Bardzo duże elementy (>CONSTANT_5 GB): {len(items_to_clean)} sztuk, {_format_bytes(total_huge)}", fg="red"))
1092
1105
  elif selection == 'old':
1093
1106
  # Items not modified in 30+ days
1094
1107
  from datetime import datetime, timedelta
1095
- cutoff = datetime.now() - timedelta(days=30)
1108
+ cutoff = datetime.now() - timedelta(days=CONSTANT_30)
1096
1109
  items_to_clean = [
1097
1110
  item for item in analyzer.items
1098
1111
  if hasattr(item, 'last_modified') and item.last_modified and item.last_modified < cutoff
@@ -1102,7 +1115,7 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
1102
1115
  elif selection == 'stale':
1103
1116
  # Items not modified in 90+ days
1104
1117
  from datetime import datetime, timedelta
1105
- cutoff = datetime.now() - timedelta(days=90)
1118
+ cutoff = datetime.now() - timedelta(days=CONSTANT_90)
1106
1119
  items_to_clean = [
1107
1120
  item for item in analyzer.items
1108
1121
  if hasattr(item, 'last_modified') and item.last_modified and item.last_modified < cutoff
@@ -1112,7 +1125,7 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
1112
1125
  elif selection.startswith('top:'):
1113
1126
  # Top N largest items
1114
1127
  try:
1115
- n = int(selection[4:])
1128
+ n = int(selection[CONSTANT_4:])
1116
1129
  items_to_clean = analyzer.items[:n]
1117
1130
  total_top = sum(item.size_bytes for item in items_to_clean)
1118
1131
  click.echo(click.style(f"\n🏆 Top {n} największych: {_format_bytes(total_top)}", fg="yellow"))
@@ -1121,7 +1134,7 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
1121
1134
  return
1122
1135
  elif selection.startswith('category:'):
1123
1136
  # Filter by category
1124
- selected_category = selection[9:].strip()
1137
+ selected_category = selection[CONSTANT_9:].strip()
1125
1138
  items_to_clean = [item for item in analyzer.items if item.category == selected_category]
1126
1139
 
1127
1140
  if not items_to_clean:
@@ -1134,7 +1147,7 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
1134
1147
  click.echo(click.style(f"\n📁 Kategoria '{selected_category}': {len(items_to_clean)} elementów, {_format_bytes(total_cat)}", fg="blue"))
1135
1148
  elif selection.startswith('type:'):
1136
1149
  # Filter by type (single or multiple)
1137
- selected_types = [t.strip() for t in selection[5:].split(',')]
1150
+ selected_types = [t.strip() for t in selection[CONSTANT_5:].split(',')]
1138
1151
 
1139
1152
  for item in analyzer.items:
1140
1153
  item_type = item.name.split(' (')[0] if ' (' in item.name else item.name
@@ -1154,9 +1167,9 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
1154
1167
  if not items_to_clean:
1155
1168
  return
1156
1169
 
1157
- click.echo("\n" + click.style("="*60, fg="cyan"))
1170
+ click.echo("\n" + click.style("="*CONSTANT_60, fg="cyan"))
1158
1171
  click.echo(click.style("🚀 WYKONYWANIE CZYSZCZENIA", fg="cyan", bold=True))
1159
- click.echo(click.style("="*60, fg="cyan") + "\n")
1172
+ click.echo(click.style("="*CONSTANT_60, fg="cyan") + "\n")
1160
1173
 
1161
1174
  results = {"success": 0, "failed": 0, "space_reclaimed": 0}
1162
1175
 
@@ -1181,7 +1194,7 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
1181
1194
  item.cleanup_command.split(),
1182
1195
  capture_output=True,
1183
1196
  text=True,
1184
- timeout=300,
1197
+ timeout=CONSTANT_300,
1185
1198
  )
1186
1199
  if result.returncode == 0:
1187
1200
  click.echo(click.style(" ✅ Sukces", fg="green"))
@@ -1195,9 +1208,9 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
1195
1208
  results['failed'] += 1
1196
1209
 
1197
1210
  # Summary
1198
- click.echo("\n" + click.style("="*60, fg="cyan"))
1211
+ click.echo("\n" + click.style("="*CONSTANT_60, fg="cyan"))
1199
1212
  click.echo(click.style("📊 PODSUMOWANIE", fg="cyan", bold=True))
1200
- click.echo(click.style("="*60, fg="cyan"))
1213
+ click.echo(click.style("="*CONSTANT_60, fg="cyan"))
1201
1214
  click.echo(f" ✅ Sukces: {results['success']}")
1202
1215
  click.echo(f" ❌ Błędy: {results['failed']}")
1203
1216
 
@@ -1206,18 +1219,18 @@ def _cleanup_full_system(json_output: bool, dry_run: bool):
1206
1219
  else:
1207
1220
  click.echo(click.style(f"\n 💰 Odzyskano: {_format_bytes(results['space_reclaimed'])}", fg="green"))
1208
1221
 
1209
- click.echo(click.style("="*60 + "\n", fg="cyan"))
1222
+ click.echo(click.style("="*CONSTANT_60 + "\n", fg="cyan"))
1210
1223
 
1211
1224
 
1212
1225
  def _parse_size_to_gb(size_str: str) -> float:
1213
1226
  """Parse human-readable size to GB"""
1214
1227
  size_str = size_str.strip().upper()
1215
1228
  multipliers = {
1216
- 'B': 1 / (1024**3),
1217
- 'KB': 1 / (1024**2),
1218
- 'MB': 1 / 1024,
1229
+ 'B': 1 / (CONSTANT_1024**CONSTANT_3),
1230
+ 'KB': 1 / (CONSTANT_1024**2),
1231
+ 'MB': 1 / CONSTANT_1024,
1219
1232
  'GB': 1,
1220
- 'TB': 1024,
1233
+ 'TB': CONSTANT_1024,
1221
1234
  }
1222
1235
 
1223
1236
  for suffix, mult in sorted(multipliers.items(), key=lambda x: -len(x[0])):
@@ -1228,6 +1241,6 @@ def _parse_size_to_gb(size_str: str) -> float:
1228
1241
  return 0
1229
1242
 
1230
1243
  try:
1231
- return float(size_str) / (1024**3)
1244
+ return float(size_str) / (CONSTANT_1024**CONSTANT_3)
1232
1245
  except ValueError:
1233
1246
  return 0
@@ -12,6 +12,12 @@ from dataclasses import dataclass, field
12
12
  from pathlib import Path
13
13
  from typing import Optional
14
14
 
15
+ CONSTANT_4 = 4
16
+ CONSTANT_8 = 8
17
+ CONSTANT_12 = 12
18
+ CONSTANT_384 = 384
19
+ TIMEOUT_3600 = 3600
20
+
15
21
  # Próbuj załadować python-dotenv
16
22
  try:
17
23
  from dotenv import load_dotenv
@@ -162,7 +168,7 @@ class FixOsConfig:
162
168
 
163
169
  # Agent
164
170
  agent_mode: str = "hitl" # hitl | autonomous
165
- session_timeout: int = 3600
171
+ session_timeout: int = TIMEOUT_3600
166
172
  max_auto_fixes: int = 10 # limit dla trybu autonomous
167
173
 
168
174
  # UI
@@ -274,8 +280,8 @@ class FixOsConfig:
274
280
  def summary(self) -> str:
275
281
  """Krótkie podsumowanie konfiguracji (bez klucza API)."""
276
282
  if self.api_key:
277
- if len(self.api_key) > 12:
278
- key_masked = f"{self.api_key[:8]}...{self.api_key[-4:]}"
283
+ if len(self.api_key) > CONSTANT_12:
284
+ key_masked = f"{self.api_key[:CONSTANT_8]}...{self.api_key[-CONSTANT_4:]}"
279
285
  else:
280
286
  key_masked = "***"
281
287
  else:
@@ -411,7 +417,7 @@ def interactive_provider_setup() -> Optional["FixOsConfig"]:
411
417
  env_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
412
418
  env_path.chmod(0o600)
413
419
 
414
- masked = f"{key[:8]}...{key[-4:]}" if len(key) > 12 else "***"
420
+ masked = f"{key[:CONSTANT_8]}...{key[-CONSTANT_4:]}" if len(key) > CONSTANT_12 else "***"
415
421
  print(f" 💾 Zapisano {key_env}={masked} → {env_path}")
416
422
  print()
417
423