dbagent-cli 0.11.3__tar.gz → 0.11.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 (101) hide show
  1. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/PKG-INFO +1 -1
  2. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/__init__.py +1 -1
  3. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/agent/generator.py +2 -2
  4. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/agent/pipeline.py +1 -1
  5. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/cli.py +192 -15
  6. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/connectors/base.py +2 -2
  7. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/ui/chat_screen.py +8 -3
  8. dbagent_cli-0.11.5/dbagent/ui/clipboard.py +280 -0
  9. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent_cli.egg-info/PKG-INFO +1 -1
  10. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent_cli.egg-info/SOURCES.txt +2 -0
  11. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/pyproject.toml +1 -1
  12. dbagent_cli-0.11.5/tests/test_clipboard.py +120 -0
  13. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_direct_sql.py +1 -1
  14. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_generator.py +1 -1
  15. dbagent_cli-0.11.5/tests/test_ground_truth_sql_validation.py +150 -0
  16. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_knowledge.py +8 -8
  17. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_prompt_queue.py +2 -2
  18. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_prompt_queue_and_sql.py +7 -7
  19. dbagent_cli-0.11.3/tests/test_ground_truth_sql_validation.py +0 -150
  20. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/README.md +0 -0
  21. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/agent/confidence.py +0 -0
  22. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/agent/conversation.py +0 -0
  23. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/agent/doctor.py +0 -0
  24. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/agent/error_classifier.py +0 -0
  25. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/agent/recommender.py +0 -0
  26. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/agent/validator.py +0 -0
  27. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/benchmark.py +0 -0
  28. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/config.py +0 -0
  29. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/connectors/factory.py +0 -0
  30. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/connectors/mongo.py +0 -0
  31. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/connectors/relational.py +0 -0
  32. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/errors.py +0 -0
  33. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/execution/__init__.py +0 -0
  34. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/execution/cache.py +0 -0
  35. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/execution/changeset.py +0 -0
  36. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/execution/cost.py +0 -0
  37. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/execution/prompt_queue.py +0 -0
  38. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/execution/recovery.py +0 -0
  39. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/execution/sandbox.py +0 -0
  40. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/execution/streaming.py +0 -0
  41. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/llm/anthropic_provider.py +0 -0
  42. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/llm/auto_setup.py +0 -0
  43. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/llm/base.py +0 -0
  44. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/llm/deepseek_provider.py +0 -0
  45. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/llm/factory.py +0 -0
  46. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/llm/gemini_provider.py +0 -0
  47. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/llm/groq_provider.py +0 -0
  48. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/llm/mistral_provider.py +0 -0
  49. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/llm/mock_provider.py +0 -0
  50. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/llm/ollama_provider.py +0 -0
  51. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/llm/openai_provider.py +0 -0
  52. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/llm/openrouter_provider.py +0 -0
  53. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/plugins.py +0 -0
  54. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/sample_db.py +0 -0
  55. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/schema/cache.py +0 -0
  56. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/schema/formatter.py +0 -0
  57. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/schema/graph.py +0 -0
  58. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/schema/ground_truth.py +0 -0
  59. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/schema/models.py +0 -0
  60. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/schema/retriever.py +0 -0
  61. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/schema/selector.py +0 -0
  62. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/security/__init__.py +0 -0
  63. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/security/audit.py +0 -0
  64. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/security/environment.py +0 -0
  65. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/security/masking.py +0 -0
  66. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/security/modes.py +0 -0
  67. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/security/rbac.py +0 -0
  68. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/semantic/__init__.py +0 -0
  69. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/semantic/dictionary.py +0 -0
  70. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/semantic/knowledge.py +0 -0
  71. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/semantic/templates.py +0 -0
  72. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/sql/__init__.py +0 -0
  73. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/sql/optimizer.py +0 -0
  74. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/sql/validator.py +0 -0
  75. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/ui/console.py +0 -0
  76. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/ui/grid_window.py +0 -0
  77. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/ui/live_streamer.py +0 -0
  78. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/ui/viewer.py +0 -0
  79. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent/ui/visualizer.py +0 -0
  80. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent_cli.egg-info/dependency_links.txt +0 -0
  81. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent_cli.egg-info/entry_points.txt +0 -0
  82. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent_cli.egg-info/requires.txt +0 -0
  83. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/dbagent_cli.egg-info/top_level.txt +0 -0
  84. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/setup.cfg +0 -0
  85. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_benchmarks.py +0 -0
  86. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_cache.py +0 -0
  87. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_cli.py +0 -0
  88. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_connectors.py +0 -0
  89. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_demo_and_sample_db.py +0 -0
  90. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_doctor_and_viz.py +0 -0
  91. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_formats_and_exit_codes.py +0 -0
  92. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_graph.py +0 -0
  93. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_multi_turn_conversation.py +0 -0
  94. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_plugins.py +0 -0
  95. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_providers.py +0 -0
  96. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_recovery.py +0 -0
  97. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_safety.py +0 -0
  98. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_sandbox.py +0 -0
  99. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_schema.py +0 -0
  100. {dbagent_cli-0.11.3 → dbagent_cli-0.11.5}/tests/test_security.py +0 -0
  101. {dbagent_cli-0.11.3 → dbagent_cli-0.11.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.11.3
3
+ Version: 0.11.5
4
4
  Summary: The Privacy-First AI Database Copilot, Schema Graph Introspector & Post-Commit Recovery Engine (CLI)
5
5
  Author: Santhosh Gupta
6
6
  License: MIT
@@ -2,5 +2,5 @@
2
2
  DB-Agent: Universal Database Introspection and Script Generation AI Agent (CLI).
3
3
  """
4
4
 
5
- __version__ = "0.11.3"
5
+ __version__ = "0.11.5"
6
6
  __author__ = "Santhosh Gupta"
@@ -60,7 +60,7 @@ Generate ONLY the raw executable SQL query for the given database. No markdown f
60
60
  - If the column is VARCHAR(1) or CHAR(1) or text, check if values use 'Y'/'N', 'YES'/'NO', '1'/'0', 'ACTIVE'/'INACTIVE', 'TRUE'/'FALSE'.
61
61
  - If sample values show 'Y'/'N' (or if it's a single-character VARCHAR/CHAR column), use `active = 'Y'` or `active IN ('Y', '1', 'true', 'TRUE')` for active/enabled status.
62
62
  10. For Deletions & Cascading Mapping Cleanup (when deleting a record/user without foreign key constraint violations):
63
- - For PostgreSQL: Use a single Common Table Expression (CTE) with data-modifying statements deleting from all child, mapping, activity, profile, and preference tables first, and finally deleting from the parent table (e.g. `WITH target AS (SELECT pk_user_id FROM ivis_user WHERE ...), del_act AS (DELETE FROM user_activity WHERE user_id IN (SELECT pk_user_id FROM target)), del_map AS (DELETE FROM ivis_user_access WHERE fk_user_id IN (SELECT pk_user_id FROM target)) DELETE FROM ivis_user WHERE pk_user_id IN (SELECT pk_user_id FROM target);`).
63
+ - For PostgreSQL: Use a single Common Table Expression (CTE) with data-modifying statements deleting from all child, mapping, activity, profile, and preference tables first, and finally deleting from the parent table (e.g. `WITH target AS (SELECT id FROM users WHERE status = 'inactive'), del_orders AS (DELETE FROM orders WHERE user_id IN (SELECT id FROM target)), del_logs AS (DELETE FROM user_logs WHERE user_id IN (SELECT id FROM target)) DELETE FROM users WHERE id IN (SELECT id FROM target);`).
64
64
  - For MySQL / SQLite: Generate clean, ordered DELETE statements deleting referencing child records before the parent record.
65
65
  - Always delete from all referencing tables present in the schema to avoid foreign key violations.
66
66
  11. Multi-Turn Session Memory & Query Refinement:
@@ -201,7 +201,7 @@ class ScriptGenerator:
201
201
  if re.search(r'\b(?:is|are)\b.*\b(?:same|different|difference|related|equal|linked)\b', prompt_lower):
202
202
  return "analysis"
203
203
 
204
- # Contextual clarification for analysis (e.g. "i mean ivis_scope", "i mean table_x")
204
+ # Contextual clarification for analysis (e.g. "i mean user_profiles", "i mean table_x")
205
205
  if re.search(r'\bi mean\b', prompt_lower) and not re.search(r'\b(insert|update|delete|drop)\b', prompt_lower):
206
206
  return "analysis"
207
207
 
@@ -176,7 +176,7 @@ class QueryPipeline:
176
176
  all_table_names = self.connector.get_table_names()
177
177
  stems = set()
178
178
  for t in target_names:
179
- unprefixed = re.sub(r'^(?:ivis_|tbl_|table_|app_)', '', t.lower())
179
+ unprefixed = re.sub(r'^(?:tbl_|table_|app_|dim_|fact_|rel_|raw_)', '', t.lower())
180
180
  unprefixed = re.sub(r'(?:s|es)$', '', unprefixed)
181
181
  if len(unprefixed) >= 3:
182
182
  stems.add(unprefixed)
@@ -27,6 +27,7 @@ from prompt_toolkit import PromptSession, HTML
27
27
  from prompt_toolkit.history import FileHistory
28
28
  from prompt_toolkit.completion import WordCompleter
29
29
  from prompt_toolkit.styles import Style
30
+ from prompt_toolkit.key_binding import KeyBindings
30
31
 
31
32
  from dbagent import __version__, __author__
32
33
  from dbagent.config import ConfigManager
@@ -73,6 +74,7 @@ from dbagent.ui.console import (
73
74
  )
74
75
  from dbagent.ui.viewer import SchemaViewer
75
76
  from dbagent.ui.grid_window import open_desktop_grid_window, open_browser_grid_window
77
+ from dbagent.ui.clipboard import ClipboardManager
76
78
  from dbagent.ui.chat_screen import (
77
79
  clear_screen,
78
80
  render_chat_header,
@@ -625,6 +627,45 @@ def ask_command(
625
627
  raise typer.Exit(1)
626
628
 
627
629
 
630
+ def open_multiline_paste_session() -> Optional[str]:
631
+ """
632
+ Opens an interactive multiline editing / paste buffer for long SQL scripts or complex prompts.
633
+ Submit: Type ';;' on an empty line, or press Esc+Enter / Alt+Enter.
634
+ Cancel: Press Ctrl+C.
635
+ """
636
+ console.print("\n[bold cyan]📋 Multiline Paste & Edit Mode[/bold cyan]")
637
+ console.print("[dim]Paste your multiline SQL or instructions below.[/dim]")
638
+ console.print("[dim]Submit: Type [bold white];;[/bold white] on a new line or press [bold white]Esc+Enter[/bold white] · Cancel: [bold white]Ctrl+C[/bold white]\n[/dim]")
639
+
640
+ kb = KeyBindings()
641
+
642
+ @kb.add("escape", "enter")
643
+ def _submit(event):
644
+ event.current_buffer.validate_and_handle()
645
+
646
+ try:
647
+ multi_style = Style.from_dict({
648
+ "prompt": "#06b6d4 bold",
649
+ })
650
+ multi_session = PromptSession(
651
+ key_bindings=kb,
652
+ multiline=True,
653
+ enable_bracketed_paste=True,
654
+ style=multi_style,
655
+ )
656
+ text = multi_session.prompt(
657
+ HTML("<ansicyan><b>... </b></ansicyan>"),
658
+ prompt_continuation=lambda width, line_number, is_soft_wrap: "... ",
659
+ )
660
+ clean = text.strip()
661
+ if clean.endswith(";;"):
662
+ clean = clean[:-2].strip()
663
+ return clean if clean else None
664
+ except (KeyboardInterrupt, EOFError):
665
+ console.print("[dim italic]Multiline input cancelled.[/dim italic]\n")
666
+ return None
667
+
668
+
628
669
  # ---- chat (interactive REPL) ----
629
670
 
630
671
  @app.command(name="chat")
@@ -686,6 +727,7 @@ def chat_command(
686
727
  chat_completer = WordCompleter([
687
728
  ":help", ":tables", ":schema", ":dt", ":table", ":fk", ":path", ":join",
688
729
  ":doctor", ":mode", ":changes", ":provider", ":model", ":models",
730
+ ":copy", ":cp", ":c", ":paste", ":p", ":multiline", ":paste-mode", ":m",
689
731
  ":page", ":card", ":wide", ":scroll", ":window", ":grid", ":web",
690
732
  ":explain", ":history", ":queue", ":auto", ":noauto", ":run", ":clear", ":export",
691
733
  ":exit", ":quit", ":q",
@@ -697,16 +739,29 @@ def chat_command(
697
739
  "bottom-toolbar.cyan": "#06b6d4 bold",
698
740
  "bottom-toolbar.yellow": "#f59e0b bold",
699
741
  })
742
+
743
+ kb = KeyBindings()
744
+
745
+ @kb.add("escape", "v")
746
+ def _kb_paste_clipboard(event):
747
+ """Alt+V / Esc, V shortcut: paste clipboard text into current buffer."""
748
+ text, _ = ClipboardManager.get_text()
749
+ if text:
750
+ event.current_buffer.insert_text(text)
751
+
700
752
  session = PromptSession(
701
753
  history=FileHistory(str(history_file)),
702
754
  completer=chat_completer,
703
755
  style=chat_style,
756
+ key_bindings=kb,
757
+ enable_bracketed_paste=True,
704
758
  )
705
759
 
706
760
  auto_execute = True
707
761
  conversation_ctx = ConversationContext()
708
762
  query_history: List[PipelineResult] = []
709
763
  last_result: Optional[PipelineResult] = None
764
+ last_user_prompt: str = ""
710
765
 
711
766
  from dbagent.execution.prompt_queue import PromptQueueManager
712
767
  prompt_queue = PromptQueueManager(console=console)
@@ -785,6 +840,9 @@ def chat_command(
785
840
  cmd_table.add_row(":learn <rule>", "Teach DB-Agent custom domain rules & mappings (:learn topic = rule)")
786
841
  cmd_table.add_row(":memory, :learned", "View all learned domain rules & relationship memory")
787
842
  cmd_table.add_row(":forget <id|topic>", "Remove a learned rule from persistent memory")
843
+ cmd_table.add_row(":copy [format]", "Copy SQL, CSV, TSV (Excel), JSON, Markdown or INSERTs to clipboard")
844
+ cmd_table.add_row(":paste, :p", "Paste and execute SQL or prompt directly from system clipboard")
845
+ cmd_table.add_row(":multiline, :m", "Open multiline paste mode for long SQL blocks (Esc+Enter / ;;)")
788
846
  cmd_table.add_row(":mode <name>", "Switch safety mode (SAFE, CONFIRM, ADMIN, READ_ONLY)")
789
847
  cmd_table.add_row(":history", "View session query history, execution timing & row counts")
790
848
  cmd_table.add_row(":queue, :q-list", "View queued in-line prompts (:queue clear to reset)")
@@ -808,7 +866,7 @@ def chat_command(
808
866
 
809
867
  if cmd_name == ":table":
810
868
  if not cmd_arg:
811
- print_warning("Usage: :table <table_name> (e.g. :table ivis_users)")
869
+ print_warning("Usage: :table <table_name> (e.g. :table users)")
812
870
  continue
813
871
  exact, fuzzy = connector.resolve_tables(cmd_arg)
814
872
  target = exact[0] if exact else (fuzzy[0] if fuzzy else cmd_arg)
@@ -823,7 +881,7 @@ def chat_command(
823
881
 
824
882
  if cmd_name in [":fk", ":relations", ":mapped"]:
825
883
  if not cmd_arg:
826
- print_warning("Usage: :fk <table_name> (e.g. :fk ivis_users)")
884
+ print_warning("Usage: :fk <table_name> (e.g. :fk users)")
827
885
  continue
828
886
  exact, fuzzy = connector.resolve_tables(cmd_arg)
829
887
  target = exact[0] if exact else (fuzzy[0] if fuzzy else cmd_arg)
@@ -931,7 +989,7 @@ def chat_command(
931
989
 
932
990
  if cmd_name in [":learn", ":remember"]:
933
991
  if not cmd_arg or "=" not in cmd_arg:
934
- print_warning("Usage: :learn <topic> = <description> (e.g. :learn site_user_map = ivis_site maps to ivis_user via site_group -> group)")
992
+ print_warning("Usage: :learn <topic> = <description> (e.g. :learn user_roles = users map to roles via user_role_map)")
935
993
  continue
936
994
  parts = cmd_arg.split("=", 1)
937
995
  topic = parts[0].strip()
@@ -1124,7 +1182,123 @@ def chat_command(
1124
1182
  )
1125
1183
  continue
1126
1184
 
1127
- if cmd_name in [":page", ":cols", ":p"]:
1185
+ if cmd_name in [":copy", ":cp", ":c"]:
1186
+ target = cmd_arg.strip().lower() if cmd_arg else "sql"
1187
+ if target in ["sql", "query", "code"]:
1188
+ if not last_result or not last_result.sql:
1189
+ print_warning("No generated SQL query to copy.")
1190
+ continue
1191
+ ok, msg = ClipboardManager.copy_text(last_result.sql.strip())
1192
+ if ok:
1193
+ print_success(f"Copied SQL query to clipboard! ({len(last_result.sql.strip())} chars)")
1194
+ else:
1195
+ print_error(f"Clipboard copy failed: {msg}")
1196
+ elif target in ["csv", "comma"]:
1197
+ if not last_result or not last_result.rows:
1198
+ print_warning("No query result rows to copy.")
1199
+ continue
1200
+ formatted = ClipboardManager.format_results_as(last_result.columns, last_result.rows, format_type="csv")
1201
+ ok, msg = ClipboardManager.copy_text(formatted)
1202
+ if ok:
1203
+ print_success(f"Copied {len(last_result.rows)} row(s) as CSV to clipboard!")
1204
+ else:
1205
+ print_error(f"Clipboard copy failed: {msg}")
1206
+ elif target in ["tsv", "excel", "sheets", "tab"]:
1207
+ if not last_result or not last_result.rows:
1208
+ print_warning("No query result rows to copy.")
1209
+ continue
1210
+ formatted = ClipboardManager.format_results_as(last_result.columns, last_result.rows, format_type="tsv")
1211
+ ok, msg = ClipboardManager.copy_text(formatted)
1212
+ if ok:
1213
+ print_success(f"Copied {len(last_result.rows)} row(s) as TSV to clipboard (ready for Excel/Sheets)!")
1214
+ else:
1215
+ print_error(f"Clipboard copy failed: {msg}")
1216
+ elif target in ["json", "js"]:
1217
+ if not last_result or not last_result.rows:
1218
+ print_warning("No query result rows to copy.")
1219
+ continue
1220
+ formatted = ClipboardManager.format_results_as(last_result.columns, last_result.rows, format_type="json")
1221
+ ok, msg = ClipboardManager.copy_text(formatted)
1222
+ if ok:
1223
+ print_success(f"Copied {len(last_result.rows)} row(s) as JSON to clipboard!")
1224
+ else:
1225
+ print_error(f"Clipboard copy failed: {msg}")
1226
+ elif target in ["md", "markdown", "table"]:
1227
+ if not last_result or not last_result.rows:
1228
+ print_warning("No query result rows to copy.")
1229
+ continue
1230
+ formatted = ClipboardManager.format_results_as(last_result.columns, last_result.rows, format_type="md")
1231
+ ok, msg = ClipboardManager.copy_text(formatted)
1232
+ if ok:
1233
+ print_success(f"Copied {len(last_result.rows)} row(s) as Markdown table to clipboard!")
1234
+ else:
1235
+ print_error(f"Clipboard copy failed: {msg}")
1236
+ elif target in ["insert", "inserts", "sql-insert"]:
1237
+ if not last_result or not last_result.rows:
1238
+ print_warning("No query result rows to copy.")
1239
+ continue
1240
+ tbl = last_result.exact_tables[0] if (last_result.exact_tables) else "query_results"
1241
+ formatted = ClipboardManager.format_results_as(last_result.columns, last_result.rows, format_type="sql", table_name=tbl)
1242
+ ok, msg = ClipboardManager.copy_text(formatted)
1243
+ if ok:
1244
+ print_success(f"Copied {len(last_result.rows)} SQL INSERT statement(s) to clipboard!")
1245
+ else:
1246
+ print_error(f"Clipboard copy failed: {msg}")
1247
+ elif target in ["prompt", "question", "ask"]:
1248
+ if not last_user_prompt:
1249
+ print_warning("No previous prompt to copy.")
1250
+ continue
1251
+ ok, msg = ClipboardManager.copy_text(last_user_prompt)
1252
+ if ok:
1253
+ print_success("Copied last prompt to clipboard!")
1254
+ else:
1255
+ print_error(f"Clipboard copy failed: {msg}")
1256
+ elif target in ["history", "all-sql"]:
1257
+ sqls = [r.sql.strip() for r in query_history if r.sql]
1258
+ if not sqls:
1259
+ print_warning("No SQL queries in session history to copy.")
1260
+ continue
1261
+ joined = ";\n\n".join(sqls) + ";"
1262
+ ok, msg = ClipboardManager.copy_text(joined)
1263
+ if ok:
1264
+ print_success(f"Copied {len(sqls)} SQL statement(s) from session history to clipboard!")
1265
+ else:
1266
+ print_error(f"Clipboard copy failed: {msg}")
1267
+ else:
1268
+ print_warning(f"Unknown copy format '{target}'. Available: :copy sql, :copy csv, :copy tsv (Excel), :copy json, :copy md, :copy insert, :copy prompt, :copy history")
1269
+ continue
1270
+
1271
+ if cmd_name in [":paste", ":p"]:
1272
+ pasted_text, p_msg = ClipboardManager.get_text()
1273
+ if not pasted_text or not pasted_text.strip():
1274
+ print_warning("Clipboard is empty or could not be read.")
1275
+ continue
1276
+ pasted_clean = pasted_text.strip()
1277
+ console.print(f"\n[bold cyan]📋 Pasted from clipboard ({len(pasted_clean):,} chars):[/bold cyan]")
1278
+ if "\n" in pasted_clean or len(pasted_clean) > 80:
1279
+ if any(pasted_clean.lower().startswith(kw) for kw in ["select", "insert", "update", "delete", "create", "alter", "drop", "with", "explain"]):
1280
+ from rich.syntax import Syntax
1281
+ console.print(Panel(Syntax(pasted_clean, "sql", theme="monokai", line_numbers=False), border_style="cyan", padding=(0, 1)))
1282
+ else:
1283
+ console.print(Panel(pasted_clean, border_style="cyan", padding=(0, 1)))
1284
+ else:
1285
+ console.print(f" [bold white]{pasted_clean}[/bold white]\n")
1286
+
1287
+ user_input = pasted_clean
1288
+ if user_input.startswith(":"):
1289
+ prompt_queue.split_and_enqueue_chained(user_input)
1290
+ continue
1291
+
1292
+ elif cmd_name in [":multiline", ":paste-mode", ":m"]:
1293
+ multiline_input = open_multiline_paste_session()
1294
+ if not multiline_input:
1295
+ continue
1296
+ user_input = multiline_input
1297
+ if user_input.startswith(":"):
1298
+ prompt_queue.split_and_enqueue_chained(user_input)
1299
+ continue
1300
+
1301
+ elif cmd_name in [":page", ":cols"]:
1128
1302
  if not last_result or not last_result.rows:
1129
1303
  print_warning("No previous query results to page.")
1130
1304
  continue
@@ -1135,21 +1309,21 @@ def chat_command(
1135
1309
  print_results_table(last_result.columns, last_result.rows, title="Results", col_page=p_num)
1136
1310
  continue
1137
1311
 
1138
- if cmd_name in [":card", ":vertical", ":x", ":v"]:
1312
+ elif cmd_name in [":card", ":vertical", ":x", ":v"]:
1139
1313
  if not last_result or not last_result.rows:
1140
1314
  print_warning("No previous query results to display.")
1141
1315
  continue
1142
1316
  print_results_vertical(last_result.columns, last_result.rows)
1143
1317
  continue
1144
1318
 
1145
- if cmd_name in [":wide", ":all", ":raw"]:
1319
+ elif cmd_name in [":wide", ":all", ":raw"]:
1146
1320
  if not last_result or not last_result.rows:
1147
1321
  print_warning("No previous query results to display.")
1148
1322
  continue
1149
1323
  print_results_table(last_result.columns, last_result.rows, title="All Columns (Wide View)", show_all=True)
1150
1324
  continue
1151
1325
 
1152
- if cmd_name in [":window", ":gui", ":grid", ":w"]:
1326
+ elif cmd_name in [":window", ":gui", ":grid", ":w"]:
1153
1327
  if not last_result or not last_result.rows:
1154
1328
  print_warning("No previous query results to display.")
1155
1329
  continue
@@ -1157,7 +1331,7 @@ def chat_command(
1157
1331
  open_desktop_grid_window(last_result.columns, last_result.rows, title=f"DB-Agent Data Grid")
1158
1332
  continue
1159
1333
 
1160
- if cmd_name in [":web", ":browser", ":html"]:
1334
+ elif cmd_name in [":web", ":browser", ":html"]:
1161
1335
  if not last_result or not last_result.rows:
1162
1336
  print_warning("No previous query results to display.")
1163
1337
  continue
@@ -1165,24 +1339,24 @@ def chat_command(
1165
1339
  open_browser_grid_window(last_result.columns, last_result.rows, title=f"DB-Agent Data View")
1166
1340
  continue
1167
1341
 
1168
- if cmd_name in [":scroll", ":scroller", ":pager"]:
1342
+ elif cmd_name in [":scroll", ":scroller", ":pager"]:
1169
1343
  if not last_result or not last_result.rows:
1170
1344
  print_warning("No previous query results to scroll.")
1171
1345
  continue
1172
1346
  open_interactive_scroller(last_result.columns, last_result.rows)
1173
1347
  continue
1174
1348
 
1175
- if cmd_name == ":auto":
1349
+ elif cmd_name == ":auto":
1176
1350
  auto_execute = True
1177
1351
  print_success("Auto-execute enabled. Read queries will run automatically.")
1178
1352
  continue
1179
1353
 
1180
- if cmd_name == ":noauto":
1354
+ elif cmd_name == ":noauto":
1181
1355
  auto_execute = False
1182
1356
  print_info("Auto-execute disabled. All queries will show SQL only. Use :run to execute.")
1183
1357
  continue
1184
1358
 
1185
- if cmd_name == ":run":
1359
+ elif cmd_name == ":run":
1186
1360
  if not last_result or not last_result.sql:
1187
1361
  print_warning("No generated SQL query to run.")
1188
1362
  continue
@@ -1195,7 +1369,7 @@ def chat_command(
1195
1369
  print_results_table(cols, rows)
1196
1370
  continue
1197
1371
 
1198
- if cmd_name == ":export":
1372
+ elif cmd_name == ":export":
1199
1373
  if not cmd_arg:
1200
1374
  print_warning("Usage: :export <file_path> (e.g. :export query.sql)")
1201
1375
  continue
@@ -1208,13 +1382,16 @@ def chat_command(
1208
1382
  print_success(f"SQL saved to {p}")
1209
1383
  continue
1210
1384
 
1211
- print_warning(f"Unknown command '{user_input}'. Type [bold]:help[/bold] to see all in-chat commands.")
1212
- continue
1385
+ else:
1386
+ print_warning(f"Unknown command '{user_input}'. Type [bold]:help[/bold] to see all in-chat commands.")
1387
+ continue
1213
1388
 
1214
1389
  if user_input.lower() in ["exit", "quit", "q"]:
1215
1390
  console.print("[bold cyan]Goodbye![/bold cyan]")
1216
1391
  break
1217
1392
 
1393
+ last_user_prompt = user_input
1394
+
1218
1395
  # --- Natural language query via pipeline with live streaming & background queue listener ---
1219
1396
  pipeline.model = model
1220
1397
  pipeline.mode = current_mode
@@ -87,8 +87,8 @@ class BaseConnector(ABC):
87
87
  exact.append(t)
88
88
  continue
89
89
 
90
- # 2. Match without common prefixes (e.g. "ivis_scope" -> "scope", "scopes")
91
- unprefixed = re.sub(r'^(?:ivis_|tbl_|table_|app_)', '', t_lower)
90
+ # 2. Match without common database table prefixes (e.g. "tbl_orders" -> "orders")
91
+ unprefixed = re.sub(r'^(?:tbl_|table_|app_|dim_|fact_|rel_|raw_)', '', t_lower)
92
92
  if unprefixed and len(unprefixed) >= 3 and unprefixed not in stop_words:
93
93
  if (unprefixed in tokens or
94
94
  f"{unprefixed}s" in tokens or
@@ -60,12 +60,13 @@ def render_chat_header(
60
60
  right_grid.add_column()
61
61
  right_grid.add_row("[bold cyan]💡 Quick Database Tips[/bold cyan]")
62
62
  right_grid.add_row("[dim]Ask in plain English or paste direct SQL for 0ms execution[/dim]")
63
+ right_grid.add_row("[dim]Copy results: [bold white]:copy[/bold white] (SQL), [bold white]:copy csv[/bold white], [bold white]:copy tsv[/bold white] (Excel) · Paste: [bold white]:paste[/bold white][/dim]")
63
64
  right_grid.add_row("[dim]Type :help to inspect tables (:tables), relations (:fk), or diagnostics (:doctor)[/dim]")
64
65
  right_grid.add_row("[dim]────────────────────────────────────────────────────────────────────────[/dim]")
65
66
  right_grid.add_row(f"[bold green]🚀 Features in v{__version__}[/bold green]")
66
67
  right_grid.add_row("[dim]Universal AI: Claude 3.7 Sonnet, OpenAI GPT-4o, DeepSeek R1, Mistral, Gemini[/dim]")
67
68
  right_grid.add_row("[dim]Zero-Latency Direct SQL Fast-Path with autonomous AI auto-repair on error[/dim]")
68
- right_grid.add_row("[dim]Multi-turn conversational session memory graph & persistent connection pooling[/dim]")
69
+ right_grid.add_row("[dim]Cross-platform instant clipboard copy (SQL, CSV, TSV Excel, JSON) & bracketed paste[/dim]")
69
70
 
70
71
  layout_table = Table.grid(expand=True, padding=(0, 2))
71
72
  layout_table.add_column(ratio=2)
@@ -81,7 +82,7 @@ def render_chat_header(
81
82
  )
82
83
 
83
84
  console.print(header_panel)
84
- console.print(f"[bold cyan]│[/bold cyan] [bold cyan]⚡ Multi-AI Engine Live![/bold cyan] [dim]Type [bold white]:models[/bold white] to switch AI models · [bold white]:doctor[/bold white] for diagnostics · Mode: [bold yellow]{mode}[/bold yellow][/dim]\n")
85
+ console.print(f"[bold cyan]│[/bold cyan] [bold cyan]⚡ Multi-AI Engine Live![/bold cyan] [dim]Type [bold white]:models[/bold white] to switch AI models · [bold white]:copy[/bold white] to copy · Mode: [bold yellow]{mode}[/bold yellow][/dim]\n")
85
86
 
86
87
  if recommendations:
87
88
  console.print("[dim italic]💡 Try asking:[/dim italic]")
@@ -177,8 +178,12 @@ def render_assistant_turn(
177
178
  )
178
179
  console.print(bar_chart)
179
180
  console.print("")
181
+
182
+ console.print("[dim]💡 Quick Copy: [bold white]:copy[/bold white] (SQL) · [bold white]:copy csv[/bold white] / [bold white]:copy tsv[/bold white] (Excel) / [bold white]:copy json[/bold white] · [bold white]:paste[/bold white] to run from clipboard[/dim]")
180
183
  else:
181
184
  print_info("Query executed successfully. No rows returned.")
185
+ if result.sql:
186
+ console.print("[dim]💡 Quick Copy: [bold white]:copy[/bold white] (SQL) · [bold white]:paste[/bold white] to run from clipboard[/dim]")
182
187
 
183
188
  if result.confidence:
184
189
  console.print(result.confidence.format_summary())
@@ -204,7 +209,7 @@ def render_assistant_turn(
204
209
  elif result.sql:
205
210
  syntax = Syntax(result.sql.strip(), "sql", theme="monokai", line_numbers=False)
206
211
  console.print(Panel(syntax, title="Generated SQL", border_style="dim"))
207
- console.print("")
212
+ console.print("[dim]💡 Quick Copy: [bold white]:copy[/bold white] (SQL) · [bold white]:paste[/bold white] to run from clipboard[/dim]\n")
208
213
 
209
214
 
210
215
  def render_session_history(query_history: List[PipelineResult]) -> None: