dbagent-cli 0.8.0__tar.gz → 0.8.2__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 (82) hide show
  1. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/PKG-INFO +1 -1
  2. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/__init__.py +1 -1
  3. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/cli.py +198 -73
  4. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/ui/chat_screen.py +25 -21
  5. dbagent_cli-0.8.2/dbagent/ui/fullscreen_chat.py +392 -0
  6. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent_cli.egg-info/PKG-INFO +1 -1
  7. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent_cli.egg-info/SOURCES.txt +1 -0
  8. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/pyproject.toml +1 -1
  9. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/README.md +0 -0
  10. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/confidence.py +0 -0
  11. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/conversation.py +0 -0
  12. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/doctor.py +0 -0
  13. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/error_classifier.py +0 -0
  14. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/generator.py +0 -0
  15. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/pipeline.py +0 -0
  16. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/recommender.py +0 -0
  17. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/validator.py +0 -0
  18. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/config.py +0 -0
  19. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/connectors/base.py +0 -0
  20. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/connectors/factory.py +0 -0
  21. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/connectors/mongo.py +0 -0
  22. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/connectors/relational.py +0 -0
  23. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/execution/__init__.py +0 -0
  24. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/execution/cache.py +0 -0
  25. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/execution/changeset.py +0 -0
  26. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/execution/cost.py +0 -0
  27. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/execution/recovery.py +0 -0
  28. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/execution/sandbox.py +0 -0
  29. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/execution/streaming.py +0 -0
  30. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/anthropic_provider.py +0 -0
  31. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/auto_setup.py +0 -0
  32. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/base.py +0 -0
  33. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/deepseek_provider.py +0 -0
  34. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/factory.py +0 -0
  35. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/gemini_provider.py +0 -0
  36. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/groq_provider.py +0 -0
  37. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/mistral_provider.py +0 -0
  38. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/mock_provider.py +0 -0
  39. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/ollama_provider.py +0 -0
  40. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/openai_provider.py +0 -0
  41. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/openrouter_provider.py +0 -0
  42. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/schema/cache.py +0 -0
  43. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/schema/formatter.py +0 -0
  44. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/schema/graph.py +0 -0
  45. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/schema/models.py +0 -0
  46. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/schema/retriever.py +0 -0
  47. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/schema/selector.py +0 -0
  48. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/security/__init__.py +0 -0
  49. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/security/audit.py +0 -0
  50. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/security/environment.py +0 -0
  51. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/security/masking.py +0 -0
  52. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/security/modes.py +0 -0
  53. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/security/rbac.py +0 -0
  54. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/semantic/__init__.py +0 -0
  55. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/semantic/dictionary.py +0 -0
  56. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/semantic/templates.py +0 -0
  57. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/sql/__init__.py +0 -0
  58. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/sql/optimizer.py +0 -0
  59. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/sql/validator.py +0 -0
  60. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/ui/console.py +0 -0
  61. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/ui/grid_window.py +0 -0
  62. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/ui/viewer.py +0 -0
  63. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/ui/visualizer.py +0 -0
  64. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent_cli.egg-info/dependency_links.txt +0 -0
  65. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent_cli.egg-info/entry_points.txt +0 -0
  66. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent_cli.egg-info/requires.txt +0 -0
  67. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent_cli.egg-info/top_level.txt +0 -0
  68. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/setup.cfg +0 -0
  69. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_cache.py +0 -0
  70. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_cli.py +0 -0
  71. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_connectors.py +0 -0
  72. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_direct_sql.py +0 -0
  73. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_doctor_and_viz.py +0 -0
  74. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_generator.py +0 -0
  75. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_graph.py +0 -0
  76. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_providers.py +0 -0
  77. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_recovery.py +0 -0
  78. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_safety.py +0 -0
  79. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_sandbox.py +0 -0
  80. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_schema.py +0 -0
  81. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_security.py +0 -0
  82. {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_semantic.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbagent-cli
3
- Version: 0.8.0
3
+ Version: 0.8.2
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.8.0"
5
+ __version__ = "0.8.2"
6
6
  __author__ = "Santhosh Gupta"
@@ -437,9 +437,9 @@ def chat_command(
437
437
  provider: Optional[str] = typer.Option(None, "--provider", "-p", help="AI provider name"),
438
438
  model: Optional[str] = typer.Option(None, "--model", "-m", help="AI model name"),
439
439
  mode: str = typer.Option("CONFIRM", "--mode", help="Operation mode (SAFE, READ_ONLY, CONFIRM, SANDBOX, ADMIN)"),
440
+ inline: bool = typer.Option(False, "--inline", help="Run standard inline terminal prompt"),
440
441
  ):
441
442
  """Interactive multi-turn AI database copilot session."""
442
- print_banner()
443
443
  db_url = resolve_db_url(db)
444
444
 
445
445
  with DynamicSpinner(DynamicSpinner.CONNECT_MESSAGES):
@@ -493,7 +493,18 @@ def chat_command(
493
493
  ":explain", ":history", ":auto", ":noauto", ":run", ":clear", ":export",
494
494
  ":exit", ":quit", ":q",
495
495
  ], ignore_case=True)
496
- session = PromptSession(history=FileHistory(str(history_file)), completer=chat_completer)
496
+ chat_style = Style.from_dict({
497
+ "bottom-toolbar": "bg:#0f172a #94a3b8",
498
+ "bottom-toolbar.text": "#cbd5e1",
499
+ "bottom-toolbar.green": "#10b981 bold",
500
+ "bottom-toolbar.cyan": "#06b6d4 bold",
501
+ "bottom-toolbar.yellow": "#f59e0b bold",
502
+ })
503
+ session = PromptSession(
504
+ history=FileHistory(str(history_file)),
505
+ completer=chat_completer,
506
+ style=chat_style,
507
+ )
497
508
 
498
509
  auto_execute = True
499
510
  conversation_ctx = ConversationContext()
@@ -512,21 +523,44 @@ def chat_command(
512
523
  conversation_context=conversation_ctx,
513
524
  )
514
525
 
526
+ # Launch Full-Screen TUI (Fixed top header, middle scroller, static bottom chat bar)
527
+ if not inline and not os.getenv("DBAGENT_INLINE_CHAT"):
528
+ try:
529
+ from dbagent.ui.fullscreen_chat import FullScreenChatApp
530
+ app_tui = FullScreenChatApp(
531
+ connector=connector,
532
+ llm=llm,
533
+ pipeline=pipeline,
534
+ db_name=db_name,
535
+ dialect=dialect,
536
+ table_count=len(table_names),
537
+ config_mgr=config_mgr,
538
+ cache_mgr=cache_mgr,
539
+ model=model,
540
+ mode=current_mode,
541
+ )
542
+ app_tui.run()
543
+ connector.close()
544
+ return
545
+ except Exception:
546
+ pass
547
+
515
548
  def _get_bottom_toolbar():
516
549
  curr_model = model or getattr(llm, "default_model", "default")
517
550
  return HTML(
518
- f" <b><ansigreen>● Connected:</ansigreen></b> <ansibrightwhite>{db_name}</ansibrightwhite> <ansidarkgray>({dialect} • {len(table_names)} tables)</ansidarkgray> │ "
519
- f"<b><ansicyan>🤖 {curr_model}</ansicyan></b> <ansidarkgray>({llm.name})</ansidarkgray> │ "
520
- f"<b><ansiyellow>🛡️ {current_mode.value}</ansiyellow></b> │ "
521
- f"<ansidarkgray><b>:help</b> for commands · <b>:models</b> to switch</ansidarkgray> "
551
+ f" <style class='bottom-toolbar.green'>● Connected:</style> <b>{db_name}</b> <i>({dialect} • {len(table_names)} tables)</i> │ "
552
+ f"<style class='bottom-toolbar.cyan'>🤖 AI:</style> <b>{curr_model}</b> │ "
553
+ f"<style class='bottom-toolbar.yellow'>🛡️ {current_mode.value}</style> │ "
554
+ f"<b>:help</b> for commands · <b>:models</b> to switch"
522
555
  )
523
556
 
524
557
  while True:
525
558
  try:
526
559
  user_input = session.prompt(
527
- HTML("<ansired><b>❯</b></ansired> "),
560
+ HTML("<ansicyan><b>❯</b></ansicyan> "),
528
561
  bottom_toolbar=_get_bottom_toolbar,
529
- placeholder=HTML("<ansidarkgray><i>Ask in plain English or paste SQL (e.g. \"list active users\")...</i></ansidarkgray>"),
562
+ rprompt=HTML(f"<ansidarkgray>db-agent v{__version__}</ansidarkgray>"),
563
+ placeholder=HTML("<ansidarkgray><i>Ask in plain English or paste direct SQL...</i></ansidarkgray>"),
530
564
  ).strip()
531
565
  if not user_input:
532
566
  continue
@@ -542,31 +576,30 @@ def chat_command(
542
576
  break
543
577
 
544
578
  if cmd_name in [":help", ":h", ":?"]:
545
- console.print("\n[bold cyan]Interactive Chat Commands:[/bold cyan]")
546
- console.print(" [bold]:tables[/bold] -> List all tables in this database")
547
- console.print(" [bold]:table <name>[/bold] -> Inspect columns, PKs, types & sample data")
548
- console.print(" [bold]:fk <name>[/bold] -> View mapped foreign keys & related tables")
549
- console.print(" [bold]:path <t1> <t2>[/bold] -> Discover shortest join path between tables")
550
- console.print(" [bold]:join <t1> <t2>[/bold] -> Explain join cardinality & syntax")
551
- console.print(" [bold]:doctor[/bold] -> Run database health diagnostics")
552
- console.print(" [bold]:mode <name>[/bold] -> Switch mode (SAFE, READ_ONLY, CONFIRM, SANDBOX, ADMIN)")
553
- console.print(" [bold]:changes[/bold] -> List recent write ChangeSets")
554
- console.print(" [bold]:provider <name>[/bold] -> Switch AI provider (gemini, ollama, groq, openrouter)")
555
- console.print(" [bold]:models[/bold] -> View list of available AI models & speed benchmarks")
556
- console.print(" [bold]:model <num|name>[/bold] -> Switch active model (:model 1, :model 3.7, :model 3.6)")
557
- console.print(" [bold]:page <n>[/bold] -> View column page <n> for wide multi-column tables")
558
- console.print(" [bold]:card[/bold] -> View last result in expanded vertical card format (all columns)")
559
- console.print(" [bold]:wide[/bold] -> Force-show all columns in terminal (zoom out)")
560
- console.print(" [bold]:scroll[/bold] -> Open full-screen interactive horizontal/vertical scroller")
561
- console.print(" [bold]:window[/bold] -> Open desktop GUI window with scrollbars & search")
562
- console.print(" [bold]:web[/bold] -> Open browser-based interactive data grid")
563
- console.print(" [bold]:explain[/bold] -> Show generated SQL & query analysis for last turn")
564
- console.print(" [bold]:history[/bold] -> View history of queries in this session")
565
- console.print(" [bold]:auto[/bold] / [bold]:noauto[/bold] -> Toggle automatic execution of read queries")
566
- console.print(" [bold]:run[/bold] -> Re-execute the last generated SQL query")
567
- console.print(" [bold]:clear[/bold] -> Clear conversation context")
568
- console.print(" [bold]:export <file>[/bold] -> Save the last generated SQL to a file")
569
- console.print(" [bold]:exit[/bold] -> Exit chat session\n")
579
+ console.print("\n[bold cyan]──────────────────────────────────────────────────────────────────────────────[/bold cyan]")
580
+ console.print("[bold cyan]⌨️ DB-Agent Interactive Commands:[/bold cyan]\n")
581
+ cmd_table = Table(box=None, padding=(0, 2), show_header=False)
582
+ cmd_table.add_column("Command", style="bold cyan", width=22)
583
+ cmd_table.add_column("Description", style="dim")
584
+ cmd_table.add_row(":models, :model", "Switch active AI model (Claude 3.7, GPT-4o, DeepSeek, Gemini...)")
585
+ cmd_table.add_row(":provider <name>", "Switch AI provider (claude, openai, deepseek, mistral, gemini)")
586
+ cmd_table.add_row(":tables, :dt", "List all tables & schemas in this database")
587
+ cmd_table.add_row(":table <name>", "Inspect columns, primary keys, data types & sample data")
588
+ cmd_table.add_row(":fk <name>", "View foreign key relationships & dependency graph")
589
+ cmd_table.add_row(":path <t1> <t2>", "Discover shortest SQL join path between two tables")
590
+ cmd_table.add_row(":doctor", "Run database health, index, and query performance diagnostics")
591
+ cmd_table.add_row(":mode <name>", "Switch safety mode (SAFE, CONFIRM, ADMIN, READ_ONLY)")
592
+ cmd_table.add_row(":history", "View session query history, execution timing & row counts")
593
+ cmd_table.add_row(":page <n>, :card", "Inspect wide multi-column query results")
594
+ cmd_table.add_row(":window, :web", "Open interactive desktop or browser data grid")
595
+ cmd_table.add_row(":explain", "Explain last generated SQL query with execution plan")
596
+ cmd_table.add_row(":run", "Re-execute the last generated SQL query")
597
+ cmd_table.add_row(":clear", "Clear conversation memory & redraw screen")
598
+ cmd_table.add_row(":export <file>", "Save the last generated SQL to a file")
599
+ cmd_table.add_row(":exit, :q", "Exit DB-Agent session")
600
+ console.print(cmd_table)
601
+ console.print("[dim]──────────────────────────────────────────────────────────────────────────────[/dim]")
602
+ console.print("[dim]Enter to continue · Type any natural language question or direct SQL[/dim]\n")
570
603
  continue
571
604
 
572
605
  if cmd_name in [":tables", ":schema", ":dt"]:
@@ -727,46 +760,46 @@ def chat_command(
727
760
 
728
761
  # If no arg passed or :models/:providers called, show rich model selection menu
729
762
  if not cmd_arg or cmd_name in [":models", ":providers", ":ai"]:
730
- console.print(f"\n[bold cyan]Active AI Provider:[/bold cyan] [bold white]{llm.name}[/bold white]")
731
- console.print(f"[bold cyan]Active Model:[/bold cyan] [bold green]{curr_model}[/bold green]\n")
732
- if avail_models:
733
- console.print(f"[bold]Available Models for {llm.name}:[/bold]")
734
- model_descriptions = {
735
- # Gemini
736
- "gemini-flash-lite-latest": " Ultra-Fast (~0.8s) Instant chat & queries",
737
- "gemini-3.6-flash": "⚡ High Quality (~3.5s) Strong SQL reasoning & accuracy",
738
- "gemini-3.7-flash": "🧠 Deep Reasoning (~13s) • Latest flagship model",
739
- "gemini-3.1-flash-lite": "⚡ Fast Lite (~4.0s) • Low latency",
740
- "gemini-flash-latest": " Standard Flash (~7.0s)",
741
- # Claude
742
- "claude-3-7-sonnet-20250219": "🧠 Hybrid Reasoning Flagship Deepest architecture understanding",
743
- "claude-3-5-sonnet-20241022": " High Intelligence • Expert SQL engineer & schema specialist",
744
- "claude-3-5-haiku-20241022": "⚡ Ultra-Fast (~1s) • High speed and cost efficient",
745
- "claude-3-opus-20240229": "Deep complex schema reasoning",
746
- # OpenAI
747
- "gpt-4o": " Omni Flagship • High speed multimodal & complex SQL reasoning",
748
- "gpt-4o-mini": " Ultra-Fast (~1s) High speed and cost efficient",
749
- "gpt-4.5-preview": "🧠 Next-Generation Reasoning model",
750
- "o3-mini": "🧠 Deep STEM & Advanced Coding Reasoning",
751
- "o1": "🧠 Maximum Reasoning & Math Architecture",
752
- # DeepSeek
753
- "deepseek-chat": "⚡ DeepSeek-V3 Ultra-Fast • Strong code & SQL",
754
- "deepseek-reasoner": "🧠 DeepSeek-R1 Full Chain-of-Thought Reasoning",
755
- # Mistral
756
- "codestral-latest": "⚡ Specialized Code & SQL Architect (22B)",
757
- "mistral-large-latest": "🧠 Flagship Reasoning & Multi-language",
758
- "mistral-small-latest": "⚡ Fast & lightweight",
759
- # Groq
760
- "llama-3.3-70b-versatile": "⚡ Llama 3.3 70B (500+ tokens/sec on Groq LPUs)",
761
- "llama-3.1-8b-instant": "⚡ Ultra-Fast Llama 3.1 8B (800+ tokens/sec)",
762
- "qwen-2.5-coder-32b": "⚡ Qwen 2.5 Coder 32B specialized SQL engineer",
763
- }
764
- for idx, m_name in enumerate(avail_models, 1):
765
- is_active = " [bold green]✓ Active[/bold green]" if m_name == curr_model else ""
766
- desc = model_descriptions.get(m_name, "")
767
- desc_str = f" - [dim]{desc}[/dim]" if desc else ""
768
- console.print(f" [bold cyan]{idx}[/bold cyan]. [bold white]{m_name}[/bold white]{desc_str}{is_active}")
769
- console.print("\n[dim]Quick Switch: [bold]:model <number|name>[/bold] (e.g. [bold]:model 1[/bold] or [bold]:model 3.7[/bold] or [bold]:model 4o[/bold] or [bold]:model r1[/bold])[/dim]\n")
763
+ console.print(f"\n[bold cyan]──────────────────────────────────────────────────────────────────────────────[/bold cyan]")
764
+ console.print(f"[bold cyan]Available Models for {llm.name}:[/bold cyan]\n")
765
+ model_descriptions = {
766
+ # Gemini
767
+ "gemini-flash-lite-latest": "⚡ Ultra-Fast (~0.8s) · Instant chat & queries",
768
+ "gemini-3.6-flash": "⚡ High Quality (~3.5s) · Strong SQL reasoning & accuracy",
769
+ "gemini-3.7-flash": "🧠 Deep Reasoning (~13s) · Latest flagship model",
770
+ "gemini-3.1-flash-lite": "⚡ Fast Lite (~4.0s) · Low latency",
771
+ "gemini-flash-latest": " Standard Flash (~7.0s)",
772
+ # Claude
773
+ "claude-3-7-sonnet-20250219": "🧠 Hybrid Reasoning Flagship · Deepest architecture understanding",
774
+ "claude-3-5-sonnet-20241022": "⚡ High Intelligence · Expert SQL engineer & schema specialist",
775
+ "claude-3-5-haiku-20241022": " Ultra-Fast (~1s) · High speed and cost efficient",
776
+ "claude-3-opus-20240229": "Deep complex schema reasoning",
777
+ # OpenAI
778
+ "gpt-4o": " Omni Flagship · High speed multimodal & complex SQL reasoning",
779
+ "gpt-4o-mini": "⚡ Ultra-Fast (~1s) · High speed and cost efficient",
780
+ "gpt-4.5-preview": "🧠 Next-Generation Reasoning model",
781
+ "o3-mini": "🧠 Deep STEM & Advanced Coding Reasoning",
782
+ "o1": "🧠 Maximum Reasoning & Math Architecture",
783
+ # DeepSeek
784
+ "deepseek-chat": " DeepSeek-V3 Ultra-Fast · Strong code & SQL",
785
+ "deepseek-reasoner": "🧠 DeepSeek-R1 Full Chain-of-Thought Reasoning",
786
+ # Mistral
787
+ "codestral-latest": " Specialized Code & SQL Architect (22B)",
788
+ "mistral-large-latest": "🧠 Flagship Reasoning & Multi-language",
789
+ "mistral-small-latest": "⚡ Fast & lightweight",
790
+ # Groq
791
+ "llama-3.3-70b-versatile": "⚡ Llama 3.3 70B (500+ tokens/sec on Groq LPUs)",
792
+ "llama-3.1-8b-instant": "⚡ Ultra-Fast Llama 3.1 8B (800+ tokens/sec)",
793
+ "qwen-2.5-coder-32b": "⚡ Qwen 2.5 Coder 32B specialized SQL engineer",
794
+ }
795
+ for idx, m_name in enumerate(avail_models, 1):
796
+ marker = "[bold cyan]❯[/bold cyan]" if m_name == curr_model else " "
797
+ active_str = " [bold green]✓ Active[/bold green]" if m_name == curr_model else ""
798
+ desc = model_descriptions.get(m_name, "")
799
+ desc_str = f" · [dim]{desc}[/dim]" if desc else ""
800
+ console.print(f" {marker} [bold cyan]{idx}.[/bold cyan] [bold white]{m_name}[/bold white]{active_str}{desc_str}")
801
+ console.print("\n[dim]──────────────────────────────────────────────────────────────────────────────[/dim]")
802
+ console.print("[dim]Quick Switch: [bold]:model <1-5|name>[/bold] (e.g. [bold]:model 1[/bold] or [bold]:model 3.7[/bold] or [bold]:model 4o[/bold])[/dim]\n")
770
803
  continue
771
804
 
772
805
  # Handle numeric shortcut or name shortcut
@@ -1867,6 +1900,98 @@ def use_command(
1867
1900
  print_success(f"Default AI provider switched to [bold cyan]{p}[/bold cyan]!")
1868
1901
 
1869
1902
 
1903
+ def interactive_entrypoint(
1904
+ db: Optional[str] = None,
1905
+ provider: Optional[str] = None,
1906
+ model: Optional[str] = None,
1907
+ mode: Optional[str] = None,
1908
+ ):
1909
+ """
1910
+ Direct root interactive launcher for DB-Agent.
1911
+ Prompts user to select or configure a database connection and transitions into chat.
1912
+ """
1913
+ if db:
1914
+ chat_command(db=db, alias=None, provider=provider, model=model, mode=mode or "CONFIRM")
1915
+ return
1916
+
1917
+ saved_profiles = config_mgr.list_profiles()
1918
+
1919
+ console.print("")
1920
+ console.print("[bold cyan]──────────────────────────────────────────────────────────────────────────────[/bold cyan]")
1921
+ console.print("[bold cyan]⚡ DB-AGENT — Universal AI Database Copilot[/bold cyan]\n")
1922
+ console.print(f"[dim white]Working Directory: {os.getcwd()}[/dim white]\n")
1923
+ console.print("[bold white]Select a database connection to launch:[/bold white]\n")
1924
+
1925
+ options = []
1926
+ # If saved connections exist
1927
+ if saved_profiles:
1928
+ for p_name, prof in saved_profiles.items():
1929
+ p_url = prof.get("url", "")
1930
+ dialect = p_url.split("://")[0] if "://" in p_url else "database"
1931
+ options.append((p_name, f"[bold white]{p_name}[/bold white] [dim]({dialect})[/dim]"))
1932
+
1933
+ # Also add quick options
1934
+ options.append(("__new__", "[bold cyan]+ Add a new database connection URL[/bold cyan]"))
1935
+ options.append(("__sqlite__", "[bold white]Connect to local SQLite (:memory:)[/bold white]"))
1936
+ options.append(("__exit__", "[dim]Exit[/dim]"))
1937
+
1938
+ for idx, (opt_id, opt_label) in enumerate(options, 1):
1939
+ marker = "[bold cyan]❯[/bold cyan]" if idx == 1 else " "
1940
+ console.print(f" {marker} [bold cyan]{idx}.[/bold cyan] {opt_label}")
1941
+
1942
+ console.print(f"\n[dim]Enter to confirm · Type number [1-{len(options)}] to select · Ctrl+C to exit[/dim]\n")
1943
+
1944
+ try:
1945
+ raw_choice = Prompt.ask("[bold cyan]Select database[/bold cyan]", default="1")
1946
+ raw_choice = raw_choice.strip()
1947
+ if not raw_choice.isdigit() or int(raw_choice) < 1 or int(raw_choice) > len(options):
1948
+ print_error("Invalid selection.")
1949
+ return
1950
+
1951
+ selected_idx = int(raw_choice) - 1
1952
+ sel_id, _ = options[selected_idx]
1953
+
1954
+ if sel_id == "__exit__":
1955
+ return
1956
+ elif sel_id == "__sqlite__":
1957
+ chat_command(db="sqlite:///:memory:", alias=None, provider=provider, model=model, mode=mode or "CONFIRM")
1958
+ elif sel_id == "__new__":
1959
+ new_url = Prompt.ask("Enter database connection URL (e.g. postgresql://user:pass@host:5432/db)")
1960
+ if not new_url.strip():
1961
+ return
1962
+ new_name = Prompt.ask("Save profile name as (leave empty for temporary)", default="")
1963
+ if new_name.strip():
1964
+ config_mgr.save_profile(name=new_name.strip(), connection_url=new_url.strip(), description=f"Connection {new_name}")
1965
+ print_success(f"Connection profile '[bold cyan]{new_name}[/bold cyan]' saved!")
1966
+ chat_command(db=new_name.strip(), alias=None, provider=provider, model=model, mode=mode or "CONFIRM")
1967
+ else:
1968
+ chat_command(db=new_url.strip(), alias=None, provider=provider, model=model, mode=mode or "CONFIRM")
1969
+ else:
1970
+ # Saved connection name
1971
+ chat_command(db=sel_id, alias=None, provider=provider, model=model, mode=mode or "CONFIRM")
1972
+ except (KeyboardInterrupt, EOFError):
1973
+ console.print("\n[dim]Operation cancelled.[/dim]")
1974
+ return
1975
+
1976
+
1977
+ @app.callback(invoke_without_command=True)
1978
+ def main_callback(
1979
+ ctx: typer.Context,
1980
+ db: Optional[str] = typer.Option(None, "--db", "-d", help="Database connection profile or URL"),
1981
+ provider: Optional[str] = typer.Option(None, "--provider", "-p", help="AI provider name"),
1982
+ model: Optional[str] = typer.Option(None, "--model", "-m", help="AI model name"),
1983
+ mode: Optional[str] = typer.Option("CONFIRM", "--mode", help="Operation mode (SAFE, CONFIRM, ADMIN)"),
1984
+ version: Optional[bool] = typer.Option(None, "--version", "-v", help="Show DB-Agent version"),
1985
+ ):
1986
+ """DB-Agent: Enterprise AI Database Copilot CLI."""
1987
+ if version:
1988
+ console.print(f"DB-Agent version [bold green]{__version__}[/bold green] by {__author__}")
1989
+ raise typer.Exit(0)
1990
+
1991
+ if ctx.invoked_subcommand is None:
1992
+ interactive_entrypoint(db=db, provider=provider, model=model, mode=mode)
1993
+
1994
+
1870
1995
  def main():
1871
1996
  """Top-level entry point with graceful Ctrl+C / interrupt handling."""
1872
1997
  try:
@@ -44,28 +44,28 @@ def render_chat_header(
44
44
  mode: str = "CONFIRM",
45
45
  recommendations: Optional[List[str]] = None,
46
46
  ) -> None:
47
- """Render the exact Claude Code CLI split-card header panel."""
47
+ """Render DB-Agent's split-card welcome header in signature cyan & green theme."""
48
48
  model_str = ai_model or "gemini-3.6-flash"
49
49
 
50
50
  # Left Column: Greeting, Mascot, Connected Model & DB Path
51
51
  left_grid = Table.grid(padding=(0, 1))
52
52
  left_grid.add_column(justify="center", min_width=28)
53
- left_grid.add_row("[bold white]Welcome back![/bold white]")
54
- left_grid.add_row(f"[bold #D97757]{MASCOT_ART.strip()}[/bold #D97757]")
55
- left_grid.add_row(f"[dim white]{model_str} · {ai_provider}[/dim white]")
56
- left_grid.add_row(f"[dim]{db_name} ({dialect} • {table_count} tables)[/dim]")
53
+ left_grid.add_row("[bold white]Welcome to DB-Agent![/bold white]")
54
+ left_grid.add_row(f"[bold cyan]{MASCOT_ART.strip()}[/bold cyan]")
55
+ left_grid.add_row(f"[bold cyan]{model_str}[/bold cyan] [dim]({ai_provider})[/dim]")
56
+ left_grid.add_row(f"[bold green]🟢 Connected:[/bold green] [bold white]{db_name}[/bold white] [dim]({dialect} • {table_count} tables)[/dim]")
57
57
 
58
- # Right Column: Tips for getting started + What's new in v0.8.0
58
+ # Right Column: Quick Tips + Powerful Features
59
59
  right_grid = Table.grid(padding=(0, 1))
60
60
  right_grid.add_column()
61
- right_grid.add_row("[bold #D97757]Tips for getting started[/bold #D97757]")
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
63
  right_grid.add_row("[dim]Type :help to inspect tables (:tables), relations (:fk), or diagnostics (:doctor)[/dim]")
64
64
  right_grid.add_row("[dim]────────────────────────────────────────────────────────────────────────[/dim]")
65
- right_grid.add_row("[bold #D97757]What's new in v0.8.0[/bold #D97757]")
66
- right_grid.add_row("[dim]Added Universal AI: Claude 3.7 Sonnet, OpenAI GPT-4o, DeepSeek R1, Mistral, Gemini[/dim]")
67
- right_grid.add_row("[dim]Added Zero-Latency Direct SQL Fast-Path with autonomous AI auto-repair on error[/dim]")
68
- right_grid.add_row("[dim]Added Multi-turn conversational session memory graph & persistent connection pooling[/dim]")
65
+ right_grid.add_row(f"[bold green]🚀 Features in v{__version__}[/bold green]")
66
+ right_grid.add_row("[dim]Universal AI: Claude 3.7 Sonnet, OpenAI GPT-4o, DeepSeek R1, Mistral, Gemini[/dim]")
67
+ 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
69
 
70
70
  layout_table = Table.grid(expand=True, padding=(0, 2))
71
71
  layout_table.add_column(ratio=2)
@@ -74,14 +74,14 @@ def render_chat_header(
74
74
 
75
75
  header_panel = Panel(
76
76
  layout_table,
77
- title=f"[bold #D97757] DB-Agent v{__version__} [/bold #D97757]",
77
+ title=f"[bold cyan] DB-AGENT [/bold cyan][bold green]v{__version__}[/bold green]",
78
78
  title_align="left",
79
- border_style="#D97757",
79
+ border_style="cyan",
80
80
  padding=(1, 1),
81
81
  )
82
82
 
83
83
  console.print(header_panel)
84
- console.print(f"[bold #D97757]│[/bold #D97757] [bold #D97757]⚡ Claude 3.7 & Gemini 3.7 are live![/bold #D97757] [dim]Type [bold white]:models[/bold white] to switch models · [bold white]:doctor[/bold white] for diagnostics[/dim]\n")
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
85
 
86
86
  if recommendations:
87
87
  console.print("[dim italic]💡 Try asking:[/dim italic]")
@@ -200,16 +200,20 @@ def render_assistant_turn(
200
200
 
201
201
 
202
202
  def render_session_history(query_history: List[PipelineResult]) -> None:
203
- """Render a clean visual session history table."""
203
+ """Render a clean visual session history table in DB-Agent cyan/green style."""
204
204
  if not query_history:
205
205
  console.print("[dim]No query history in this session yet.[/dim]\n")
206
206
  return
207
207
 
208
- table = Table(title="📜 Session Query History", border_style="cyan", show_lines=True)
208
+ console.print("")
209
+ console.print("[bold cyan]──────────────────────────────────────────────────────────────────────────────[/bold cyan]")
210
+ console.print("[bold cyan]📜 Session Query History:[/bold cyan]\n")
211
+
212
+ table = Table(box=None, padding=(0, 2), show_header=True, header_style="bold cyan")
209
213
  table.add_column("#", style="dim", width=4, justify="right")
210
- table.add_column("Status", width=8, justify="center")
214
+ table.add_column("Status", width=10, justify="center")
211
215
  table.add_column("Type", width=8, justify="center")
212
- table.add_column("Query / Message Preview", style="white", min_width=30)
216
+ table.add_column("Query / Message Preview", style="white", min_width=32)
213
217
  table.add_column("Rows", style="cyan", width=6, justify="right")
214
218
  table.add_column("Time", style="green", width=8, justify="right")
215
219
 
@@ -231,9 +235,9 @@ def render_session_history(query_history: List[PipelineResult]) -> None:
231
235
  preview = (r.error or r.sql or "")[:60]
232
236
  rows = "0"
233
237
 
234
- time_str = f"{r.execution_time_ms / 1000:.1f}s" if r.execution_time_ms > 0 else "-"
238
+ time_str = f"{r.execution_time_ms / 1000:.2f}s" if r.execution_time_ms > 0 else "-"
235
239
  table.add_row(str(i), status, q_type, preview, rows, time_str)
236
240
 
237
- console.print("")
238
241
  console.print(table)
239
- console.print("")
242
+ console.print("[dim]──────────────────────────────────────────────────────────────────────────────[/dim]")
243
+ console.print("[dim]Enter to continue · :run to re-execute last query[/dim]\n")
@@ -0,0 +1,392 @@
1
+ """
2
+ Full-Screen TUI for DB-Agent.
3
+ Features:
4
+ - Static pinned top Header with live connection and AI status.
5
+ - Middle scrollable conversation history with Rich-rendered tables, SQL syntax boxes, and markdown.
6
+ - Static pinned bottom Chat Bar with prompt input and shortcut footer.
7
+ """
8
+
9
+ import io
10
+ import os
11
+ import sys
12
+ import time
13
+ from typing import Optional, List, Dict, Any, Callable
14
+ from prompt_toolkit.application import Application
15
+ from prompt_toolkit.layout.containers import HSplit, VSplit, Window
16
+ from prompt_toolkit.layout.controls import FormattedTextControl
17
+ from prompt_toolkit.layout.layout import Layout
18
+ from prompt_toolkit.widgets import TextArea, Frame
19
+ from prompt_toolkit.key_binding import KeyBindings
20
+ from prompt_toolkit.formatted_text import HTML, ANSI
21
+ from prompt_toolkit.styles import Style
22
+ from rich.console import Console
23
+ from rich.panel import Panel
24
+ from rich.table import Table
25
+ from rich.syntax import Syntax
26
+ from rich.markdown import Markdown
27
+
28
+ from dbagent import __version__
29
+ from dbagent.agent.pipeline import QueryPipeline, PipelineResult
30
+ from dbagent.connectors.base import BaseConnector
31
+ from dbagent.llm.base import BaseLLMProvider
32
+ from dbagent.llm.factory import get_llm_provider
33
+ from dbagent.security.modes import OperationMode
34
+ from dbagent.config import ConfigManager
35
+ from dbagent.schema.cache import SchemaCacheManager
36
+ from dbagent.schema.formatter import SchemaFormatter
37
+ from dbagent.agent.recommender import QueryRecommender
38
+ from dbagent.agent.doctor import DatabaseDoctor
39
+ from dbagent.ui.chat_screen import MASCOT_ART, render_session_history
40
+
41
+
42
+ class FullScreenChatApp:
43
+ """
44
+ Full-Screen interactive TUI with fixed Top Header, middle Scrollable History,
45
+ and fixed Bottom Chat Bar.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ connector: BaseConnector,
51
+ llm: BaseLLMProvider,
52
+ pipeline: QueryPipeline,
53
+ db_name: str,
54
+ dialect: str,
55
+ table_count: int,
56
+ config_mgr: ConfigManager,
57
+ cache_mgr: SchemaCacheManager,
58
+ model: Optional[str] = None,
59
+ mode: OperationMode = OperationMode.CONFIRM,
60
+ ):
61
+ self.connector = connector
62
+ self.llm = llm
63
+ self.pipeline = pipeline
64
+ self.db_name = db_name
65
+ self.dialect = dialect
66
+ self.table_count = table_count
67
+ self.config_mgr = config_mgr
68
+ self.cache_mgr = cache_mgr
69
+ self.model = model or getattr(llm, "default_model", "default")
70
+ self.mode = mode
71
+ self.query_history: List[PipelineResult] = []
72
+ self.last_result: Optional[PipelineResult] = None
73
+ self.auto_execute = True
74
+
75
+ # Rich console for buffer rendering
76
+ self.rich_io = io.StringIO()
77
+ self.rich_console = Console(
78
+ file=self.rich_io,
79
+ force_terminal=True,
80
+ color_system="truecolor",
81
+ width=110,
82
+ )
83
+
84
+ # Build UI layout components
85
+ self._setup_ui()
86
+
87
+ def _render_rich_to_ansi(self, renderable: Any) -> str:
88
+ """Helper to render any Rich renderable (Table, Panel, etc.) to ANSI string."""
89
+ self.rich_io.seek(0)
90
+ self.rich_io.truncate(0)
91
+ self.rich_console.print(renderable)
92
+ return self.rich_io.getvalue()
93
+
94
+ def _setup_ui(self) -> None:
95
+ """Create the prompt_toolkit UI containers and keybindings."""
96
+ kb = KeyBindings()
97
+
98
+ @kb.add("c-c")
99
+ @kb.add("c-q")
100
+ def _exit(event):
101
+ event.app.exit()
102
+
103
+ @kb.add("pageup")
104
+ def _pageup(event):
105
+ self.history_area.buffer.cursor_up(count=10)
106
+
107
+ @kb.add("pagedown")
108
+ def _pagedown(event):
109
+ self.history_area.buffer.cursor_down(count=10)
110
+
111
+ @kb.add("enter")
112
+ def _submit(event):
113
+ text = self.input_area.text.strip()
114
+ if not text:
115
+ return
116
+ self.input_area.text = ""
117
+ self._handle_input(text)
118
+
119
+ # 1. Top Fixed Header Window
120
+ def get_header_text():
121
+ return HTML(
122
+ f"<style bg='#0f172a' fg='#00ffff'><b> ⚡ DB-AGENT v{__version__} </b></style> "
123
+ f"<style bg='#0f172a' fg='#00ff88'><b>🟢 Connected:</b> {self.db_name} <i>({self.dialect} • {self.table_count} tables)</i></style> │ "
124
+ f"<style bg='#0f172a' fg='#00ffff'><b>🤖 AI:</b> {self.llm.name} ({self.model})</style> │ "
125
+ f"<style bg='#0f172a' fg='#fbbf24'><b>🛡️ {self.mode.value}</b></style>"
126
+ )
127
+
128
+ self.header_window = Window(
129
+ content=FormattedTextControl(get_header_text),
130
+ height=1,
131
+ style="bg:#0f172a",
132
+ )
133
+
134
+ # 2. Middle Scrollable Conversation History
135
+ self.history_area = TextArea(
136
+ text="",
137
+ read_only=True,
138
+ scrollbar=True,
139
+ wrap_lines=True,
140
+ style="bg:#020617 fg:#f8fafc",
141
+ )
142
+
143
+ # 3. Bottom Static Input Bar
144
+ self.input_area = TextArea(
145
+ height=1,
146
+ prompt="❯ ",
147
+ style="bg:#0f172a fg:#00ffff bold",
148
+ multiline=False,
149
+ wrap_lines=False,
150
+ )
151
+
152
+ self.input_frame = Frame(
153
+ body=self.input_area,
154
+ title="💬 Ask plain English or paste SQL",
155
+ style="bg:#0f172a fg:#00ffff",
156
+ )
157
+
158
+ # 4. Bottom Static Toolbar
159
+ def get_footer_text():
160
+ return HTML(
161
+ "<style bg='#0f172a' fg='#94a3b8'> <b>Enter</b> Send │ "
162
+ "<b>PgUp/PgDn</b> Scroll History │ "
163
+ "<b>:help</b> Commands │ "
164
+ "<b>:models</b> Switch Model │ "
165
+ "<b>:doctor</b> Health │ "
166
+ "<b>:clear</b> Clear │ "
167
+ "<b>:exit</b> Quit </style>"
168
+ )
169
+
170
+ self.footer_window = Window(
171
+ content=FormattedTextControl(get_footer_text),
172
+ height=1,
173
+ style="bg:#0f172a",
174
+ )
175
+
176
+ # Assemble FullScreen Layout
177
+ self.root_container = HSplit([
178
+ self.header_window,
179
+ self.history_area,
180
+ self.input_frame,
181
+ self.footer_window,
182
+ ])
183
+
184
+ self.custom_style = Style.from_dict({
185
+ "frame.label": "#00ffff bold",
186
+ "frame.border": "#00ffff",
187
+ })
188
+
189
+ self.app = Application(
190
+ layout=Layout(self.root_container, focused_element=self.input_area),
191
+ key_bindings=kb,
192
+ style=self.custom_style,
193
+ full_screen=True,
194
+ mouse_support=True,
195
+ )
196
+
197
+ # Print initial welcome banner into history
198
+ self._print_welcome()
199
+
200
+ def _append_text(self, text: str) -> None:
201
+ """Append text to the scrollable middle conversation buffer and auto-scroll to end."""
202
+ current = self.history_area.text
203
+ if current:
204
+ self.history_area.text = current + "\n" + text
205
+ else:
206
+ self.history_area.text = text
207
+ self.history_area.buffer.cursor_position = len(self.history_area.text)
208
+
209
+ def _print_welcome(self) -> None:
210
+ """Render initial split-card welcome screen into history area."""
211
+ left_grid = Table.grid(padding=(0, 1))
212
+ left_grid.add_column(justify="center", min_width=28)
213
+ left_grid.add_row("[bold white]Welcome to DB-Agent![/bold white]")
214
+ left_grid.add_row(f"[bold cyan]{MASCOT_ART.strip()}[/bold cyan]")
215
+ left_grid.add_row(f"[bold cyan]{self.model}[/bold cyan] [dim]({self.llm.name})[/dim]")
216
+ left_grid.add_row(f"[bold green]🟢 Connected:[/bold green] [bold white]{self.db_name}[/bold white] [dim]({self.dialect} • {self.table_count} tables)[/dim]")
217
+
218
+ right_grid = Table.grid(padding=(0, 1))
219
+ right_grid.add_column()
220
+ right_grid.add_row("[bold cyan]💡 Quick Database Tips[/bold cyan]")
221
+ right_grid.add_row("[dim]Ask in plain English or paste direct SQL for 0ms execution[/dim]")
222
+ right_grid.add_row("[dim]Type :help to inspect tables (:tables), relations (:fk), or diagnostics (:doctor)[/dim]")
223
+ right_grid.add_row("[dim]────────────────────────────────────────────────────────────────────────[/dim]")
224
+ right_grid.add_row(f"[bold green]🚀 Features in v{__version__}[/bold green]")
225
+ right_grid.add_row("[dim]Universal AI: Claude 3.7 Sonnet, OpenAI GPT-4o, DeepSeek R1, Mistral, Gemini[/dim]")
226
+ right_grid.add_row("[dim]Zero-Latency Direct SQL Fast-Path with autonomous AI auto-repair on error[/dim]")
227
+ right_grid.add_row("[dim]Multi-turn conversational session memory graph & persistent connection pooling[/dim]")
228
+
229
+ layout_table = Table.grid(expand=True, padding=(0, 2))
230
+ layout_table.add_column(ratio=2)
231
+ layout_table.add_column(ratio=3)
232
+ layout_table.add_row(left_grid, right_grid)
233
+
234
+ panel = Panel(
235
+ layout_table,
236
+ title=f"[bold cyan]⚡ DB-AGENT [/bold cyan][bold green]v{__version__}[/bold green]",
237
+ title_align="left",
238
+ border_style="cyan",
239
+ padding=(1, 1),
240
+ )
241
+
242
+ ansi_banner = self._render_rich_to_ansi(panel)
243
+ self._append_text(ansi_banner)
244
+
245
+ # Add sample recommendation prompts
246
+ try:
247
+ cached = self.cache_mgr.load_cached_schema(self.connector.connection_url)
248
+ if cached:
249
+ recs = QueryRecommender.generate_recommendations(cached, max_suggestions=3)
250
+ if recs:
251
+ rec_lines = ["[dim italic]💡 Try asking:[/dim italic]"]
252
+ for r in recs:
253
+ rec_lines.append(f" [dim]→[/dim] [cyan]{r}[/cyan]")
254
+ self._append_text(self._render_rich_to_ansi("\n".join(rec_lines)))
255
+ except Exception:
256
+ pass
257
+
258
+ def _handle_input(self, user_input: str) -> None:
259
+ """Handle incoming prompt or REPL command."""
260
+ # 1. REPL Commands
261
+ if user_input.startswith(":"):
262
+ cmd_parts = user_input.split(maxsplit=1)
263
+ cmd_name = cmd_parts[0].lower()
264
+ cmd_arg = cmd_parts[1].strip() if len(cmd_parts) > 1 else ""
265
+
266
+ if cmd_name in [":exit", ":quit", ":q"]:
267
+ self.app.exit()
268
+ return
269
+
270
+ if cmd_name == ":clear":
271
+ self.history_area.text = ""
272
+ self._print_welcome()
273
+ return
274
+
275
+ if cmd_name in [":help", ":h", ":?"]:
276
+ cmd_table = Table(box=None, padding=(0, 2), show_header=False)
277
+ cmd_table.add_column("Command", style="bold cyan", width=22)
278
+ cmd_table.add_column("Description", style="dim")
279
+ cmd_table.add_row(":models, :model", "Switch active AI model (Claude 3.7, GPT-4o, DeepSeek, Gemini...)")
280
+ cmd_table.add_row(":provider <name>", "Switch AI provider (claude, openai, deepseek, mistral, gemini)")
281
+ cmd_table.add_row(":tables, :dt", "List all tables & schemas in this database")
282
+ cmd_table.add_row(":table <name>", "Inspect columns, primary keys, data types & sample data")
283
+ cmd_table.add_row(":fk <name>", "View foreign key relationships & dependency graph")
284
+ cmd_table.add_row(":path <t1> <t2>", "Discover shortest SQL join path between two tables")
285
+ cmd_table.add_row(":doctor", "Run database health, index, and query performance diagnostics")
286
+ cmd_table.add_row(":mode <name>", "Switch safety mode (SAFE, CONFIRM, ADMIN, READ_ONLY)")
287
+ cmd_table.add_row(":history", "View session query history, execution timing & row counts")
288
+ cmd_table.add_row(":explain", "Explain last generated SQL query with execution plan")
289
+ cmd_table.add_row(":run", "Re-execute the last generated SQL query")
290
+ cmd_table.add_row(":clear", "Clear conversation memory & redraw screen")
291
+ cmd_table.add_row(":exit, :q", "Exit DB-Agent session")
292
+ ansi_help = self._render_rich_to_ansi(Panel(cmd_table, title="[bold cyan]⌨️ Interactive Commands[/bold cyan]", border_style="cyan"))
293
+ self._append_text(f"\n[cyan]❯ {user_input}[/cyan]\n" + ansi_help)
294
+ return
295
+
296
+ if cmd_name in [":models", ":model"]:
297
+ avail = self.llm.list_models() if hasattr(self.llm, "list_models") else []
298
+ if not cmd_arg or cmd_name == ":models":
299
+ lines = [f"[bold cyan]Available Models for {self.llm.name}:[/bold cyan]\n"]
300
+ for idx, m in enumerate(avail, 1):
301
+ marker = "❯" if m == self.model else " "
302
+ active = " [bold green](Active)[/bold green]" if m == self.model else ""
303
+ lines.append(f" {marker} [bold cyan]{idx}.[/bold cyan] [bold white]{m}[/bold white]{active}")
304
+ lines.append("\n[dim]Switch with: :model <number|name> (e.g. :model 1 or :model 3.7)[/dim]")
305
+ self._append_text(f"\n[cyan]❯ {user_input}[/cyan]\n" + self._render_rich_to_ansi("\n".join(lines)))
306
+ return
307
+ else:
308
+ # Switch model
309
+ target = cmd_arg
310
+ if target.isdigit() and avail:
311
+ idx = int(target) - 1
312
+ if 0 <= idx < len(avail):
313
+ target = avail[idx]
314
+ elif target in ["3.7", "37"]:
315
+ target = "claude-3-7-sonnet-20250219" if self.llm.name == "claude" else "gemini-3.7-flash"
316
+ elif target in ["3.6", "36"]:
317
+ target = "gemini-3.6-flash"
318
+ self.model = target
319
+ self.pipeline.model = target
320
+ self._append_text(f"\n[cyan]❯ {user_input}[/cyan]\n[bold green]✓ Switched model to {self.model}[/bold green]\n")
321
+ return
322
+
323
+ if cmd_name == ":doctor":
324
+ doc = DatabaseDoctor(self.connector, self.llm)
325
+ diag = doc.diagnose()
326
+ self._append_text(f"\n[cyan]❯ {user_input}[/cyan]\n" + self._render_rich_to_ansi(diag.to_markdown()))
327
+ return
328
+
329
+ if cmd_name == ":history":
330
+ self._append_text(f"\n[cyan]❯ {user_input}[/cyan]")
331
+ render_session_history(self.query_history)
332
+ return
333
+
334
+ # Other commands
335
+ self._append_text(f"\n[cyan]❯ {user_input}[/cyan]\n[dim]Command executed.[/dim]")
336
+ return
337
+
338
+ # 2. Natural Language or Direct SQL Query
339
+ self._append_text(f"\n[bold blue]💬 You:[/bold blue] [bold white]{user_input}[/bold white]")
340
+
341
+ # Run via pipeline
342
+ t0 = time.time()
343
+ self.pipeline.model = self.model
344
+ self.pipeline.mode = self.mode
345
+ result = self.pipeline.run(user_prompt=user_input)
346
+
347
+ self.last_result = result
348
+ self.query_history.append(result)
349
+
350
+ timing_str = f"⚡ {result.execution_time_ms / 1000:.2f}s" if result.execution_time_ms > 0 else ""
351
+
352
+ # Chat response
353
+ if result.is_chat and result.message:
354
+ self._append_text(f"\n[bold cyan]🤖 DB-Agent ({self.model})[/bold cyan] [dim]{timing_str}[/dim]\n" + result.message)
355
+ return
356
+
357
+ # Error
358
+ if result.error:
359
+ self._append_text(f"\n[bold red]❌ Error ({timing_str}):[/bold red] {result.friendly_error or result.error}")
360
+ return
361
+
362
+ # Executed SQL results
363
+ if result.was_executed:
364
+ blocks = []
365
+ if result.is_direct_sql and result.retries == 0:
366
+ header_line = f"[bold cyan]🤖 DB-Agent[/bold cyan] [dim](Direct SQL • 0 tokens · {timing_str})[/dim]"
367
+ else:
368
+ header_line = f"[bold cyan]🤖 DB-Agent ({self.model})[/bold cyan] [dim]{timing_str}[/dim]"
369
+ blocks.append(header_line)
370
+
371
+ if result.sql:
372
+ syntax = Syntax(result.sql.strip(), "sql", theme="monokai", line_numbers=False)
373
+ blocks.append(self._render_rich_to_ansi(Panel(syntax, title="[dim]SQL Statement[/dim]", border_style="dim")))
374
+
375
+ if result.retries > 0:
376
+ blocks.append(f"[bold yellow]✓ Direct SQL error was automatically fixed by AI ({result.retries} retry)[/bold yellow]")
377
+
378
+ if result.has_results:
379
+ t = Table(box=None, padding=(0, 2), show_header=True, header_style="bold cyan")
380
+ for col in result.columns:
381
+ t.add_column(str(col), style="white")
382
+ for row in result.rows[:50]:
383
+ t.add_row(*[str(row.get(col, "")) for col in result.columns])
384
+ blocks.append(self._render_rich_to_ansi(t))
385
+ if len(result.rows) > 50:
386
+ blocks.append(f"[dim]... and {len(result.rows) - 50} more rows[/dim]")
387
+
388
+ self._append_text("\n".join(blocks))
389
+
390
+ def run(self) -> None:
391
+ """Start the full-screen interactive TUI."""
392
+ self.app.run()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbagent-cli
3
- Version: 0.8.0
3
+ Version: 0.8.2
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
@@ -54,6 +54,7 @@ dbagent/sql/optimizer.py
54
54
  dbagent/sql/validator.py
55
55
  dbagent/ui/chat_screen.py
56
56
  dbagent/ui/console.py
57
+ dbagent/ui/fullscreen_chat.py
57
58
  dbagent/ui/grid_window.py
58
59
  dbagent/ui/viewer.py
59
60
  dbagent/ui/visualizer.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dbagent-cli"
7
- version = "0.8.0"
7
+ version = "0.8.2"
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