evo-cli 0.19.0__tar.gz → 0.20.0__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 (119) hide show
  1. {evo_cli-0.19.0 → evo_cli-0.20.0}/PKG-INFO +1 -1
  2. evo_cli-0.20.0/evo_cli/VERSION +1 -0
  3. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/download.py +51 -6
  4. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli.egg-info/PKG-INFO +1 -1
  5. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_download.py +58 -0
  6. evo_cli-0.19.0/evo_cli/VERSION +0 -1
  7. {evo_cli-0.19.0 → evo_cli-0.20.0}/Containerfile +0 -0
  8. {evo_cli-0.19.0 → evo_cli-0.20.0}/HISTORY.md +0 -0
  9. {evo_cli-0.19.0 → evo_cli-0.20.0}/LICENSE +0 -0
  10. {evo_cli-0.19.0 → evo_cli-0.20.0}/MANIFEST.in +0 -0
  11. {evo_cli-0.19.0 → evo_cli-0.20.0}/README.md +0 -0
  12. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/__init__.py +0 -0
  13. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/__main__.py +0 -0
  14. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/base.py +0 -0
  15. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/cli.py +0 -0
  16. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/__init__.py +0 -0
  17. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/agent_toy.py +0 -0
  18. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/claude_code.py +0 -0
  19. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/cloudflare.py +0 -0
  20. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/cred.py +0 -0
  21. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/fix_claude.py +0 -0
  22. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/gdrive.py +0 -0
  23. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/gh.py +0 -0
  24. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/__init__.py +0 -0
  25. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/_dag.py +0 -0
  26. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/_git.py +0 -0
  27. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/_model.py +0 -0
  28. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/_mutate.py +0 -0
  29. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/_paths.py +0 -0
  30. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/_render.py +0 -0
  31. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/_server.py +0 -0
  32. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/check.py +0 -0
  33. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/clone.py +0 -0
  34. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/edit.py +0 -0
  35. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/pull.py +0 -0
  36. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/serve.py +0 -0
  37. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/show.py +0 -0
  38. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/views.py +0 -0
  39. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/assets/index-CsALFg16.css +0 -0
  40. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/assets/index-DwEXL5pV.js +0 -0
  41. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  42. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  43. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  44. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  45. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  46. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  47. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  48. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  49. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  50. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  51. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  52. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  53. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/harness/web/index.html +0 -0
  54. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/hwid.py +0 -0
  55. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/hwid_reset.py +0 -0
  56. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/localproxy.py +0 -0
  57. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/mcp.py +0 -0
  58. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/miniconda.py +0 -0
  59. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/netcheck.py +0 -0
  60. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/opencode.py +0 -0
  61. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/plantuml.py +0 -0
  62. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/serp.py +0 -0
  63. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/site2s.py +0 -0
  64. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/ssh.py +0 -0
  65. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/sysmon.py +0 -0
  66. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/tts.py +0 -0
  67. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/update.py +0 -0
  68. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/commands/wifi.py +0 -0
  69. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/console.py +0 -0
  70. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/credentials/__init__.py +0 -0
  71. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/credentials/doctor.py +0 -0
  72. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/credentials/google_oauth.py +0 -0
  73. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/credentials/migrate.py +0 -0
  74. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/credentials/oauth_flow.py +0 -0
  75. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/credentials/registry.py +0 -0
  76. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/credentials/store.py +0 -0
  77. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/credentials/sync.py +0 -0
  78. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/mcp_registry.py +0 -0
  79. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/serp/__init__.py +0 -0
  80. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/serp/client.py +0 -0
  81. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/serp/creds.py +0 -0
  82. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/serp/errors.py +0 -0
  83. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/serp/install.py +0 -0
  84. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/serp/render.py +0 -0
  85. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/tts/__init__.py +0 -0
  86. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/tts/chunking.py +0 -0
  87. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/tts/core.py +0 -0
  88. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/tts/creds.py +0 -0
  89. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/tts/errors.py +0 -0
  90. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/tts/http.py +0 -0
  91. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/tts/openai.py +0 -0
  92. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/tts/player.py +0 -0
  93. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli/tts/vbee.py +0 -0
  94. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli.egg-info/SOURCES.txt +0 -0
  95. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli.egg-info/dependency_links.txt +0 -0
  96. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli.egg-info/entry_points.txt +0 -0
  97. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli.egg-info/requires.txt +0 -0
  98. {evo_cli-0.19.0 → evo_cli-0.20.0}/evo_cli.egg-info/top_level.txt +0 -0
  99. {evo_cli-0.19.0 → evo_cli-0.20.0}/pyproject.toml +0 -0
  100. {evo_cli-0.19.0 → evo_cli-0.20.0}/setup.cfg +0 -0
  101. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/__init__.py +0 -0
  102. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_agent_toy.py +0 -0
  103. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_claude_code.py +0 -0
  104. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_cli.py +0 -0
  105. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_console.py +0 -0
  106. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_cred.py +0 -0
  107. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_fix_claude.py +0 -0
  108. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_gh.py +0 -0
  109. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_harness.py +0 -0
  110. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_harness_clone.py +0 -0
  111. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_harness_dag.py +0 -0
  112. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_mcp.py +0 -0
  113. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_opencode.py +0 -0
  114. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_plantuml.py +0 -0
  115. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_serp.py +0 -0
  116. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_sysmon.py +0 -0
  117. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_tts.py +0 -0
  118. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_update.py +0 -0
  119. {evo_cli-0.19.0 → evo_cli-0.20.0}/tests/test_wifi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: evo_cli
3
- Version: 0.19.0
3
+ Version: 0.20.0
4
4
  Summary: Evolution CLI - a developer toolbox for setting up dev machines
5
5
  Author: maycuatroi
6
6
  Project-URL: Homepage, https://github.com/maycuatroi/evo-cli
@@ -0,0 +1 @@
1
+ 0.20.0
@@ -1,3 +1,4 @@
1
+ import datetime
1
2
  import importlib.util
2
3
  import os
3
4
  import shutil
@@ -26,6 +27,7 @@ AUDIO_FORMATS = ["mp3", "m4a", "opus", "flac", "wav", "aac", "vorbis", "best"]
26
27
  CONTAINERS = ["mp4", "mkv", "webm", "auto"]
27
28
  BROWSERS = ["chrome", "edge", "firefox", "brave", "chromium", "opera", "vivaldi", "safari"]
28
29
  JS_RUNTIMES = ["deno", "bun", "node"]
30
+ YTDLP_STALE_DAYS = 60
29
31
 
30
32
  DIRECT_EXTS = {
31
33
  ".7z",
@@ -137,6 +139,38 @@ def ytdlp_version():
137
139
  return (result.stdout or "").strip() or None
138
140
 
139
141
 
142
+ def ytdlp_stale_age(version=None):
143
+ """Days since the installed build was released, or None if it is recent enough.
144
+
145
+ Sites like YouTube change their streaming setup every few months, so an old
146
+ yt-dlp fails with 403s that look like a bug in this command.
147
+ """
148
+ if version is None:
149
+ version = ytdlp_version()
150
+ if not version:
151
+ return None
152
+ parts = version.split(".")[:3]
153
+ if len(parts) < 3:
154
+ return None
155
+ try:
156
+ released = datetime.date(int(parts[0]), int(parts[1]), int(parts[2]))
157
+ except ValueError:
158
+ return None
159
+ age = (datetime.date.today() - released).days
160
+ return age if age >= YTDLP_STALE_DAYS else None
161
+
162
+
163
+ def warn_if_stale(version=None):
164
+ if version is None:
165
+ version = ytdlp_version()
166
+ age = ytdlp_stale_age(version)
167
+ if age is None:
168
+ return False
169
+ warning(f"yt-dlp {version} is {age} days old: sites change often and old builds start failing with 403s.")
170
+ info("Update it with `evo download install`.")
171
+ return True
172
+
173
+
140
174
  def find_ffmpeg():
141
175
  return shutil.which("ffmpeg")
142
176
 
@@ -197,6 +231,7 @@ def install_ffmpeg(assume_yes):
197
231
  def ensure_ytdlp(assume_yes=False):
198
232
  cmd = ytdlp_command()
199
233
  if cmd:
234
+ warn_if_stale()
200
235
  return cmd
201
236
  warning("yt-dlp is not installed.")
202
237
  if not assume_yes and not click.confirm("Install yt-dlp now with pip?", default=True):
@@ -295,11 +330,15 @@ def report_environment():
295
330
  table.add_column("Component", style="info", no_wrap=True)
296
331
  table.add_column("Status", no_wrap=True)
297
332
  table.add_column("Detail", style="dim")
298
- table.add_row(
299
- "yt-dlp",
300
- "[success]ok[/success]" if version else "[error]missing[/error]",
301
- version or "run `evo download install`",
302
- )
333
+ age = ytdlp_stale_age(version)
334
+ if not version:
335
+ ytdlp_status, ytdlp_detail = "[error]missing[/error]", "run `evo download install`"
336
+ elif age is not None:
337
+ ytdlp_status = "[warning]stale[/warning]"
338
+ ytdlp_detail = f"{version} ({age} days old) - run `evo download install`"
339
+ else:
340
+ ytdlp_status, ytdlp_detail = "[success]ok[/success]", version
341
+ table.add_row("yt-dlp", ytdlp_status, ytdlp_detail)
303
342
  table.add_row(
304
343
  "ffmpeg",
305
344
  "[success]ok[/success]" if ffmpeg else "[error]missing[/error]",
@@ -356,6 +395,10 @@ def do_get(urls, opts, assume_yes):
356
395
 
357
396
  result = run_command(cmd, check=False)
358
397
  if result.returncode != 0:
398
+ if ytdlp_stale_age() is not None:
399
+ raise click.ClickException(
400
+ "yt-dlp failed and the installed build is stale. Run `evo download install`, then try again."
401
+ )
359
402
  raise click.ClickException("yt-dlp failed. Try `evo download formats <url>` to inspect the source.")
360
403
  if not opts["list_formats"]:
361
404
  success(f"Saved to [accent]{outdir}[/accent]")
@@ -675,8 +718,10 @@ def install_cmd(with_deps, assume_yes):
675
718
  def check_cmd():
676
719
  step("evo download check")
677
720
  report_environment()
678
- if not ytdlp_version():
721
+ version = ytdlp_version()
722
+ if not version:
679
723
  raise click.ClickException("yt-dlp is missing. Run `evo download install`.")
724
+ warn_if_stale(version)
680
725
  if not find_ffmpeg():
681
726
  warning("ffmpeg missing: merging and audio conversion will not work.")
682
727
  info("Fix it with `evo download install --with-deps`.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: evo_cli
3
- Version: 0.19.0
3
+ Version: 0.20.0
4
4
  Summary: Evolution CLI - a developer toolbox for setting up dev machines
5
5
  Author: maycuatroi
6
6
  Project-URL: Homepage, https://github.com/maycuatroi/evo-cli
@@ -1,3 +1,4 @@
1
+ import datetime
1
2
  from pathlib import Path
2
3
 
3
4
  import pytest
@@ -231,3 +232,60 @@ def test_check_fails_without_ytdlp(monkeypatch):
231
232
  monkeypatch.setattr(dl, "ytdlp_version", lambda: None)
232
233
  result = CliRunner().invoke(cli, ["download", "check"])
233
234
  assert result.exit_code != 0
235
+
236
+
237
+ def version_released_days_ago(days):
238
+ released = datetime.date.today() - datetime.timedelta(days=days)
239
+ return f"{released.year}.{released.month:02d}.{released.day:02d}"
240
+
241
+
242
+ def test_recent_ytdlp_is_not_stale():
243
+ assert dl.ytdlp_stale_age(version_released_days_ago(3)) is None
244
+
245
+
246
+ def test_old_ytdlp_reports_its_age():
247
+ assert dl.ytdlp_stale_age(version_released_days_ago(200)) == 200
248
+
249
+
250
+ def test_nightly_ytdlp_build_still_parses():
251
+ assert dl.ytdlp_stale_age(version_released_days_ago(90) + ".232919") == 90
252
+
253
+
254
+ @pytest.mark.parametrize("version", [None, "", "2026", "2026.13.99", "unknown"])
255
+ def test_unparsable_ytdlp_version_is_not_stale(version):
256
+ assert dl.ytdlp_stale_age(version) is None
257
+
258
+
259
+ def test_check_warns_when_ytdlp_is_stale(monkeypatch):
260
+ monkeypatch.setattr(dl, "ytdlp_version", lambda: version_released_days_ago(200))
261
+ monkeypatch.setattr(dl, "find_ffmpeg", lambda: "/usr/bin/ffmpeg")
262
+ monkeypatch.setattr(dl, "find_js_runtime", lambda: ("node", "/usr/bin/node"))
263
+ result = CliRunner().invoke(cli, ["download", "check"])
264
+ assert result.exit_code == 0
265
+ assert "200 days old" in result.output.replace("\n", "")
266
+
267
+
268
+ def test_failure_on_stale_ytdlp_points_at_the_update(monkeypatch, tmp_path):
269
+ class Failed:
270
+ returncode = 1
271
+
272
+ monkeypatch.setattr(dl, "ensure_ytdlp", lambda assume_yes=False: ["yt-dlp"])
273
+ monkeypatch.setattr(dl, "find_ffmpeg", lambda: "/usr/bin/ffmpeg")
274
+ monkeypatch.setattr(dl, "ytdlp_version", lambda: version_released_days_ago(200))
275
+ monkeypatch.setattr(dl, "run_command", lambda *a, **kw: Failed())
276
+ result = CliRunner().invoke(cli, ["download", YOUTUBE, "-o", str(tmp_path)])
277
+ assert result.exit_code != 0
278
+ assert "evo download install" in result.output.replace("\n", "")
279
+
280
+
281
+ def test_failure_on_fresh_ytdlp_keeps_the_formats_hint(monkeypatch, tmp_path):
282
+ class Failed:
283
+ returncode = 1
284
+
285
+ monkeypatch.setattr(dl, "ensure_ytdlp", lambda assume_yes=False: ["yt-dlp"])
286
+ monkeypatch.setattr(dl, "find_ffmpeg", lambda: "/usr/bin/ffmpeg")
287
+ monkeypatch.setattr(dl, "ytdlp_version", lambda: version_released_days_ago(3))
288
+ monkeypatch.setattr(dl, "run_command", lambda *a, **kw: Failed())
289
+ result = CliRunner().invoke(cli, ["download", YOUTUBE, "-o", str(tmp_path)])
290
+ assert result.exit_code != 0
291
+ assert "evo download formats" in result.output.replace("\n", "")
@@ -1 +0,0 @@
1
- 0.19.0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes