dbagent-cli 0.9.4__tar.gz → 0.9.6__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.4 → dbagent_cli-0.9.6}/PKG-INFO +1 -1
  2. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/__init__.py +1 -1
  3. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/agent/generator.py +16 -0
  4. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/agent/pipeline.py +8 -2
  5. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/cli.py +65 -21
  6. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/connectors/base.py +30 -3
  7. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent_cli.egg-info/PKG-INFO +1 -1
  8. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/pyproject.toml +1 -1
  9. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/README.md +0 -0
  10. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/agent/confidence.py +0 -0
  11. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/agent/conversation.py +0 -0
  12. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/agent/doctor.py +0 -0
  13. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/agent/error_classifier.py +0 -0
  14. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/agent/recommender.py +0 -0
  15. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/agent/validator.py +0 -0
  16. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/config.py +0 -0
  17. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/connectors/factory.py +0 -0
  18. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/connectors/mongo.py +0 -0
  19. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/connectors/relational.py +0 -0
  20. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/execution/__init__.py +0 -0
  21. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/execution/cache.py +0 -0
  22. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/execution/changeset.py +0 -0
  23. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/execution/cost.py +0 -0
  24. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/execution/recovery.py +0 -0
  25. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/execution/sandbox.py +0 -0
  26. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/execution/streaming.py +0 -0
  27. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/llm/anthropic_provider.py +0 -0
  28. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/llm/auto_setup.py +0 -0
  29. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/llm/base.py +0 -0
  30. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/llm/deepseek_provider.py +0 -0
  31. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/llm/factory.py +0 -0
  32. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/llm/gemini_provider.py +0 -0
  33. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/llm/groq_provider.py +0 -0
  34. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/llm/mistral_provider.py +0 -0
  35. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/llm/mock_provider.py +0 -0
  36. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/llm/ollama_provider.py +0 -0
  37. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/llm/openai_provider.py +0 -0
  38. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/llm/openrouter_provider.py +0 -0
  39. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/schema/cache.py +0 -0
  40. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/schema/formatter.py +0 -0
  41. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/schema/graph.py +0 -0
  42. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/schema/ground_truth.py +0 -0
  43. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/schema/models.py +0 -0
  44. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/schema/retriever.py +0 -0
  45. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/schema/selector.py +0 -0
  46. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/security/__init__.py +0 -0
  47. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/security/audit.py +0 -0
  48. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/security/environment.py +0 -0
  49. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/security/masking.py +0 -0
  50. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/security/modes.py +0 -0
  51. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/security/rbac.py +0 -0
  52. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/semantic/__init__.py +0 -0
  53. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/semantic/dictionary.py +0 -0
  54. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/semantic/knowledge.py +0 -0
  55. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/semantic/templates.py +0 -0
  56. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/sql/__init__.py +0 -0
  57. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/sql/optimizer.py +0 -0
  58. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/sql/validator.py +0 -0
  59. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/ui/chat_screen.py +0 -0
  60. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/ui/console.py +0 -0
  61. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/ui/grid_window.py +0 -0
  62. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/ui/viewer.py +0 -0
  63. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent/ui/visualizer.py +0 -0
  64. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent_cli.egg-info/SOURCES.txt +0 -0
  65. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent_cli.egg-info/dependency_links.txt +0 -0
  66. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent_cli.egg-info/entry_points.txt +0 -0
  67. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent_cli.egg-info/requires.txt +0 -0
  68. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/dbagent_cli.egg-info/top_level.txt +0 -0
  69. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/setup.cfg +0 -0
  70. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/tests/test_cache.py +0 -0
  71. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/tests/test_cli.py +0 -0
  72. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/tests/test_connectors.py +0 -0
  73. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/tests/test_direct_sql.py +0 -0
  74. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/tests/test_doctor_and_viz.py +0 -0
  75. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/tests/test_generator.py +0 -0
  76. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/tests/test_graph.py +0 -0
  77. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/tests/test_knowledge.py +0 -0
  78. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/tests/test_providers.py +0 -0
  79. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/tests/test_recovery.py +0 -0
  80. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/tests/test_safety.py +0 -0
  81. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/tests/test_sandbox.py +0 -0
  82. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/tests/test_schema.py +0 -0
  83. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/tests/test_security.py +0 -0
  84. {dbagent_cli-0.9.4 → dbagent_cli-0.9.6}/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.4
3
+ Version: 0.9.6
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.4"
5
+ __version__ = "0.9.6"
6
6
  __author__ = "Santhosh Gupta"
@@ -166,11 +166,27 @@ 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",
171
+ "flow diagram", "flow chart", "give the flow diagram", "give the diagram",
172
+ "show flow diagram", "give diagram", "show diagram", "architecture diagram",
169
173
  ]
170
174
  for kw in analysis_keywords:
171
175
  if kw in prompt_lower:
172
176
  return "analysis"
173
177
 
178
+ # Regex for "how <X> and <Y> were/are linked/connected/related"
179
+ if re.search(r'\bhow\b.*\b(?:linked|connected|related|mapped|joined)\b', prompt_lower):
180
+ return "analysis"
181
+
182
+ # Regex for diagram / blueprint requests
183
+ if re.search(r'\b(?:flow\s+diagram|diagram|flow\s+chart|blueprint|er\s+diagram)\b', prompt_lower):
184
+ return "analysis"
185
+
186
+ # Comparative analysis questions (e.g. "is fk_site_group_id and fk_group_id same", "are table_a and table_b related")
187
+ if re.search(r'\b(?:is|are)\b.*\b(?:same|different|difference|related|equal|linked)\b', prompt_lower):
188
+ return "analysis"
189
+
174
190
  # Contextual clarification for analysis (e.g. "i mean ivis_scope", "i mean table_x")
175
191
  if re.search(r'\bi mean\b', prompt_lower) and not re.search(r'\b(insert|update|delete|drop)\b', prompt_lower):
176
192
  return "analysis"
@@ -241,7 +241,7 @@ class QueryPipeline:
241
241
  if not exact and not fuzzy:
242
242
  inherited = self.conversation.get_active_tables()
243
243
  if inherited:
244
- exact = inherited[:4]
244
+ exact = inherited[:8]
245
245
 
246
246
  sub_schema, join_steps = self._get_fast_schema_context(user_prompt, exact, fuzzy)
247
247
  result.schema = sub_schema
@@ -594,10 +594,16 @@ class QueryPipeline:
594
594
  )
595
595
 
596
596
  if result.success:
597
+ sql_tables = set()
598
+ if result.sql:
599
+ import re as _re
600
+ for m in _re.findall(r'\b(?:FROM|JOIN)\s+([a-zA-Z0-9_]+)', result.sql, _re.IGNORECASE):
601
+ sql_tables.add(m.lower())
602
+ turn_tables = list(set((exact or []) + (fuzzy or []) + list(sql_tables)))
597
603
  self.conversation.add_turn(
598
604
  user_prompt=user_prompt,
599
605
  sql=result.sql,
600
- tables=exact or fuzzy,
606
+ tables=turn_tables,
601
607
  is_chat=False,
602
608
  )
603
609
 
@@ -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,32 @@ 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
+ "flow", "diagram", "chart", "graph", "link", "links", "structure", "design", "model", "blueprint",
68
+ "mapping", "mappings", "overview", "query", "code", "script", "report", "summary", "relation",
69
+ "relations", "relationship", "relationships", "architecture"
70
+ }
71
+
72
+ raw_tokens = set(re.findall(r'\b[a-zA-Z0-9_]+\b', prompt_lower))
73
+ tokens = set(raw_tokens)
74
+
75
+ # Extract entity stems from foreign key column tokens (e.g. fk_site_group_id -> site_group, fk_group_id -> group)
76
+ for tok in raw_tokens:
77
+ if tok.startswith("fk_") or tok.startswith("pk_"):
78
+ entity = re.sub(r'^(?:fk_|pk_)', '', tok)
79
+ entity = re.sub(r'_(?:id|code|key|no|num)$', '', entity)
80
+ if entity and len(entity) >= 3 and entity not in stop_words:
81
+ tokens.add(entity)
57
82
 
58
83
  for t in all_tables:
59
84
  t_lower = t.lower()
@@ -64,7 +89,7 @@ class BaseConnector(ABC):
64
89
 
65
90
  # 2. Match without common prefixes (e.g. "ivis_scope" -> "scope", "scopes")
66
91
  unprefixed = re.sub(r'^(?:ivis_|tbl_|table_|app_)', '', t_lower)
67
- if unprefixed and len(unprefixed) >= 3:
92
+ if unprefixed and len(unprefixed) >= 3 and unprefixed not in stop_words:
68
93
  if (unprefixed in tokens or
69
94
  f"{unprefixed}s" in tokens or
70
95
  f"{unprefixed}es" in tokens or
@@ -73,8 +98,10 @@ class BaseConnector(ABC):
73
98
  exact.append(t)
74
99
  continue
75
100
 
76
- # 3. Fuzzy partial match
101
+ # 3. Fuzzy partial match (strictly exclude English stop words)
77
102
  for tok in tokens:
103
+ if tok in stop_words:
104
+ continue
78
105
  if len(tok) >= 4 and (tok in t_lower or t_lower in tok):
79
106
  if t not in exact and t not in fuzzy:
80
107
  fuzzy.append(t)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbagent-cli
3
- Version: 0.9.4
3
+ Version: 0.9.6
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.4"
7
+ version = "0.9.6"
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