zai-cli 0.1.2__tar.gz → 0.1.3__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 (104) hide show
  1. {zai_cli-0.1.2 → zai_cli-0.1.3}/CHANGELOG.md +9 -0
  2. {zai_cli-0.1.2/zai_cli.egg-info → zai_cli-0.1.3}/PKG-INFO +1 -1
  3. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_interactive.py +64 -2
  4. zai_cli-0.1.3/zai/__init__.py +1 -0
  5. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/cli/interactive.py +87 -6
  6. {zai_cli-0.1.2 → zai_cli-0.1.3/zai_cli.egg-info}/PKG-INFO +1 -1
  7. zai_cli-0.1.2/zai/__init__.py +0 -1
  8. {zai_cli-0.1.2 → zai_cli-0.1.3}/LICENSE +0 -0
  9. {zai_cli-0.1.2 → zai_cli-0.1.3}/MANIFEST.in +0 -0
  10. {zai_cli-0.1.2 → zai_cli-0.1.3}/README.md +0 -0
  11. {zai_cli-0.1.2 → zai_cli-0.1.3}/pyproject.toml +0 -0
  12. {zai_cli-0.1.2 → zai_cli-0.1.3}/scripts/release_preflight.py +0 -0
  13. {zai_cli-0.1.2 → zai_cli-0.1.3}/setup.cfg +0 -0
  14. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_agent.py +0 -0
  15. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_browser.py +0 -0
  16. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_code_runner.py +0 -0
  17. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_config_main.py +0 -0
  18. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_core.py +0 -0
  19. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_errors.py +0 -0
  20. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_hooks_skills_session.py +0 -0
  21. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_input.py +0 -0
  22. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_integrations_cli.py +0 -0
  23. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_mcp.py +0 -0
  24. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_plugins.py +0 -0
  25. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_process.py +0 -0
  26. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_providers.py +0 -0
  27. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_release_preflight.py +0 -0
  28. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_search.py +0 -0
  29. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_security.py +0 -0
  30. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_settings_cli.py +0 -0
  31. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_storage.py +0 -0
  32. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_streaming.py +0 -0
  33. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_tool_schema.py +0 -0
  34. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_tools.py +0 -0
  35. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_undo.py +0 -0
  36. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_utilities.py +0 -0
  37. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_vision.py +0 -0
  38. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_watch.py +0 -0
  39. {zai_cli-0.1.2 → zai_cli-0.1.3}/tests/test_workflows.py +0 -0
  40. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/__main__.py +0 -0
  41. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/cli/__init__.py +0 -0
  42. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/cli/common.py +0 -0
  43. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/cli/integrations.py +0 -0
  44. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/cli/settings.py +0 -0
  45. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/cli/utilities.py +0 -0
  46. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/cli/workflows.py +0 -0
  47. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/commands/commit.md +0 -0
  48. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/commands/explain.md +0 -0
  49. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/commands/feature.md +0 -0
  50. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/commands/fix.md +0 -0
  51. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/commands/review.md +0 -0
  52. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/config.py +0 -0
  53. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/__init__.py +0 -0
  54. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/agent.py +0 -0
  55. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/cancellation.py +0 -0
  56. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/commands.py +0 -0
  57. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/context.py +0 -0
  58. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/errors.py +0 -0
  59. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/fallback.py +0 -0
  60. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/hooks.py +0 -0
  61. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/memory.py +0 -0
  62. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/process.py +0 -0
  63. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/repomap.py +0 -0
  64. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/runtime.py +0 -0
  65. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/security.py +0 -0
  66. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/session.py +0 -0
  67. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/storage.py +0 -0
  68. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/streaming.py +0 -0
  69. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/tool_schema.py +0 -0
  70. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/undo.py +0 -0
  71. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/core/watch.py +0 -0
  72. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/main.py +0 -0
  73. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/mcp/__init__.py +0 -0
  74. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/mcp/client.py +0 -0
  75. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/mcp/manager.py +0 -0
  76. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/plugins/__init__.py +0 -0
  77. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/plugins/base.py +0 -0
  78. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/plugins/loader.py +0 -0
  79. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/__init__.py +0 -0
  80. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/anthropic.py +0 -0
  81. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/base.py +0 -0
  82. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/cerebras.py +0 -0
  83. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/gemini.py +0 -0
  84. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/groq.py +0 -0
  85. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/ollama.py +0 -0
  86. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/openai.py +0 -0
  87. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/openrouter.py +0 -0
  88. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/providers/qwen.py +0 -0
  89. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/skills/__init__.py +0 -0
  90. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/skills/registry.py +0 -0
  91. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/tools/__init__.py +0 -0
  92. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/tools/browser.py +0 -0
  93. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/tools/code_runner.py +0 -0
  94. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/tools/files.py +0 -0
  95. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/tools/git.py +0 -0
  96. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/tools/search.py +0 -0
  97. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/tools/vision.py +0 -0
  98. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/ui/__init__.py +0 -0
  99. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai/ui/input.py +0 -0
  100. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai_cli.egg-info/SOURCES.txt +0 -0
  101. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai_cli.egg-info/dependency_links.txt +0 -0
  102. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai_cli.egg-info/entry_points.txt +0 -0
  103. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai_cli.egg-info/requires.txt +0 -0
  104. {zai_cli-0.1.2 → zai_cli-0.1.3}/zai_cli.egg-info/top_level.txt +0 -0
@@ -5,6 +5,15 @@ All notable changes to this project are documented in this file.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and the project uses [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [0.1.3] - 2026-06-24
9
+
10
+ ### Fixed
11
+
12
+ - Made Ctrl+C exit predictable in interactive mode: one Ctrl+C cancels the
13
+ current prompt/operation, and a second consecutive Ctrl+C exits the session.
14
+ - Added in-chat key setup: starting `zai` without a provider now offers API-key
15
+ setup immediately, and `/setup` can save keys without leaving interactive mode.
16
+
8
17
  ## [0.1.2] - 2026-06-24
9
18
 
10
19
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zai-cli
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Your personal AI CLI — free, fast, and smart
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/HumaizaNaz/zai_cli
@@ -38,15 +38,58 @@ def test_interactive_plain_commands_show_local_help(tmp_path, monkeypatch, capsy
38
38
  side_effect=["/", "commands", "setup", "/model", EOFError()],
39
39
  ):
40
40
  with patch("zai.cli.interactive.run_agent") as run_agent:
41
- run_interactive("groq")
41
+ with patch(
42
+ "zai.cli.interactive._run_key_setup_interactive",
43
+ return_value=True,
44
+ ) as setup:
45
+ run_interactive("groq")
42
46
 
43
47
  output = capsys.readouterr().out
44
48
  assert "Interactive slash commands" in output
45
- assert "API key setup" in output
49
+ setup.assert_called_once()
46
50
  assert "Available models" in output
47
51
  run_agent.assert_not_called()
48
52
 
49
53
 
54
+ def test_interactive_offers_setup_when_no_provider(tmp_path, monkeypatch, capsys):
55
+ monkeypatch.chdir(tmp_path)
56
+ with patch("zai.cli.interactive.has_available_provider", return_value=False):
57
+ with patch("zai.cli.interactive.Confirm.ask", return_value=True):
58
+ with patch(
59
+ "zai.cli.interactive._run_key_setup_interactive",
60
+ return_value=True,
61
+ ) as setup:
62
+ with patch("zai.cli.interactive.fire_hook", return_value=True):
63
+ with patch("zai.cli.interactive.plugin_loader.load_all", return_value={}):
64
+ with patch("zai.cli.interactive.plugin_loader.get_errors", return_value={}):
65
+ with patch("zai.cli.interactive._connect_mcp_servers"):
66
+ with patch(
67
+ "zai.cli.interactive.InteractiveInput.prompt",
68
+ side_effect=EOFError(),
69
+ ):
70
+ run_interactive("groq")
71
+
72
+ output = capsys.readouterr().out
73
+ assert "No AI provider available" in output
74
+ setup.assert_called_once()
75
+
76
+
77
+ def test_interactive_key_setup_saves_entered_key(capsys):
78
+ from zai.cli.interactive import _run_key_setup_interactive
79
+
80
+ answers = iter(["gemini-key", "", "", "", "", "", ""])
81
+
82
+ with patch("zai.cli.interactive.get_api_key", return_value=None):
83
+ with patch("zai.cli.interactive.Prompt.ask", side_effect=lambda *a, **k: next(answers)):
84
+ with patch("zai.cli.interactive.save_api_key") as save_key:
85
+ with patch("zai.cli.interactive.has_available_provider", return_value=True):
86
+ assert _run_key_setup_interactive()
87
+
88
+ output = capsys.readouterr().out
89
+ assert "Provider key setup" in output
90
+ save_key.assert_called_once_with("gemini", "gemini-key")
91
+
92
+
50
93
  def test_interactive_help_then_exit(tmp_path, monkeypatch, capsys):
51
94
  monkeypatch.chdir(tmp_path)
52
95
  with patch("zai.cli.interactive.has_available_provider", return_value=True):
@@ -87,6 +130,25 @@ def test_interactive_fires_session_end(tmp_path, monkeypatch):
87
130
  assert any(call.args[0] == "SessionEnd" for call in fire.call_args_list)
88
131
 
89
132
 
133
+ def test_interactive_exits_after_second_input_cancel(tmp_path, monkeypatch, capsys):
134
+ monkeypatch.chdir(tmp_path)
135
+ with patch("zai.cli.interactive.has_available_provider", return_value=True):
136
+ with patch("zai.cli.interactive.fire_hook", return_value=True) as fire:
137
+ with patch("zai.cli.interactive.plugin_loader.load_all", return_value={}):
138
+ with patch("zai.cli.interactive.plugin_loader.get_errors", return_value={}):
139
+ with patch("zai.cli.interactive._connect_mcp_servers"):
140
+ with patch(
141
+ "zai.cli.interactive.InteractiveInput.prompt",
142
+ side_effect=[KeyboardInterrupt(), KeyboardInterrupt()],
143
+ ):
144
+ run_interactive("groq")
145
+
146
+ output = capsys.readouterr().out
147
+ assert "Press Ctrl+C again to exit" in output
148
+ assert "Goodbye" in output
149
+ assert any(call.args[0] == "SessionEnd" for call in fire.call_args_list)
150
+
151
+
90
152
  def test_interactive_session_rename_routes_arguments(tmp_path, monkeypatch):
91
153
  monkeypatch.chdir(tmp_path)
92
154
  with patch("zai.cli.interactive.has_available_provider", return_value=True):
@@ -0,0 +1 @@
1
+ __version__ = "0.1.3"
@@ -7,7 +7,7 @@ from rich.panel import Panel
7
7
  from rich.prompt import Confirm, Prompt
8
8
  from rich.table import Table
9
9
 
10
- from ..config import get_api_key, get_models, load_config
10
+ from ..config import ENV_FILE, get_api_key, get_models, load_config, save_api_key, save_config
11
11
  from ..core.agent import plan_agent, run_agent, undo_last
12
12
  from ..core.commands import get_command_prompt, list_commands, load_commands
13
13
  from ..core.hooks import fire as fire_hook
@@ -73,6 +73,16 @@ PLAIN_COMMAND_ALIASES = {
73
73
  "undo": "/undo",
74
74
  }
75
75
 
76
+ KEY_SETUP_PROVIDERS = [
77
+ ("gemini", "Google Gemini (free, recommended)", "https://aistudio.google.com/app/apikey"),
78
+ ("groq", "Groq (free, fast)", "https://console.groq.com/keys"),
79
+ ("openai", "OpenAI (paid)", "https://platform.openai.com/api-keys"),
80
+ ("cerebras", "Cerebras (free)", "https://cloud.cerebras.ai/platform"),
81
+ ("openrouter", "OpenRouter", "https://openrouter.ai/keys"),
82
+ ("qwen", "Qwen/DashScope", "https://dashscope.aliyun.com/"),
83
+ ("anthropic", "Anthropic Claude (paid)", "https://console.anthropic.com/settings/keys"),
84
+ ]
85
+
76
86
 
77
87
  def _show_help() -> None:
78
88
  console.print(
@@ -151,6 +161,58 @@ def _show_models() -> None:
151
161
  console.print(table)
152
162
 
153
163
 
164
+ def _run_key_setup_interactive() -> bool:
165
+ console.print(Panel(
166
+ "[bold cyan]Provider key setup[/bold cyan]\n"
167
+ "Add at least one API key. Gemini and Groq are good free starting points; "
168
+ "OpenAI is paid.\n"
169
+ f"[dim]Keys are saved locally in {ENV_FILE}[/dim]",
170
+ border_style="cyan",
171
+ ))
172
+ for provider, label, url in KEY_SETUP_PROVIDERS:
173
+ existing = get_api_key(provider)
174
+ if existing:
175
+ console.print(f"[green]✓[/green] {label} already set")
176
+ value = Prompt.ask(
177
+ f"[cyan]{label}[/cyan] new API key [dim](Enter to keep)[/dim]",
178
+ default="",
179
+ password=True,
180
+ )
181
+ else:
182
+ console.print(f"[dim]{label} key: {url}[/dim]")
183
+ value = Prompt.ask(
184
+ f"[cyan]{label}[/cyan] API key [dim](Enter to skip)[/dim]",
185
+ default="",
186
+ password=True,
187
+ )
188
+ if value.strip():
189
+ save_api_key(provider, value.strip())
190
+ console.print("[green]✓ Saved[/green]")
191
+
192
+ config = load_config()
193
+ models = get_models(config)
194
+ available_free = [
195
+ name for name, data in models.items()
196
+ if data["free"] and get_api_key(data["provider"])
197
+ ]
198
+ if available_free:
199
+ current = config.get("default_model")
200
+ default = current if current in models else available_free[0]
201
+ selected = Prompt.ask(
202
+ "Default model",
203
+ choices=list(models.keys()),
204
+ default=default,
205
+ )
206
+ config["default_model"] = selected
207
+ save_config(config)
208
+
209
+ if has_available_provider():
210
+ console.print("[green]zai is ready.[/green]")
211
+ return True
212
+ console.print("[yellow]No provider key was saved. Run /setup when ready.[/yellow]")
213
+ return False
214
+
215
+
154
216
  def _normalize_plain_command(message: str) -> str:
155
217
  stripped = message.strip()
156
218
  if stripped == "/":
@@ -243,10 +305,13 @@ def run_interactive(model: str = None) -> None:
243
305
  if not has_available_provider():
244
306
  console.print(Panel(
245
307
  "[red]No AI provider available![/red]\n"
246
- "Run [cyan]zai setup[/cyan] or start Ollama.",
308
+ "Set up an API key now, run [cyan]zai setup[/cyan], or start Ollama.",
247
309
  border_style="red",
248
310
  ))
249
- return
311
+ if not Confirm.ask("Set up an API key now?", default=True):
312
+ return
313
+ if not _run_key_setup_interactive():
314
+ return
250
315
 
251
316
  try:
252
317
  visible = [name for name in sorted(os.listdir(cwd)) if not name.startswith(".")]
@@ -328,15 +393,23 @@ def run_interactive(model: str = None) -> None:
328
393
  session_context.compress(preferred)
329
394
  save_auto_history(history, cwd)
330
395
 
396
+ cancelled_once = False
397
+
331
398
  while True:
332
399
  try:
333
400
  try:
334
401
  message = terminal_input.prompt()
335
402
  except KeyboardInterrupt:
336
- console.print("[dim]Input cancelled.[/dim]")
403
+ if cancelled_once:
404
+ fire_hook("SessionEnd", {"cwd": cwd, "messages": len(history)})
405
+ console.print("\n[dim]Goodbye![/dim]")
406
+ break
407
+ cancelled_once = True
408
+ console.print("[dim]Input cancelled. Press Ctrl+C again to exit.[/dim]")
337
409
  continue
338
410
  if not message.strip():
339
411
  continue
412
+ cancelled_once = False
340
413
  message = _correct_slash_command(message)
341
414
  message = _normalize_plain_command(message)
342
415
  stripped = message.strip()
@@ -346,7 +419,7 @@ def run_interactive(model: str = None) -> None:
346
419
  elif stripped == "/commands":
347
420
  _show_commands()
348
421
  elif stripped == "/setup":
349
- _show_setup_hint()
422
+ _run_key_setup_interactive()
350
423
  elif stripped == "/clear":
351
424
  console.clear()
352
425
  elif stripped == "/files":
@@ -584,7 +657,15 @@ def run_interactive(model: str = None) -> None:
584
657
  remember_turn(stripped, result)
585
658
  save_session(task=stripped[:80], model=preferred)
586
659
  except KeyboardInterrupt:
587
- console.print("\n[yellow]Current operation cancelled.[/yellow]")
660
+ if cancelled_once:
661
+ fire_hook("SessionEnd", {"cwd": cwd, "messages": len(history)})
662
+ console.print("\n[dim]Goodbye![/dim]")
663
+ break
664
+ cancelled_once = True
665
+ console.print(
666
+ "\n[yellow]Current operation cancelled. "
667
+ "Press Ctrl+C again to exit.[/yellow]"
668
+ )
588
669
  continue
589
670
  except EOFError:
590
671
  fire_hook("SessionEnd", {"cwd": cwd, "messages": len(history)})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zai-cli
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Your personal AI CLI — free, fast, and smart
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/HumaizaNaz/zai_cli
@@ -1 +0,0 @@
1
- __version__ = "0.1.2"
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
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
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