fixos 2.2.18__tar.gz → 2.2.20__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 (143) hide show
  1. {fixos-2.2.18 → fixos-2.2.20}/CHANGELOG.md +28 -0
  2. {fixos-2.2.18 → fixos-2.2.20}/PKG-INFO +5 -5
  3. {fixos-2.2.18 → fixos-2.2.20}/README.md +4 -4
  4. {fixos-2.2.18 → fixos-2.2.20}/fixos/__init__.py +1 -1
  5. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/ask_cmd.py +1 -1
  6. fixos-2.2.20/fixos/cli/config_cmd.py +221 -0
  7. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/features_cmd.py +1 -1
  8. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/fix_cmd.py +2 -2
  9. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/main.py +4 -4
  10. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/quickfix_cmd.py +1 -1
  11. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/report_cmd.py +1 -1
  12. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/rollback_cmd.py +4 -4
  13. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/scan_cmd.py +4 -3
  14. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/shared.py +3 -5
  15. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/token_cmd.py +6 -6
  16. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/watch_cmd.py +1 -1
  17. {fixos-2.2.18 → fixos-2.2.20}/fixos/config.py +81 -1
  18. {fixos-2.2.18 → fixos-2.2.20}/fixos.egg-info/PKG-INFO +5 -5
  19. {fixos-2.2.18 → fixos-2.2.20}/pyproject.toml +1 -1
  20. {fixos-2.2.18 → fixos-2.2.20}/setup.py +1 -1
  21. fixos-2.2.18/fixos/cli/config_cmd.py +0 -83
  22. {fixos-2.2.18 → fixos-2.2.20}/.env.example +0 -0
  23. {fixos-2.2.18 → fixos-2.2.20}/LICENSE +0 -0
  24. {fixos-2.2.18 → fixos-2.2.20}/MANIFEST.in +0 -0
  25. {fixos-2.2.18 → fixos-2.2.20}/docker/README.md +0 -0
  26. {fixos-2.2.18 → fixos-2.2.20}/docker/TEST_RESULTS.md +0 -0
  27. {fixos-2.2.18 → fixos-2.2.20}/docker/TEST_RESULTS_V2.md +0 -0
  28. {fixos-2.2.18 → fixos-2.2.20}/docker/alpine/Dockerfile +0 -0
  29. {fixos-2.2.18 → fixos-2.2.20}/docker/arch/Dockerfile +0 -0
  30. {fixos-2.2.18 → fixos-2.2.20}/docker/base/Dockerfile +0 -0
  31. {fixos-2.2.18 → fixos-2.2.20}/docker/broken-audio/Dockerfile +0 -0
  32. {fixos-2.2.18 → fixos-2.2.20}/docker/broken-full/Dockerfile +0 -0
  33. {fixos-2.2.18 → fixos-2.2.20}/docker/broken-network/Dockerfile +0 -0
  34. {fixos-2.2.18 → fixos-2.2.20}/docker/broken-thumbnails/Dockerfile +0 -0
  35. {fixos-2.2.18 → fixos-2.2.20}/docker/debian/Dockerfile +0 -0
  36. {fixos-2.2.18 → fixos-2.2.20}/docker/docker-compose.multi-system.yml +0 -0
  37. {fixos-2.2.18 → fixos-2.2.20}/docker/docker-compose.yml +0 -0
  38. {fixos-2.2.18 → fixos-2.2.20}/docker/fedora/Dockerfile +0 -0
  39. {fixos-2.2.18 → fixos-2.2.20}/docker/test-multi-system.sh +0 -0
  40. {fixos-2.2.18 → fixos-2.2.20}/docker/ubuntu/Dockerfile +0 -0
  41. {fixos-2.2.18 → fixos-2.2.20}/docs/examples/advanced_usage.py +0 -0
  42. {fixos-2.2.18 → fixos-2.2.20}/docs/examples/quickstart.py +0 -0
  43. {fixos-2.2.18 → fixos-2.2.20}/fixos/agent/__init__.py +0 -0
  44. {fixos-2.2.18 → fixos-2.2.20}/fixos/agent/autonomous.py +0 -0
  45. {fixos-2.2.18 → fixos-2.2.20}/fixos/agent/autonomous_session.py +0 -0
  46. {fixos-2.2.18 → fixos-2.2.20}/fixos/agent/hitl.py +0 -0
  47. {fixos-2.2.18 → fixos-2.2.20}/fixos/agent/hitl_session.py +0 -0
  48. {fixos-2.2.18 → fixos-2.2.20}/fixos/agent/session_core.py +0 -0
  49. {fixos-2.2.18 → fixos-2.2.20}/fixos/agent/session_handlers.py +0 -0
  50. {fixos-2.2.18 → fixos-2.2.20}/fixos/agent/session_io.py +0 -0
  51. {fixos-2.2.18 → fixos-2.2.20}/fixos/anonymizer.py +0 -0
  52. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/__init__.py +0 -0
  53. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/cleanup_cmd.py +0 -0
  54. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/history_cmd.py +0 -0
  55. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/orchestrate_cmd.py +0 -0
  56. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/profile_cmd.py +0 -0
  57. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli/provider_cmd.py +0 -0
  58. {fixos-2.2.18 → fixos-2.2.20}/fixos/cli.py +0 -0
  59. {fixos-2.2.18 → fixos-2.2.20}/fixos/config_interactive.py +0 -0
  60. {fixos-2.2.18 → fixos-2.2.20}/fixos/constants.py +0 -0
  61. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/__init__.py +0 -0
  62. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/checks/__init__.py +0 -0
  63. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/checks/_shared.py +0 -0
  64. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/checks/audio.py +0 -0
  65. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/checks/hardware.py +0 -0
  66. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/checks/resources.py +0 -0
  67. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/checks/security.py +0 -0
  68. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/checks/system_core.py +0 -0
  69. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/checks/thumbnails.py +0 -0
  70. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/dev_project_analyzer.py +0 -0
  71. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/disk_analyzer.py +0 -0
  72. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/flatpak_analyzer.py +0 -0
  73. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/service_cleanup.py +0 -0
  74. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/service_details.py +0 -0
  75. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/service_scanner.py +0 -0
  76. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/storage_analyzer.py +0 -0
  77. {fixos-2.2.18 → fixos-2.2.20}/fixos/diagnostics/system_checks.py +0 -0
  78. {fixos-2.2.18 → fixos-2.2.20}/fixos/features/__init__.py +0 -0
  79. {fixos-2.2.18 → fixos-2.2.20}/fixos/features/auditor.py +0 -0
  80. {fixos-2.2.18 → fixos-2.2.20}/fixos/features/catalog.py +0 -0
  81. {fixos-2.2.18 → fixos-2.2.20}/fixos/features/installer.py +0 -0
  82. {fixos-2.2.18 → fixos-2.2.20}/fixos/features/profiles.py +0 -0
  83. {fixos-2.2.18 → fixos-2.2.20}/fixos/features/renderer.py +0 -0
  84. {fixos-2.2.18 → fixos-2.2.20}/fixos/fixes/__init__.py +0 -0
  85. {fixos-2.2.18 → fixos-2.2.20}/fixos/interactive/__init__.py +0 -0
  86. {fixos-2.2.18 → fixos-2.2.20}/fixos/interactive/cleanup_planner.py +0 -0
  87. {fixos-2.2.18 → fixos-2.2.20}/fixos/llm_shell.py +0 -0
  88. {fixos-2.2.18 → fixos-2.2.20}/fixos/orchestrator/__init__.py +0 -0
  89. {fixos-2.2.18 → fixos-2.2.20}/fixos/orchestrator/executor.py +0 -0
  90. {fixos-2.2.18 → fixos-2.2.20}/fixos/orchestrator/graph.py +0 -0
  91. {fixos-2.2.18 → fixos-2.2.20}/fixos/orchestrator/orchestrator.py +0 -0
  92. {fixos-2.2.18 → fixos-2.2.20}/fixos/orchestrator/rollback.py +0 -0
  93. {fixos-2.2.18 → fixos-2.2.20}/fixos/platform_utils.py +0 -0
  94. {fixos-2.2.18 → fixos-2.2.20}/fixos/plugins/__init__.py +0 -0
  95. {fixos-2.2.18 → fixos-2.2.20}/fixos/plugins/base.py +0 -0
  96. {fixos-2.2.18 → fixos-2.2.20}/fixos/plugins/builtin/__init__.py +0 -0
  97. {fixos-2.2.18 → fixos-2.2.20}/fixos/plugins/builtin/audio.py +0 -0
  98. {fixos-2.2.18 → fixos-2.2.20}/fixos/plugins/builtin/disk.py +0 -0
  99. {fixos-2.2.18 → fixos-2.2.20}/fixos/plugins/builtin/hardware.py +0 -0
  100. {fixos-2.2.18 → fixos-2.2.20}/fixos/plugins/builtin/resources.py +0 -0
  101. {fixos-2.2.18 → fixos-2.2.20}/fixos/plugins/builtin/security.py +0 -0
  102. {fixos-2.2.18 → fixos-2.2.20}/fixos/plugins/builtin/thumbnails.py +0 -0
  103. {fixos-2.2.18 → fixos-2.2.20}/fixos/plugins/registry.py +0 -0
  104. {fixos-2.2.18 → fixos-2.2.20}/fixos/profiles/__init__.py +0 -0
  105. {fixos-2.2.18 → fixos-2.2.20}/fixos/providers/__init__.py +0 -0
  106. {fixos-2.2.18 → fixos-2.2.20}/fixos/providers/llm.py +0 -0
  107. {fixos-2.2.18 → fixos-2.2.20}/fixos/providers/llm_analyzer.py +0 -0
  108. {fixos-2.2.18 → fixos-2.2.20}/fixos/providers/schemas.py +0 -0
  109. {fixos-2.2.18 → fixos-2.2.20}/fixos/system_checks.py +0 -0
  110. {fixos-2.2.18 → fixos-2.2.20}/fixos/utils/__init__.py +0 -0
  111. {fixos-2.2.18 → fixos-2.2.20}/fixos/utils/anonymizer.py +0 -0
  112. {fixos-2.2.18 → fixos-2.2.20}/fixos/utils/terminal.py +0 -0
  113. {fixos-2.2.18 → fixos-2.2.20}/fixos/utils/timeout.py +0 -0
  114. {fixos-2.2.18 → fixos-2.2.20}/fixos/utils/web_search.py +0 -0
  115. {fixos-2.2.18 → fixos-2.2.20}/fixos/watch.py +0 -0
  116. {fixos-2.2.18 → fixos-2.2.20}/fixos.egg-info/SOURCES.txt +0 -0
  117. {fixos-2.2.18 → fixos-2.2.20}/fixos.egg-info/dependency_links.txt +0 -0
  118. {fixos-2.2.18 → fixos-2.2.20}/fixos.egg-info/entry_points.txt +0 -0
  119. {fixos-2.2.18 → fixos-2.2.20}/fixos.egg-info/requires.txt +0 -0
  120. {fixos-2.2.18 → fixos-2.2.20}/fixos.egg-info/top_level.txt +0 -0
  121. {fixos-2.2.18 → fixos-2.2.20}/pytest.ini +0 -0
  122. {fixos-2.2.18 → fixos-2.2.20}/requirements-dev.txt +0 -0
  123. {fixos-2.2.18 → fixos-2.2.20}/requirements.txt +0 -0
  124. {fixos-2.2.18 → fixos-2.2.20}/scripts/pyqual-calibrate.py +0 -0
  125. {fixos-2.2.18 → fixos-2.2.20}/setup.cfg +0 -0
  126. {fixos-2.2.18 → fixos-2.2.20}/tests/__init__.py +0 -0
  127. {fixos-2.2.18 → fixos-2.2.20}/tests/conftest.py +0 -0
  128. {fixos-2.2.18 → fixos-2.2.20}/tests/e2e/__init__.py +0 -0
  129. {fixos-2.2.18 → fixos-2.2.20}/tests/e2e/test_anonymization_layers.py +0 -0
  130. {fixos-2.2.18 → fixos-2.2.20}/tests/e2e/test_audio_broken.py +0 -0
  131. {fixos-2.2.18 → fixos-2.2.20}/tests/e2e/test_cli.py +0 -0
  132. {fixos-2.2.18 → fixos-2.2.20}/tests/e2e/test_executor.py +0 -0
  133. {fixos-2.2.18 → fixos-2.2.20}/tests/e2e/test_multi_system.py +0 -0
  134. {fixos-2.2.18 → fixos-2.2.20}/tests/e2e/test_network_broken.py +0 -0
  135. {fixos-2.2.18 → fixos-2.2.20}/tests/e2e/test_thumbnails_broken.py +0 -0
  136. {fixos-2.2.18 → fixos-2.2.20}/tests/test_fixos.py +0 -0
  137. {fixos-2.2.18 → fixos-2.2.20}/tests/unit/__init__.py +0 -0
  138. {fixos-2.2.18 → fixos-2.2.20}/tests/unit/test_anonymizer.py +0 -0
  139. {fixos-2.2.18 → fixos-2.2.20}/tests/unit/test_core.py +0 -0
  140. {fixos-2.2.18 → fixos-2.2.20}/tests/unit/test_executor.py +0 -0
  141. {fixos-2.2.18 → fixos-2.2.20}/tests/unit/test_orchestrator.py +0 -0
  142. {fixos-2.2.18 → fixos-2.2.20}/tests/unit/test_service_cleanup.py +0 -0
  143. {fixos-2.2.18 → fixos-2.2.20}/tests/unit/test_service_scanner.py +0 -0
@@ -150,6 +150,34 @@ 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.20] - 2026-05-04
154
+
155
+ ### Docs
156
+ - Update README.md
157
+
158
+ ### Other
159
+ - Update fixos/cli/ask_cmd.py
160
+ - Update fixos/cli/features_cmd.py
161
+ - Update fixos/cli/fix_cmd.py
162
+ - Update fixos/cli/main.py
163
+ - Update fixos/cli/quickfix_cmd.py
164
+ - Update fixos/cli/report_cmd.py
165
+ - Update fixos/cli/rollback_cmd.py
166
+ - Update fixos/cli/scan_cmd.py
167
+ - Update fixos/cli/shared.py
168
+ - Update fixos/cli/token_cmd.py
169
+ - ... and 3 more files
170
+
171
+ ## [2.2.19] - 2026-05-04
172
+
173
+ ### Docs
174
+ - Update README.md
175
+
176
+ ### Other
177
+ - Update fixos/cli/config_cmd.py
178
+ - Update fixos/config.py
179
+ - Update uv.lock
180
+
153
181
  ## [2.2.18] - 2026-05-04
154
182
 
155
183
  ### Docs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.18
3
+ Version: 2.2.20
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
@@ -63,11 +63,11 @@ AI-powered OS Diagnostics
63
63
 
64
64
  ## AI Cost Tracking
65
65
 
66
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.18-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-22.1h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
66
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.20-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-22.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
68
68
 
69
- - 🤖 **LLM usage:** $7.5000 (117 commits)
70
- - 👤 **Human dev:** ~$2210 (22.1h @ $100/h, 30min dedup)
69
+ - 🤖 **LLM usage:** $7.5000 (119 commits)
70
+ - 👤 **Human dev:** ~$2223 (22.2h @ $100/h, 30min dedup)
71
71
 
72
72
  Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
73
73
 
@@ -19,11 +19,11 @@ AI-powered OS Diagnostics
19
19
 
20
20
  ## AI Cost Tracking
21
21
 
22
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.18-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
23
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-22.1h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
22
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.20-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
23
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-22.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
24
24
 
25
- - 🤖 **LLM usage:** $7.5000 (117 commits)
26
- - 👤 **Human dev:** ~$2210 (22.1h @ $100/h, 30min dedup)
25
+ - 🤖 **LLM usage:** $7.5000 (119 commits)
26
+ - 👤 **Human dev:** ~$2223 (22.2h @ $100/h, 30min dedup)
27
27
 
28
28
  Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
29
29
 
@@ -1,2 +1,2 @@
1
1
  """fixos – AI-powered Linux/Windows diagnostics and repair."""
2
- __version__ = "2.2.18"
2
+ __version__ = "2.2.20"
@@ -36,7 +36,7 @@ _OBJECT_KEYWORDS: list[tuple[list[str], tuple]] = [
36
36
  ]
37
37
 
38
38
 
39
- def _object_based_match(prompt_lower: str):
39
+ def _object_based_match(prompt_lower: str) -> object:
40
40
  """Fallback object-based matching when no action keyword is found."""
41
41
  for keywords, cmd in _OBJECT_KEYWORDS:
42
42
  if any(kw in prompt_lower for kw in keywords):
@@ -0,0 +1,221 @@
1
+ """
2
+ Config management commands for fixOS CLI
3
+ """
4
+ import click
5
+ from pathlib import Path
6
+
7
+
8
+ def _env_path() -> Path:
9
+ """Return path to active .env file (cwd > home)."""
10
+ for p in [Path.cwd() / ".env", Path.home() / ".fixos.env", Path.home() / ".fixos.conf"]:
11
+ if p.exists():
12
+ return p
13
+ return Path.cwd() / ".env"
14
+
15
+
16
+ def _set_env_key(key: str, value: str) -> Path:
17
+ """Update or insert KEY=VALUE in the active .env file."""
18
+ path = _env_path()
19
+ lines: list[str] = path.read_text(encoding="utf-8").splitlines() if path.exists() else []
20
+ replaced = False
21
+ for i, line in enumerate(lines):
22
+ if line.startswith(f"{key}="):
23
+ lines[i] = f"{key}={value}"
24
+ replaced = True
25
+ break
26
+ if not replaced:
27
+ lines.append(f"{key}={value}")
28
+ path.write_text("\n".join(lines) + "\n", encoding="utf-8")
29
+ path.chmod(0o600)
30
+ return path
31
+
32
+
33
+ @click.group("config")
34
+ def config() -> None:
35
+ """Zarządzanie konfiguracją fixOS."""
36
+ pass
37
+
38
+
39
+ @config.command("show")
40
+ def config_show() -> None:
41
+ """Pokaż aktualną konfigurację."""
42
+ from fixos.config import FixOsConfig
43
+
44
+ cfg = FixOsConfig.load()
45
+ click.echo(click.style("\nKonfiguracja fixOS:", fg="cyan", bold=True))
46
+ click.echo(click.style("─" * 40, fg="cyan"))
47
+
48
+ click.echo(f" Provider: {cfg.provider}")
49
+ click.echo(f" Model: {cfg.model}")
50
+ click.echo(f" Timeout: {cfg.session_timeout}s")
51
+ click.echo(f" Agent mode: {cfg.agent_mode}")
52
+ click.echo(f" Web search: {'włączone' if cfg.enable_web_search else 'wyłączone'}")
53
+
54
+ key_status = click.style("skonfigurowany", fg="green") if cfg.api_key else click.style("BRAK", fg="red")
55
+ click.echo(f" API Key: {key_status}")
56
+
57
+ if cfg.env_file_loaded:
58
+ click.echo(f" Plik .env: {cfg.env_file_loaded}")
59
+ else:
60
+ click.echo(" Plik .env: nie znaleziono")
61
+ click.echo()
62
+
63
+
64
+ @config.command("init")
65
+ @click.option("--force", is_flag=True, help="Nadpisz istniejący plik")
66
+ def config_init(force) -> None:
67
+ """Zainicjalizuj plik konfiguracyjny .env."""
68
+ from pathlib import Path
69
+
70
+ env_path = Path(".env")
71
+ if env_path.exists() and not force:
72
+ click.echo(click.style("Plik .env już istnieje. Użyj --force aby nadpisać.", fg="yellow"))
73
+ return
74
+
75
+ template = """# fixOS Configuration
76
+ # Provider: gemini|openai|xai|openrouter|ollama|custom
77
+ PROVIDER=gemini
78
+
79
+ # API Keys (wypełnij odpowiedni dla swojego providera)
80
+ GEMINI_API_KEY=
81
+ # OPENAI_API_KEY=
82
+ # XAI_API_KEY=
83
+ # OPENROUTER_API_KEY=
84
+
85
+ # Opcjonalne ustawienia
86
+ # MODEL=gemini-2.0-flash-exp
87
+ # SESSION_TIMEOUT=300
88
+ # AGENT_MODE=hitl
89
+ """
90
+ env_path.write_text(template)
91
+ click.echo(click.style(f"Utworzono {env_path}", fg="green"))
92
+ click.echo("Edytuj plik i dodaj swój klucz API.")
93
+
94
+
95
+ @config.command("set")
96
+ @click.argument("key")
97
+ @click.argument("value")
98
+ def config_set(key, value) -> None:
99
+ """Ustaw wartość konfiguracyjną w .env."""
100
+ from dotenv import set_key
101
+
102
+ env_path = Path(".env")
103
+ if not env_path.exists():
104
+ click.echo(click.style("Plik .env nie istnieje. Uruchom: fixos config init", fg="yellow"))
105
+ return
106
+
107
+ set_key(str(env_path), key.upper(), value)
108
+ click.echo(click.style(f"Ustawiono {key.upper()}={value}", fg="green"))
109
+
110
+
111
+ @config.command("model")
112
+ @click.option("--provider", "-p", default=None, help="Provider (domyślnie: aktualny z .env)")
113
+ def config_model(provider: str) -> None:
114
+ """Interaktywnie wybierz model LLM z listy."""
115
+ from fixos.config import FixOsConfig, PROVIDER_MODELS, PROVIDER_DEFAULTS
116
+
117
+ cfg = FixOsConfig.load(provider=provider)
118
+ current_provider = cfg.provider
119
+ models = PROVIDER_MODELS.get(current_provider, [])
120
+
121
+ click.echo(click.style(f"\n🤖 Wybór modelu dla providera: ", fg="cyan") +
122
+ click.style(current_provider, fg="yellow", bold=True))
123
+ click.echo(click.style(f" Aktualny model: {cfg.model}", fg="white"))
124
+ click.echo(click.style("─" * 50, fg="cyan"))
125
+
126
+ if not models:
127
+ click.echo(click.style(f"Brak znanych modeli dla '{current_provider}'.", fg="yellow"))
128
+ custom = click.prompt("Podaj nazwę modelu ręcznie", default=cfg.model)
129
+ chosen = custom
130
+ else:
131
+ click.echo()
132
+ for i, m in enumerate(models, 1):
133
+ marker = click.style("◀ aktualny", fg="green") if m == cfg.model else ""
134
+ click.echo(f" [{i:2d}] {m} {marker}")
135
+ click.echo(f" [ 0] Wpisz własną nazwę modelu")
136
+ click.echo()
137
+
138
+ while True:
139
+ raw = click.prompt(click.style("Wybierz numer lub 0", fg="cyan"), default="1")
140
+ if raw == "0":
141
+ chosen = click.prompt("Podaj nazwę modelu", default=cfg.model)
142
+ break
143
+ if raw.isdigit() and 1 <= int(raw) <= len(models):
144
+ chosen = models[int(raw) - 1]
145
+ break
146
+ click.echo(click.style(f" ❌ Wpisz numer 0–{len(models)}", fg="red"))
147
+
148
+ provider_key = f"{current_provider.upper()}_MODEL"
149
+ env_file = _set_env_key(provider_key, chosen)
150
+ click.echo()
151
+ click.echo(click.style(f" ✅ Ustawiono {provider_key}={chosen}", fg="green"))
152
+ click.echo(click.style(f" 💾 Zapisano → {env_file}", fg="cyan"))
153
+ click.echo()
154
+
155
+
156
+ @config.command("provider")
157
+ def config_provider() -> None:
158
+ """Interaktywnie wybierz providera LLM z listy."""
159
+ from fixos.config import FixOsConfig, PROVIDER_DEFAULTS
160
+
161
+ cfg = FixOsConfig.load()
162
+ free = [(n, d) for n, d in PROVIDER_DEFAULTS.items() if d.get("free_tier")]
163
+ paid = [(n, d) for n, d in PROVIDER_DEFAULTS.items() if not d.get("free_tier")]
164
+
165
+ click.echo(click.style("\n⚙️ Wybór providera LLM", fg="cyan", bold=True))
166
+ click.echo(click.style(f" Aktualny: {cfg.provider} ({cfg.model})", fg="white"))
167
+ click.echo(click.style("─" * 60, fg="cyan"))
168
+ click.echo()
169
+ click.echo(click.style(" 🟢 DARMOWE:", fg="green"))
170
+
171
+ num_map: dict[int, str] = {}
172
+ idx = 1
173
+ for name, d in free:
174
+ marker = click.style(" ◀ aktualny", fg="green") if name == cfg.provider else ""
175
+ click.echo(f" [{idx:2d}] {click.style(name, fg='yellow'):<20} {d['model']:<38}{marker}")
176
+ num_map[idx] = name
177
+ idx += 1
178
+
179
+ click.echo()
180
+ click.echo(click.style(" 💰 PŁATNE:", fg="yellow"))
181
+ for name, d in paid:
182
+ marker = click.style(" ◀ aktualny", fg="green") if name == cfg.provider else ""
183
+ click.echo(f" [{idx:2d}] {click.style(name, fg='yellow'):<20} {d['model']:<38}{marker}")
184
+ num_map[idx] = name
185
+ idx += 1
186
+
187
+ click.echo()
188
+ click.echo(" [ 0] Anuluj")
189
+ click.echo()
190
+
191
+ while True:
192
+ raw = click.prompt(click.style("Wybierz numer providera", fg="cyan"), default="0")
193
+ if raw == "0":
194
+ click.echo(click.style("Anulowano.", fg="yellow"))
195
+ return
196
+ if raw.isdigit() and int(raw) in num_map:
197
+ chosen = num_map[int(raw)]
198
+ break
199
+ click.echo(click.style(f" ❌ Wpisz numer 0–{len(num_map)}", fg="red"))
200
+
201
+ pdef = PROVIDER_DEFAULTS[chosen]
202
+ key_env = pdef.get("key_env")
203
+
204
+ env_file = _set_env_key("LLM_PROVIDER", chosen)
205
+ click.echo()
206
+ click.echo(click.style(f" ✅ Ustawiono LLM_PROVIDER={chosen}", fg="green"))
207
+
208
+ if chosen != "ollama" and key_env:
209
+ import os
210
+ existing_key = os.environ.get(key_env, "")
211
+ if not existing_key:
212
+ click.echo(click.style(f" ℹ️ Pobierz klucz API: {pdef.get('key_url', '')}", fg="cyan"))
213
+ if click.confirm(click.style(f" Chcesz teraz wpisać klucz {key_env}?", fg="yellow"), default=False):
214
+ key = click.prompt(f" Wklej klucz {key_env}", hide_input=True)
215
+ if key.strip():
216
+ _set_env_key(key_env, key.strip())
217
+ click.echo(click.style(f" ✅ Zapisano {key_env}", fg="green"))
218
+
219
+ click.echo(click.style(f" 💾 Zapisano → {env_file}", fg="cyan"))
220
+ click.echo(click.style(f"\n Następny krok – wybierz model: fixos config model", fg="white"))
221
+ click.echo()
@@ -18,7 +18,7 @@ from ..utils.terminal import console
18
18
 
19
19
 
20
20
  @click.group(name="features")
21
- def features():
21
+ def features() -> None:
22
22
  """Zarządzanie pakietami komfortu systemu."""
23
23
  pass
24
24
 
@@ -218,7 +218,7 @@ def handle_disk_cleanup_mode(disk_analysis: Dict[str, Any], cfg, dry_run: bool,
218
218
  click.echo(click.style("\nBrak bezpiecznych akcji do automatycznego wykonania", fg="yellow"))
219
219
 
220
220
 
221
- def execute_cleanup_actions(actions: List[Dict], cfg, llm_fallback: bool):
221
+ def execute_cleanup_actions(actions: List[Dict], cfg, llm_fallback: bool) -> None:
222
222
  """Execute cleanup actions with safety checks"""
223
223
  from fixos.orchestrator.executor import CommandExecutor
224
224
 
@@ -259,7 +259,7 @@ def execute_cleanup_actions(actions: List[Dict], cfg, llm_fallback: bool):
259
259
  try_llm_fallback_for_failures(failed, cfg)
260
260
 
261
261
 
262
- def try_llm_fallback_for_failures(failed_actions, cfg):
262
+ def try_llm_fallback_for_failures(failed_actions, cfg) -> None:
263
263
  """Try to fix failed actions using LLM"""
264
264
  from fixos.providers.llm import LLMClient
265
265
 
@@ -110,10 +110,10 @@ def _print_welcome() -> None:
110
110
  click.echo(f"{click.style(' fixos fix', fg='yellow')} # zapyta o provider interaktywnie")
111
111
  else:
112
112
  click.echo(click.style(" Przykłady użycia:", fg="yellow", bold=True))
113
- click.echo(click.style(" fixos fix", fg="yellow") + " # pełna diagnostyka + naprawa")
114
- click.echo(click.style(" fixos fix --modules security,resources", fg="yellow") + " # bezpieczeństwo + zasoby")
115
- click.echo(click.style(" fixos scan --modules security", fg="yellow") + " # tylko skan bezpieczeństwa")
116
- click.echo(click.style(" fixos orchestrate --dry-run", fg="yellow") + " # podgląd napraw bez wykonania")
113
+ click.echo(f"{click.style(' fixos fix', fg='yellow')} # pełna diagnostyka + naprawa")
114
+ click.echo(f"{click.style(' fixos fix --modules security,resources', fg='yellow')} # bezpieczeństwo + zasoby")
115
+ click.echo(f"{click.style(' fixos scan --modules security', fg='yellow')} # tylko skan bezpieczeństwa")
116
+ click.echo(f"{click.style(' fixos orchestrate --dry-run', fg='yellow')} # podgląd napraw bez wykonania")
117
117
  click.echo()
118
118
 
119
119
 
@@ -9,7 +9,7 @@ from fixos.plugins.base import Severity
9
9
  @click.command("quickfix")
10
10
  @click.option("--dry-run", is_flag=True, default=False, help="Symuluj bez wykonania")
11
11
  @click.option("--modules", "-m", default=None, help="Moduły: audio,disk,security,...")
12
- def quickfix(dry_run, modules):
12
+ def quickfix(dry_run, modules) -> None:
13
13
  """
14
14
  Natychmiastowe naprawy bez API — baza znanych bugów.
15
15
 
@@ -70,7 +70,7 @@ code {{ background: #e9ecef; padding: 2px 6px; border-radius: 3px; }}
70
70
  @click.option("--output", "-o", default=None, help="Ścieżka pliku wyjściowego")
71
71
  @click.option("--modules", "-m", default=None, help="Moduły diagnostyki")
72
72
  @click.option("--profile", "-p", default=None, help="Profil diagnostyczny")
73
- def report(output_format, output, modules, profile):
73
+ def report(output_format, output, modules, profile) -> None:
74
74
  """
75
75
  Eksport wyników diagnostyki do raportu HTML/Markdown/JSON.
76
76
 
@@ -6,14 +6,14 @@ from fixos.orchestrator.rollback import RollbackSession
6
6
 
7
7
 
8
8
  @click.group("rollback")
9
- def rollback():
9
+ def rollback() -> None:
10
10
  """Zarządzanie cofaniem operacji fixOS."""
11
11
  pass
12
12
 
13
13
 
14
14
  @rollback.command("list")
15
15
  @click.option("--limit", default=20, help="Ile sesji pokazać")
16
- def rollback_list(limit):
16
+ def rollback_list(limit) -> None:
17
17
  """Pokaż historię sesji naprawczych."""
18
18
  sessions = RollbackSession.list_sessions(limit)
19
19
  if not sessions:
@@ -34,7 +34,7 @@ def rollback_list(limit):
34
34
 
35
35
  @rollback.command("show")
36
36
  @click.argument("session_id")
37
- def rollback_show(session_id):
37
+ def rollback_show(session_id) -> None:
38
38
  """Pokaż szczegóły sesji rollback."""
39
39
  try:
40
40
  session = RollbackSession.load(session_id)
@@ -60,7 +60,7 @@ def rollback_show(session_id):
60
60
  @click.argument("session_id")
61
61
  @click.option("--last", default=1, help="Ile ostatnich operacji cofnąć")
62
62
  @click.option("--dry-run", is_flag=True, default=False, help="Tylko pokaż co by się cofnęło")
63
- def rollback_undo(session_id, last, dry_run):
63
+ def rollback_undo(session_id, last, dry_run) -> None:
64
64
  """Cofnij operacje z podanej sesji."""
65
65
  try:
66
66
  session = RollbackSession.load(session_id)
@@ -13,9 +13,10 @@ from fixos.cli.shared import add_shared_options, BANNER
13
13
  @click.option("--system", "modules", flag_value="system", help="Tylko system")
14
14
  @click.option("--all", "modules", flag_value="all", default=True, help="Wszystkie moduły (domyślnie)")
15
15
  @add_shared_options
16
+ @click.option("--no-banner", "no_banner", is_flag=True, default=False, help="Ukryj baner fixos")
16
17
  @click.option("--output", "-o", default=None, help="Zapisz wyniki do pliku")
17
18
  @click.option("--profile", "-p", default=None, help="Profil diagnostyczny (server/desktop/developer/minimal)")
18
- def scan(modules, output, show_raw, no_banner, disc, dry_run, interactive, json_output, llm_fallback, profile):
19
+ def scan(modules, output, show_raw, no_banner, disc, dry_run, interactive, json_output, llm_fallback, profile) -> None:
19
20
  """
20
21
  Przeprowadza diagnostykę systemu.
21
22
 
@@ -124,7 +125,7 @@ def _display_disk_scan_mode(disk_analysis: dict) -> None:
124
125
  click.echo(f" {safe_icon} {suggestion['description']} ({suggestion.get('size_gb', 0):.1f}GB)")
125
126
 
126
127
 
127
- def _run_disk_analysis(data: dict, json_output: bool, is_fix_mode: bool = False):
128
+ def _run_disk_analysis(data: dict, json_output: bool, is_fix_mode: bool = False) -> None:
128
129
  """Helper for disk analysis logic to avoid duplication between scan and fix"""
129
130
  indent = " " if is_fix_mode else ""
130
131
  click.echo(click.style("Analizowanie zajętości dysku...", fg="blue"))
@@ -155,7 +156,7 @@ def _run_disk_analysis(data: dict, json_output: bool, is_fix_mode: bool = False)
155
156
  click.echo(click.style(f"{indent}Błąd podczas analizy dysku: {str(e)}", fg="red"))
156
157
 
157
158
 
158
- def _print_quick_issues(data: dict):
159
+ def _print_quick_issues(data: dict) -> None:
159
160
  """Wyświetla szybki przegląd problemów z zebranych danych."""
160
161
  import click
161
162
  click.echo(click.style("\nSzybki przegląd problemów:", fg="cyan"))
@@ -23,19 +23,17 @@ COMMON_OPTIONS = [
23
23
  ]
24
24
 
25
25
 
26
- def add_common_options(fn):
26
+ def add_common_options(fn) -> object:
27
27
  """Decorator adding common LLM options to a Click command."""
28
28
  for opt in reversed(COMMON_OPTIONS):
29
29
  fn = opt(fn)
30
30
  return fn
31
31
 
32
32
 
33
- def add_shared_options(func):
33
+ def add_shared_options(func) -> object:
34
34
  """Shared options for both scan and fix commands."""
35
35
  func = click.option("--show-raw", "show_raw", is_flag=True, default=False,
36
36
  help="Pokaż surowe dane diagnostyczne (JSON)")(func)
37
- func = click.option("--no-banner", "no_banner", is_flag=True, default=False,
38
- help="Ukryj baner fixos")(func)
39
37
  func = click.option("--disc", is_flag=True, default=False,
40
38
  help="Analiza zajętości dysku + grupowanie przyczyn")(func)
41
39
  func = click.option("--disk", "disc", is_flag=True, default=False,
@@ -54,7 +52,7 @@ def add_shared_options(func):
54
52
  class NaturalLanguageGroup(click.Group):
55
53
  """Click group that routes unknown commands to 'ask' command."""
56
54
 
57
- def resolve_command(self, ctx, args):
55
+ def resolve_command(self, ctx, args) -> tuple[str, click.Command, list[str]]:
58
56
  cmd_name = args[0] if args else None
59
57
  cmd = self.get_command(ctx, cmd_name) if cmd_name else None
60
58
  if cmd is None and args and not args[0].startswith("-"):
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
 
8
8
 
9
9
  @click.group("token")
10
- def token():
10
+ def token() -> None:
11
11
  """Zarządzanie tokenem API."""
12
12
  pass
13
13
 
@@ -16,7 +16,7 @@ def token():
16
16
  @click.argument("key")
17
17
  @click.option("--provider", "-p", default=None, help="Provider (auto-detect if not provided)")
18
18
  @click.option("--env-file", "-e", default=".env", help="Plik .env do zapisu")
19
- def token_set(key, provider, env_file):
19
+ def token_set(key, provider, env_file) -> None:
20
20
  """
21
21
  Zapisz token API do pliku .env.
22
22
 
@@ -69,7 +69,7 @@ def token_set(key, provider, env_file):
69
69
  set_key(str(env_path), env_var, key)
70
70
  # Set file permissions to 600 (owner read/write only)
71
71
  os.chmod(env_path, 0o600)
72
- masked = key[:8] + "..." + key[-4:] if len(key) > 12 else "***"
72
+ masked = f"{key[:8]}...{key[-4:]}" if len(key) > 12 else "***"
73
73
  click.echo(click.style(f"Zapisano {env_var}={masked} do {env_path}", fg="green"))
74
74
 
75
75
  # Also update current process
@@ -87,13 +87,13 @@ def token_set(key, provider, env_file):
87
87
 
88
88
 
89
89
  @token.command("show")
90
- def token_show():
90
+ def token_show() -> None:
91
91
  """Pokaż obecny token (masked)."""
92
92
  from fixos.config import FixOsConfig
93
93
 
94
94
  cfg = FixOsConfig.load()
95
95
  if cfg.api_key:
96
- masked = cfg.api_key[:8] + "..." + cfg.api_key[-4:]
96
+ masked = f"{cfg.api_key[:8]}...{cfg.api_key[-4:]}"
97
97
  click.echo(f"Provider: {cfg.provider}")
98
98
  click.echo(f"Model: {cfg.model}")
99
99
  click.echo(f"Token: {masked}")
@@ -103,7 +103,7 @@ def token_show():
103
103
 
104
104
  @token.command("clear")
105
105
  @click.option("--env-file", "-e", default=".env", help="Plik .env do edycji")
106
- def token_clear(env_file):
106
+ def token_clear(env_file) -> None:
107
107
  """Usuń token z pliku .env."""
108
108
  from dotenv import unset_key
109
109
 
@@ -15,7 +15,7 @@ from fixos.plugins.base import Severity
15
15
  help="Minimalny poziom alertów")
16
16
  @click.option("--max-iterations", default=0, show_default=True,
17
17
  help="Maksymalna liczba iteracji (0 = bez limitu)")
18
- def watch(interval, modules, alert_on, max_iterations):
18
+ def watch(interval, modules, alert_on, max_iterations) -> None:
19
19
  """
20
20
  Monitorowanie systemu w tle z powiadomieniami.
21
21
 
@@ -51,7 +51,7 @@ PROVIDER_DEFAULTS = {
51
51
  },
52
52
  "openrouter": {
53
53
  "base_url": "https://openrouter.ai/api/v1",
54
- "model": "openai/gpt-4o-mini",
54
+ "model": "deepseek/deepseek-v4-flash",
55
55
  "key_env": "OPENROUTER_API_KEY",
56
56
  "key_url": "https://openrouter.ai/settings/keys",
57
57
  "free_tier": True,
@@ -312,6 +312,86 @@ KEY_PREFIXES: list[tuple[str, str]] = [
312
312
  ]
313
313
 
314
314
 
315
+ PROVIDER_MODELS: dict[str, list[str]] = {
316
+ "gemini": [
317
+ "gemini-2.5-flash-preview-04-17",
318
+ "gemini-2.0-flash",
319
+ "gemini-2.0-flash-lite",
320
+ "gemini-1.5-flash",
321
+ "gemini-1.5-pro",
322
+ ],
323
+ "openai": [
324
+ "gpt-4o-mini",
325
+ "gpt-4o",
326
+ "gpt-4-turbo",
327
+ "gpt-3.5-turbo",
328
+ ],
329
+ "openrouter": [
330
+ "deepseek/deepseek-v4-flash",
331
+ "meta-llama/llama-3.1-8b-instruct:free",
332
+ "mistralai/mistral-small-3.1-24b-instruct:free",
333
+ "qwen/qwen3-235b-a22b:free",
334
+ "meta-llama/llama-3.3-70b-instruct",
335
+ "google/gemini-2.0-flash-001",
336
+ "google/gemini-flash-1.5",
337
+ "openai/gpt-4o-mini",
338
+ "openai/gpt-4o",
339
+ ],
340
+ "anthropic": [
341
+ "claude-3-haiku-20240307",
342
+ "claude-3-5-haiku-20241022",
343
+ "claude-3-5-sonnet-20241022",
344
+ "claude-3-opus-20240229",
345
+ ],
346
+ "mistral": [
347
+ "mistral-small-latest",
348
+ "mistral-medium-latest",
349
+ "mistral-large-latest",
350
+ "open-mixtral-8x22b",
351
+ ],
352
+ "groq": [
353
+ "llama-3.1-8b-instant",
354
+ "llama-3.3-70b-versatile",
355
+ "llama3-70b-8192",
356
+ "gemma2-9b-it",
357
+ "mixtral-8x7b-32768",
358
+ ],
359
+ "together": [
360
+ "meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo",
361
+ "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
362
+ "mistralai/Mixtral-8x22B-Instruct-v0.1",
363
+ ],
364
+ "cohere": [
365
+ "command-r",
366
+ "command-r-plus",
367
+ "command-a-03-2025",
368
+ ],
369
+ "deepseek": [
370
+ "deepseek-chat",
371
+ "deepseek-reasoner",
372
+ ],
373
+ "cerebras": [
374
+ "llama3.1-8b",
375
+ "llama-3.3-70b",
376
+ "llama3.1-70b",
377
+ ],
378
+ "xai": [
379
+ "grok-beta",
380
+ "grok-2-1212",
381
+ "grok-2-vision-1212",
382
+ ],
383
+ "ollama": [
384
+ "llama3.2",
385
+ "llama3.1",
386
+ "mistral",
387
+ "gemma2",
388
+ "qwen2.5",
389
+ "phi3",
390
+ "deepseek-r1",
391
+ ],
392
+ }
393
+
394
+
315
395
  def detect_provider_from_key(key: str) -> Optional[str]:
316
396
  """Wykrywa provider na podstawie prefiksu klucza API."""
317
397
  for prefix, provider in KEY_PREFIXES:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.18
3
+ Version: 2.2.20
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
@@ -63,11 +63,11 @@ AI-powered OS Diagnostics
63
63
 
64
64
  ## AI Cost Tracking
65
65
 
66
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.18-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-22.1h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
66
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.20-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-22.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
68
68
 
69
- - 🤖 **LLM usage:** $7.5000 (117 commits)
70
- - 👤 **Human dev:** ~$2210 (22.1h @ $100/h, 30min dedup)
69
+ - 🤖 **LLM usage:** $7.5000 (119 commits)
70
+ - 👤 **Human dev:** ~$2223 (22.2h @ $100/h, 30min dedup)
71
71
 
72
72
  Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
73
73