dbagent-cli 0.9.3__tar.gz → 0.9.5__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.3 → dbagent_cli-0.9.5}/PKG-INFO +1 -1
  2. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/__init__.py +1 -1
  3. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/generator.py +6 -0
  4. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/cli.py +65 -21
  5. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/connectors/base.py +27 -3
  6. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/schema/ground_truth.py +31 -3
  7. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent_cli.egg-info/PKG-INFO +1 -1
  8. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/pyproject.toml +1 -1
  9. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/README.md +0 -0
  10. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/confidence.py +0 -0
  11. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/conversation.py +0 -0
  12. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/doctor.py +0 -0
  13. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/error_classifier.py +0 -0
  14. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/pipeline.py +0 -0
  15. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/recommender.py +0 -0
  16. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/validator.py +0 -0
  17. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/config.py +0 -0
  18. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/connectors/factory.py +0 -0
  19. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/connectors/mongo.py +0 -0
  20. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/connectors/relational.py +0 -0
  21. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/execution/__init__.py +0 -0
  22. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/execution/cache.py +0 -0
  23. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/execution/changeset.py +0 -0
  24. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/execution/cost.py +0 -0
  25. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/execution/recovery.py +0 -0
  26. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/execution/sandbox.py +0 -0
  27. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/execution/streaming.py +0 -0
  28. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/anthropic_provider.py +0 -0
  29. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/auto_setup.py +0 -0
  30. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/base.py +0 -0
  31. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/deepseek_provider.py +0 -0
  32. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/factory.py +0 -0
  33. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/gemini_provider.py +0 -0
  34. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/groq_provider.py +0 -0
  35. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/mistral_provider.py +0 -0
  36. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/mock_provider.py +0 -0
  37. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/ollama_provider.py +0 -0
  38. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/openai_provider.py +0 -0
  39. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/openrouter_provider.py +0 -0
  40. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/schema/cache.py +0 -0
  41. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/schema/formatter.py +0 -0
  42. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/schema/graph.py +0 -0
  43. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/schema/models.py +0 -0
  44. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/schema/retriever.py +0 -0
  45. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/schema/selector.py +0 -0
  46. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/security/__init__.py +0 -0
  47. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/security/audit.py +0 -0
  48. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/security/environment.py +0 -0
  49. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/security/masking.py +0 -0
  50. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/security/modes.py +0 -0
  51. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/security/rbac.py +0 -0
  52. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/semantic/__init__.py +0 -0
  53. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/semantic/dictionary.py +0 -0
  54. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/semantic/knowledge.py +0 -0
  55. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/semantic/templates.py +0 -0
  56. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/sql/__init__.py +0 -0
  57. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/sql/optimizer.py +0 -0
  58. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/sql/validator.py +0 -0
  59. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/ui/chat_screen.py +0 -0
  60. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/ui/console.py +0 -0
  61. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/ui/grid_window.py +0 -0
  62. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/ui/viewer.py +0 -0
  63. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/ui/visualizer.py +0 -0
  64. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent_cli.egg-info/SOURCES.txt +0 -0
  65. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent_cli.egg-info/dependency_links.txt +0 -0
  66. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent_cli.egg-info/entry_points.txt +0 -0
  67. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent_cli.egg-info/requires.txt +0 -0
  68. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent_cli.egg-info/top_level.txt +0 -0
  69. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/setup.cfg +0 -0
  70. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_cache.py +0 -0
  71. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_cli.py +0 -0
  72. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_connectors.py +0 -0
  73. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_direct_sql.py +0 -0
  74. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_doctor_and_viz.py +0 -0
  75. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_generator.py +0 -0
  76. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_graph.py +0 -0
  77. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_knowledge.py +0 -0
  78. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_providers.py +0 -0
  79. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_recovery.py +0 -0
  80. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_safety.py +0 -0
  81. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_sandbox.py +0 -0
  82. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_schema.py +0 -0
  83. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_security.py +0 -0
  84. {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/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.3
3
+ Version: 0.9.5
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.3"
5
+ __version__ = "0.9.5"
6
6
  __author__ = "Santhosh Gupta"
@@ -166,11 +166,17 @@ class ScriptGenerator:
166
166
  "related tables", "related tebales", "table mappings", "related tables mappings",
167
167
  "related tebales mappings", "not correct", "not right", "check all the tables",
168
168
  "mappings also", "find all mappings", "check links", "check mappings",
169
+ "is fk_", "are fk_", "is pk_", "are pk_", "difference between", "compare",
170
+ "tell me", "check it and tell me", "check and tell me", "same or different",
169
171
  ]
170
172
  for kw in analysis_keywords:
171
173
  if kw in prompt_lower:
172
174
  return "analysis"
173
175
 
176
+ # Comparative analysis questions (e.g. "is fk_site_group_id and fk_group_id same", "are table_a and table_b related")
177
+ if re.search(r'\b(?:is|are)\b.*\b(?:same|different|difference|related|equal|linked)\b', prompt_lower):
178
+ return "analysis"
179
+
174
180
  # Contextual clarification for analysis (e.g. "i mean ivis_scope", "i mean table_x")
175
181
  if re.search(r'\bi mean\b', prompt_lower) and not re.search(r'\b(insert|update|delete|drop)\b', prompt_lower):
176
182
  return "analysis"
@@ -98,10 +98,13 @@ audit_logger = AuditLogger()
98
98
  # Dynamic Animated Spinner with Rotating Messages
99
99
  # ---------------------------------------------------------------------------
100
100
 
101
+ _active_spinner: Optional["DynamicSpinner"] = None
102
+
103
+
101
104
  class DynamicSpinner:
102
105
  """
103
106
  Context manager that shows an animated spinner with rotating contextual messages.
104
- Messages cycle every ~1.5s so the user always sees progress.
107
+ Messages cycle every ~1.2s so the user always sees progress.
105
108
  Displays elapsed time suffix like (2.3s).
106
109
  """
107
110
 
@@ -193,7 +196,7 @@ class DynamicSpinner:
193
196
  ]
194
197
  return cls(messages=messages, interval=1.2)
195
198
 
196
- def __init__(self, messages: Optional[List[str]] = None, interval: float = 1.5):
199
+ def __init__(self, messages: Optional[List[str]] = None, interval: float = 1.2):
197
200
  self._messages = messages or self.DEFAULT_MESSAGES
198
201
  self._interval = interval
199
202
  self._stop_event = threading.Event()
@@ -202,6 +205,8 @@ class DynamicSpinner:
202
205
  self._status = None
203
206
 
204
207
  def __enter__(self):
208
+ global _active_spinner
209
+ _active_spinner = self
205
210
  self._start_time = time.time()
206
211
  self._stop_event.clear()
207
212
  self._status = console.status(f"[bold cyan]{self._messages[0]}[/bold cyan]")
@@ -211,11 +216,36 @@ class DynamicSpinner:
211
216
  return self
212
217
 
213
218
  def __exit__(self, *args):
219
+ global _active_spinner
220
+ _active_spinner = None
214
221
  self._stop_event.set()
215
222
  if self._thread:
216
- self._thread.join(timeout=2)
223
+ self._thread.join(timeout=1)
224
+ if self._status:
225
+ try:
226
+ self._status.__exit__(*args)
227
+ except Exception:
228
+ pass
229
+
230
+ def pause(self):
231
+ """Temporarily pause the spinner so interactive prompts don't clash."""
232
+ self._stop_event.set()
217
233
  if self._status:
218
- self._status.__exit__(*args)
234
+ try:
235
+ self._status.stop()
236
+ except Exception:
237
+ pass
238
+
239
+ def resume(self):
240
+ """Resume the spinner after interactive prompt finishes."""
241
+ self._stop_event.clear()
242
+ if self._status:
243
+ try:
244
+ self._status.start()
245
+ except Exception:
246
+ pass
247
+ self._thread = threading.Thread(target=self._rotate, daemon=True)
248
+ self._thread.start()
219
249
 
220
250
  @property
221
251
  def elapsed_ms(self) -> float:
@@ -265,28 +295,42 @@ def resolve_db_url(db_arg: Optional[str] = None) -> str:
265
295
 
266
296
  def _confirm_callback(sql: str, warnings: List[str]) -> bool:
267
297
  """Prompt user to confirm a write or DDL operation."""
268
- console.print("")
269
- print_code(sql, "sql", "Action Requires Confirmation")
270
- for w in warnings:
271
- print_warning(w)
272
- return Confirm.ask("Execute this statement on the database?", default=False)
298
+ global _active_spinner
299
+ if _active_spinner:
300
+ _active_spinner.pause()
301
+ try:
302
+ console.print("")
303
+ print_code(sql, "sql", "Action Requires Confirmation")
304
+ for w in warnings:
305
+ print_warning(w)
306
+ return Confirm.ask("Execute this statement on the database?", default=False)
307
+ finally:
308
+ if _active_spinner:
309
+ _active_spinner.resume()
273
310
 
274
311
 
275
312
  def _choice_callback(prompt_text: str, choices: List[str]) -> Optional[str]:
276
313
  """Prompt user to disambiguate table choices."""
277
- console.print(f"\n[bold yellow]?[/bold yellow] {prompt_text}")
278
- for i, c in enumerate(choices, 1):
279
- console.print(f" [{i}] {c}")
280
- choice = Prompt.ask("Select number or table name", default="1")
314
+ global _active_spinner
315
+ if _active_spinner:
316
+ _active_spinner.pause()
281
317
  try:
282
- idx = int(choice) - 1
283
- if 0 <= idx < len(choices):
284
- return choices[idx]
285
- except ValueError:
286
- pass
287
- if choice in choices:
288
- return choice
289
- return choices[0]
318
+ console.print(f"\n[bold yellow]?[/bold yellow] {prompt_text}")
319
+ for i, c in enumerate(choices, 1):
320
+ console.print(f" [{i}] {c}")
321
+ choice = Prompt.ask("Select number or table name", default="1")
322
+ try:
323
+ idx = int(choice) - 1
324
+ if 0 <= idx < len(choices):
325
+ return choices[idx]
326
+ except ValueError:
327
+ pass
328
+ if choice in choices:
329
+ return choice
330
+ return choices[0]
331
+ finally:
332
+ if _active_spinner:
333
+ _active_spinner.resume()
290
334
 
291
335
 
292
336
  def _display_pipeline_result(result: PipelineResult, show_sql: bool = True) -> None:
@@ -53,7 +53,29 @@ class BaseConnector(ABC):
53
53
  exact = []
54
54
  fuzzy = []
55
55
 
56
- tokens = set(re.findall(r'\b[a-zA-Z0-9_]+\b', prompt_lower))
56
+ stop_words = {
57
+ "check", "tell", "same", "show", "list", "find", "get", "is", "are", "does", "will", "can", "what",
58
+ "which", "how", "why", "where", "with", "and", "from", "that", "this", "these", "those", "have",
59
+ "has", "had", "not", "all", "any", "few", "some", "many", "each", "every", "table", "tables",
60
+ "column", "columns", "field", "fields", "row", "rows", "record", "records", "data", "info",
61
+ "information", "name", "names", "type", "types", "key", "keys", "id", "ids", "status", "true",
62
+ "false", "null", "seriously", "please", "mean", "like", "about", "between", "among", "into",
63
+ "onto", "over", "under", "before", "after", "during", "since", "until", "across", "along",
64
+ "throughout", "outside", "inside", "above", "below", "behind", "beyond", "plus", "except",
65
+ "but", "against", "towards", "upon", "within", "without", "give", "also", "explain", "look",
66
+ "know", "make", "take", "help", "view", "count", "done", "good", "nice", "okay", "fine"
67
+ }
68
+
69
+ raw_tokens = set(re.findall(r'\b[a-zA-Z0-9_]+\b', prompt_lower))
70
+ tokens = set(raw_tokens)
71
+
72
+ # Extract entity stems from foreign key column tokens (e.g. fk_site_group_id -> site_group, fk_group_id -> group)
73
+ for tok in raw_tokens:
74
+ if tok.startswith("fk_") or tok.startswith("pk_"):
75
+ entity = re.sub(r'^(?:fk_|pk_)', '', tok)
76
+ entity = re.sub(r'_(?:id|code|key|no|num)$', '', entity)
77
+ if entity and len(entity) >= 3 and entity not in stop_words:
78
+ tokens.add(entity)
57
79
 
58
80
  for t in all_tables:
59
81
  t_lower = t.lower()
@@ -64,7 +86,7 @@ class BaseConnector(ABC):
64
86
 
65
87
  # 2. Match without common prefixes (e.g. "ivis_scope" -> "scope", "scopes")
66
88
  unprefixed = re.sub(r'^(?:ivis_|tbl_|table_|app_)', '', t_lower)
67
- if unprefixed and len(unprefixed) >= 3:
89
+ if unprefixed and len(unprefixed) >= 3 and unprefixed not in stop_words:
68
90
  if (unprefixed in tokens or
69
91
  f"{unprefixed}s" in tokens or
70
92
  f"{unprefixed}es" in tokens or
@@ -73,8 +95,10 @@ class BaseConnector(ABC):
73
95
  exact.append(t)
74
96
  continue
75
97
 
76
- # 3. Fuzzy partial match
98
+ # 3. Fuzzy partial match (strictly exclude English stop words)
77
99
  for tok in tokens:
100
+ if tok in stop_words:
101
+ continue
78
102
  if len(tok) >= 4 and (tok in t_lower or t_lower in tok):
79
103
  if t not in exact and t not in fuzzy:
80
104
  fuzzy.append(t)
@@ -42,6 +42,8 @@ class GroundTruthValidationResult:
42
42
  lines.append(f" [dim]• ... and {len(self.verified_fks) - 4} more[/dim]")
43
43
  if self.sql_queries_checked > 0:
44
44
  lines.append(f" [bold green]✓[/bold green] [bold white]SQL Queries Verified:[/bold white] {self.sql_queries_checked} dialect statement(s) validated against schema")
45
+ for missing in self.missing_tables:
46
+ lines.append(f" [bold yellow]⚠ Note:[/bold yellow] Query referenced [dim]{missing}[/dim]")
45
47
  for tbl, cols in self.missing_columns.items():
46
48
  lines.append(f" [bold red]⚠ Warning:[/bold red] Column(s) [yellow]{', '.join(cols)}[/yellow] not found in table [cyan]{tbl}[/cyan]")
47
49
  return "\n".join(lines)
@@ -83,14 +85,40 @@ class SchemaGroundTruthValidator:
83
85
  if fk_desc not in result.verified_fks:
84
86
  result.verified_fks.append(fk_desc)
85
87
 
86
- # 2. Extract and count SQL queries
88
+ # 2. Extract and validate SQL queries against physical schema
87
89
  sql_blocks = re.findall(r'```(?:sql)?\s*([\s\S]*?)\s*```', response_text, re.IGNORECASE)
88
90
  result.sql_queries_checked = len(sql_blocks)
91
+ sql_referenced_tables = set()
92
+
93
+ for block in sql_blocks:
94
+ # Extract FROM and JOIN table references
95
+ matches = re.findall(r'\b(?:FROM|JOIN)\s+([a-zA-Z0-9_]+)', block, re.IGNORECASE)
96
+ for m in matches:
97
+ m_clean = m.strip().lower()
98
+ sql_referenced_tables.add(m_clean)
99
+
100
+ for sql_tbl in sql_referenced_tables:
101
+ if sql_tbl in schema_table_map:
102
+ if schema_table_map[sql_tbl].name not in result.verified_tables:
103
+ result.verified_tables.append(schema_table_map[sql_tbl].name)
104
+ else:
105
+ # Check if it's a common alias or slight naming divergence
106
+ found_match = None
107
+ for real_tbl in schema_table_map:
108
+ if sql_tbl in real_tbl or real_tbl in sql_tbl:
109
+ found_match = schema_table_map[real_tbl].name
110
+ break
111
+ if found_match:
112
+ result.missing_tables.append(f"{sql_tbl} ➔ (Actual: {found_match})")
113
+ else:
114
+ result.missing_tables.append(sql_tbl)
89
115
 
90
116
  total_checked = len(result.verified_tables)
91
- if total_checked > 0:
117
+ if result.missing_tables:
118
+ result.score_pct = max(70, 100 - (len(result.missing_tables) * 15))
119
+ elif total_checked > 0:
92
120
  result.score_pct = 100
93
121
  else:
94
122
  result.score_pct = 95
95
-
123
+
96
124
  return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbagent-cli
3
- Version: 0.9.3
3
+ Version: 0.9.5
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.3"
7
+ version = "0.9.5"
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"
File without changes
File without changes