dbagent-cli 0.9.6__tar.gz → 0.9.8__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 (84) hide show
  1. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/PKG-INFO +1 -1
  2. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/__init__.py +1 -1
  3. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/agent/generator.py +4 -0
  4. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/agent/pipeline.py +23 -4
  5. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/cli.py +43 -7
  6. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/connectors/relational.py +5 -1
  7. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/ui/chat_screen.py +8 -1
  8. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent_cli.egg-info/PKG-INFO +1 -1
  9. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/pyproject.toml +1 -1
  10. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_cli.py +19 -2
  11. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/README.md +0 -0
  12. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/agent/confidence.py +0 -0
  13. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/agent/conversation.py +0 -0
  14. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/agent/doctor.py +0 -0
  15. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/agent/error_classifier.py +0 -0
  16. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/agent/recommender.py +0 -0
  17. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/agent/validator.py +0 -0
  18. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/config.py +0 -0
  19. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/connectors/base.py +0 -0
  20. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/connectors/factory.py +0 -0
  21. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/connectors/mongo.py +0 -0
  22. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/execution/__init__.py +0 -0
  23. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/execution/cache.py +0 -0
  24. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/execution/changeset.py +0 -0
  25. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/execution/cost.py +0 -0
  26. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/execution/recovery.py +0 -0
  27. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/execution/sandbox.py +0 -0
  28. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/execution/streaming.py +0 -0
  29. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/llm/anthropic_provider.py +0 -0
  30. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/llm/auto_setup.py +0 -0
  31. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/llm/base.py +0 -0
  32. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/llm/deepseek_provider.py +0 -0
  33. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/llm/factory.py +0 -0
  34. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/llm/gemini_provider.py +0 -0
  35. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/llm/groq_provider.py +0 -0
  36. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/llm/mistral_provider.py +0 -0
  37. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/llm/mock_provider.py +0 -0
  38. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/llm/ollama_provider.py +0 -0
  39. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/llm/openai_provider.py +0 -0
  40. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/llm/openrouter_provider.py +0 -0
  41. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/schema/cache.py +0 -0
  42. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/schema/formatter.py +0 -0
  43. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/schema/graph.py +0 -0
  44. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/schema/ground_truth.py +0 -0
  45. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/schema/models.py +0 -0
  46. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/schema/retriever.py +0 -0
  47. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/schema/selector.py +0 -0
  48. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/security/__init__.py +0 -0
  49. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/security/audit.py +0 -0
  50. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/security/environment.py +0 -0
  51. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/security/masking.py +0 -0
  52. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/security/modes.py +0 -0
  53. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/security/rbac.py +0 -0
  54. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/semantic/__init__.py +0 -0
  55. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/semantic/dictionary.py +0 -0
  56. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/semantic/knowledge.py +0 -0
  57. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/semantic/templates.py +0 -0
  58. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/sql/__init__.py +0 -0
  59. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/sql/optimizer.py +0 -0
  60. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/sql/validator.py +0 -0
  61. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/ui/console.py +0 -0
  62. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/ui/grid_window.py +0 -0
  63. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/ui/viewer.py +0 -0
  64. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent/ui/visualizer.py +0 -0
  65. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent_cli.egg-info/SOURCES.txt +0 -0
  66. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent_cli.egg-info/dependency_links.txt +0 -0
  67. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent_cli.egg-info/entry_points.txt +0 -0
  68. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent_cli.egg-info/requires.txt +0 -0
  69. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/dbagent_cli.egg-info/top_level.txt +0 -0
  70. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/setup.cfg +0 -0
  71. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_cache.py +0 -0
  72. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_connectors.py +0 -0
  73. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_direct_sql.py +0 -0
  74. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_doctor_and_viz.py +0 -0
  75. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_generator.py +0 -0
  76. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_graph.py +0 -0
  77. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_knowledge.py +0 -0
  78. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_providers.py +0 -0
  79. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_recovery.py +0 -0
  80. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_safety.py +0 -0
  81. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_sandbox.py +0 -0
  82. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_schema.py +0 -0
  83. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_security.py +0 -0
  84. {dbagent_cli-0.9.6 → dbagent_cli-0.9.8}/tests/test_semantic.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbagent-cli
3
- Version: 0.9.6
3
+ Version: 0.9.8
4
4
  Summary: Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)
5
5
  Author: Santhosh Gupta
6
6
  Requires-Python: >=3.9
@@ -2,5 +2,5 @@
2
2
  DB-Agent: Universal Database Introspection and Script Generation AI Agent (CLI).
3
3
  """
4
4
 
5
- __version__ = "0.9.6"
5
+ __version__ = "0.9.8"
6
6
  __author__ = "Santhosh Gupta"
@@ -247,6 +247,7 @@ class ScriptGenerator:
247
247
  user_prompt: str,
248
248
  conversation_history: Optional[str] = None,
249
249
  model: Optional[str] = None,
250
+ stream_callback: Optional[Callable[[str], None]] = None,
250
251
  ) -> str:
251
252
  """Generate a natural conversational response to the user's message with full session memory."""
252
253
  table_names = [t.name for t in schema.tables]
@@ -268,6 +269,7 @@ class ScriptGenerator:
268
269
  prompt=full_prompt,
269
270
  system_prompt=system_prompt,
270
271
  model=model,
272
+ stream_callback=stream_callback,
271
273
  )
272
274
 
273
275
  def generate_analysis_response(
@@ -276,6 +278,7 @@ class ScriptGenerator:
276
278
  user_prompt: str,
277
279
  conversation_history: Optional[str] = None,
278
280
  model: Optional[str] = None,
281
+ stream_callback: Optional[Callable[[str], None]] = None,
279
282
  ) -> str:
280
283
  """Generate a comprehensive architectural analysis report with ASCII diagrams, theory, and SQL queries."""
281
284
  # 1. Filter relevant tables including foreign keys and junction tables
@@ -301,6 +304,7 @@ class ScriptGenerator:
301
304
  prompt=full_prompt,
302
305
  system_prompt=system_prompt,
303
306
  model=model,
307
+ stream_callback=stream_callback,
304
308
  )
305
309
 
306
310
  # --- Full generation (for `generate` command) ---
@@ -56,6 +56,7 @@ class PipelineResult:
56
56
  # Conversational chat response
57
57
  is_chat: bool = False
58
58
  is_direct_sql: bool = False
59
+ was_streamed: bool = False
59
60
  message: str = ""
60
61
  # Query classification
61
62
  query_type: str = "unknown" # read, write, ddl, chat, unknown
@@ -192,7 +193,13 @@ class QueryPipeline:
192
193
  retriever = ContextRetriever(targeted)
193
194
  return retriever.retrieve_context(user_prompt, exact_tables=exact, fuzzy_tables=fuzzy, max_tables=12)
194
195
 
195
- def run(self, user_prompt: str, force: bool = False, bypass_cache: bool = False) -> PipelineResult:
196
+ def run(
197
+ self,
198
+ user_prompt: str,
199
+ force: bool = False,
200
+ bypass_cache: bool = False,
201
+ stream_callback: Optional[Callable[[str], None]] = None,
202
+ ) -> PipelineResult:
196
203
  """
197
204
  Executes end-to-end pipeline: Prompt -> Security/Semantics -> SQL -> Safe Exec -> Results.
198
205
  """
@@ -217,7 +224,10 @@ class QueryPipeline:
217
224
  user_prompt=user_prompt,
218
225
  conversation_history=conv_history,
219
226
  model=self.model,
227
+ stream_callback=stream_callback,
220
228
  )
229
+ if stream_callback:
230
+ result.was_streamed = True
221
231
  result.is_chat = True
222
232
  result.was_executed = True
223
233
  result.query_type = "chat"
@@ -238,12 +248,18 @@ class QueryPipeline:
238
248
  exact, fuzzy = [], []
239
249
 
240
250
  # Inherit active tables from conversation context if follow-up prompt doesn't name tables explicitly
251
+ is_inherited = False
241
252
  if not exact and not fuzzy:
242
253
  inherited = self.conversation.get_active_tables()
243
254
  if inherited:
244
255
  exact = inherited[:8]
256
+ is_inherited = True
257
+
258
+ effective_prompt = user_prompt
259
+ if is_inherited and exact:
260
+ effective_prompt = f"Focus strictly on the architecture, linkages, and data flow between {', '.join(exact[:5])}. User request: {user_prompt}"
245
261
 
246
- sub_schema, join_steps = self._get_fast_schema_context(user_prompt, exact, fuzzy)
262
+ sub_schema, join_steps = self._get_fast_schema_context(effective_prompt, exact, fuzzy)
247
263
  result.schema = sub_schema
248
264
  result.join_steps = join_steps
249
265
 
@@ -258,16 +274,19 @@ class QueryPipeline:
258
274
  source="user_guidance",
259
275
  )
260
276
 
261
- kb_context = self.knowledge_base.format_prompt_context(self.db_name, user_prompt, exact + fuzzy)
277
+ kb_context = self.knowledge_base.format_prompt_context(self.db_name, effective_prompt, exact + fuzzy)
262
278
  conv_history = self.conversation.format_context_for_prompt()
263
- analysis_prompt = f"{kb_context}\n\n{user_prompt}" if kb_context else user_prompt
279
+ analysis_prompt = f"{kb_context}\n\n{effective_prompt}" if kb_context else effective_prompt
264
280
 
265
281
  result.message = self.generator.generate_analysis_response(
266
282
  schema=sub_schema,
267
283
  user_prompt=analysis_prompt,
268
284
  conversation_history=conv_history,
269
285
  model=self.model,
286
+ stream_callback=stream_callback,
270
287
  )
288
+ if stream_callback:
289
+ result.was_streamed = True
271
290
  result.ground_truth = SchemaGroundTruthValidator.validate_response(result.message, sub_schema)
272
291
  result.is_chat = True
273
292
  result.was_executed = True
@@ -339,7 +339,10 @@ def _display_pipeline_result(result: PipelineResult, show_sql: bool = True) -> N
339
339
 
340
340
  if result.is_chat and result.message:
341
341
  console.print("")
342
- console.print(Markdown(result.message))
342
+ if not result.was_streamed:
343
+ console.print(Markdown(result.message))
344
+ if result.ground_truth:
345
+ console.print(result.ground_truth.format_summary())
343
346
  if result.execution_time_ms > 0:
344
347
  print_timing(result.execution_time_ms)
345
348
  console.print("")
@@ -527,8 +530,25 @@ def ask_command(
527
530
  choice_callback=_choice_callback,
528
531
  )
529
532
 
530
- with DynamicSpinner():
531
- result = pipeline.run(user_prompt=prompt, force=force, bypass_cache=no_cache)
533
+ spinner = DynamicSpinner.for_prompt(prompt, model_name=model or getattr(llm, "default_model", None), connector=connector)
534
+ stream_started = [False]
535
+
536
+ def _query_stream_callback(chunk: str) -> None:
537
+ if not stream_started[0]:
538
+ stream_started[0] = True
539
+ spinner.pause()
540
+ timing_str = f"⚡ {spinner.elapsed_ms / 1000:.2f}s" if spinner.elapsed_ms > 0 else ""
541
+ active_model = model or getattr(llm, "default_model", None)
542
+ model_display = f" ({active_model})" if active_model else ""
543
+ console.print(f"\n[bold cyan]● DB-Agent{model_display}[/bold cyan] [dim]{timing_str}[/dim]")
544
+ try:
545
+ sys.stdout.write(chunk)
546
+ sys.stdout.flush()
547
+ except Exception:
548
+ pass
549
+
550
+ with spinner:
551
+ result = pipeline.run(user_prompt=prompt, force=force, bypass_cache=no_cache, stream_callback=_query_stream_callback)
532
552
 
533
553
  _display_pipeline_result(result, show_sql=True)
534
554
  connector.close()
@@ -1094,14 +1114,30 @@ def chat_command(
1094
1114
  console.print("[bold cyan]Goodbye![/bold cyan]")
1095
1115
  break
1096
1116
 
1097
- # --- Natural language query via pipeline ---
1098
- # Update pipeline settings in-place (pipeline was created once before the loop)
1117
+ # --- Natural language query via pipeline with live streaming ---
1099
1118
  pipeline.model = model
1100
1119
  pipeline.mode = current_mode
1101
1120
  pipeline.auto_execute = auto_execute
1102
1121
 
1103
- with DynamicSpinner.for_prompt(user_input, model_name=model or getattr(llm, "default_model", None), connector=connector):
1104
- result = pipeline.run(user_prompt=user_input)
1122
+ spinner = DynamicSpinner.for_prompt(user_input, model_name=model or getattr(llm, "default_model", None), connector=connector)
1123
+ stream_started = [False]
1124
+
1125
+ def _live_stream_callback(chunk: str) -> None:
1126
+ if not stream_started[0]:
1127
+ stream_started[0] = True
1128
+ spinner.pause()
1129
+ timing_str = f"⚡ {spinner.elapsed_ms / 1000:.2f}s" if spinner.elapsed_ms > 0 else ""
1130
+ active_model = model or getattr(llm, "default_model", None)
1131
+ model_display = f" ({active_model})" if active_model else ""
1132
+ console.print(f"\n[bold cyan]● DB-Agent{model_display}[/bold cyan] [dim]{timing_str}[/dim]")
1133
+ try:
1134
+ sys.stdout.write(chunk)
1135
+ sys.stdout.flush()
1136
+ except Exception:
1137
+ pass
1138
+
1139
+ with spinner:
1140
+ result = pipeline.run(user_prompt=user_input, stream_callback=_live_stream_callback)
1105
1141
 
1106
1142
  render_assistant_turn(
1107
1143
  result,
@@ -47,7 +47,7 @@ def _fuzzy_match_tables(table_names: List[str], user_prompt: str) -> Tuple[List[
47
47
  exact: List[str] = []
48
48
  fuzzy: List[str] = []
49
49
 
50
- # Extract meaningful words from the prompt (3+ chars, skip SQL keywords)
50
+ # Extract meaningful words from the prompt (3+ chars, skip SQL keywords and diagram meta-terms)
51
51
  sql_keywords = {
52
52
  "select", "from", "where", "join", "left", "right", "inner", "outer",
53
53
  "order", "group", "having", "limit", "offset", "insert", "update",
@@ -57,6 +57,10 @@ def _fuzzy_match_tables(table_names: List[str], user_prompt: str) -> Tuple[List[
57
57
  "drop", "index", "primary", "foreign", "key", "null", "default",
58
58
  "hour", "minute", "day", "week", "month", "year", "ago", "today",
59
59
  "give", "tell", "want", "need", "please", "display", "fetch",
60
+ "flow", "diagram", "chart", "graph", "link", "links", "structure",
61
+ "design", "model", "blueprint", "mapping", "mappings", "overview",
62
+ "query", "code", "script", "report", "summary", "relation",
63
+ "relations", "relationship", "relationships", "architecture", "check",
60
64
  }
61
65
  prompt_words = set()
62
66
  for word in re.findall(r"\b[a-z_][a-z0-9_]*\b", prompt_lower):
@@ -114,8 +114,15 @@ def render_assistant_turn(
114
114
  model_display = f" ({ai_model})" if ai_model else ""
115
115
  agent_title = f"[bold cyan]● DB-Agent{model_display}[/bold cyan] [dim]{timing_str}[/dim]"
116
116
 
117
- # Conversational Chat Response
117
+ # Conversational Chat / Architectural Analysis Response
118
118
  if result.is_chat and result.message:
119
+ if result.was_streamed:
120
+ console.print("")
121
+ if result.ground_truth:
122
+ console.print(result.ground_truth.format_summary())
123
+ console.print("")
124
+ return
125
+
119
126
  console.print(f"\n{agent_title}")
120
127
  console.print(Markdown(result.message))
121
128
  if result.ground_truth:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbagent-cli
3
- Version: 0.9.6
3
+ Version: 0.9.8
4
4
  Summary: Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)
5
5
  Author: Santhosh Gupta
6
6
  Requires-Python: >=3.9
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dbagent-cli"
7
- version = "0.9.6"
7
+ version = "0.9.8"
8
8
  description = "Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -481,6 +481,25 @@ def test_cli_changes_and_audit_commands():
481
481
  assert res_audit.exit_code == 0
482
482
 
483
483
 
484
+ def test_pipeline_streaming_callback(sample_db: str):
485
+ from dbagent.connectors.factory import create_connector
486
+ from dbagent.llm.mock_provider import MockProvider
487
+ from dbagent.agent.pipeline import QueryPipeline
488
+
489
+ connector = create_connector(sample_db)
490
+ llm = MockProvider()
491
+ pipeline = QueryPipeline(connector=connector, llm=llm)
492
+
493
+ chunks = []
494
+ def callback(token: str):
495
+ chunks.append(token)
496
+
497
+ result = pipeline.run("hello assistant", stream_callback=callback)
498
+ assert result.was_streamed is True
499
+ assert result.is_chat is True
500
+ connector.close()
501
+
502
+
484
503
  def test_cli_recovery_commands():
485
504
  res_list = runner.invoke(app, ["recovery", "list"])
486
505
  assert res_list.exit_code == 0
@@ -491,5 +510,3 @@ def test_cli_recovery_commands():
491
510
 
492
511
  res_cleanup = runner.invoke(app, ["recovery", "cleanup"])
493
512
  assert res_cleanup.exit_code == 0
494
-
495
-
File without changes
File without changes