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.
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/PKG-INFO +1 -1
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/__init__.py +1 -1
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/cli.py +198 -73
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/ui/chat_screen.py +25 -21
- dbagent_cli-0.8.2/dbagent/ui/fullscreen_chat.py +392 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent_cli.egg-info/PKG-INFO +1 -1
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent_cli.egg-info/SOURCES.txt +1 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/pyproject.toml +1 -1
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/README.md +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/confidence.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/conversation.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/doctor.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/error_classifier.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/generator.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/pipeline.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/recommender.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/agent/validator.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/config.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/connectors/base.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/connectors/factory.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/connectors/mongo.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/connectors/relational.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/execution/__init__.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/execution/cache.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/execution/changeset.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/execution/cost.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/execution/recovery.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/execution/sandbox.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/execution/streaming.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/anthropic_provider.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/auto_setup.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/base.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/deepseek_provider.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/factory.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/gemini_provider.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/groq_provider.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/mistral_provider.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/mock_provider.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/ollama_provider.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/openai_provider.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/llm/openrouter_provider.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/schema/cache.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/schema/formatter.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/schema/graph.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/schema/models.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/schema/retriever.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/schema/selector.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/security/__init__.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/security/audit.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/security/environment.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/security/masking.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/security/modes.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/security/rbac.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/semantic/__init__.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/semantic/dictionary.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/semantic/templates.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/sql/__init__.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/sql/optimizer.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/sql/validator.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/ui/console.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/ui/grid_window.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/ui/viewer.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent/ui/visualizer.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent_cli.egg-info/dependency_links.txt +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent_cli.egg-info/entry_points.txt +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent_cli.egg-info/requires.txt +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/dbagent_cli.egg-info/top_level.txt +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/setup.cfg +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_cache.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_cli.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_connectors.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_direct_sql.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_doctor_and_viz.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_generator.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_graph.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_providers.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_recovery.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_safety.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_sandbox.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_schema.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_security.py +0 -0
- {dbagent_cli-0.8.0 → dbagent_cli-0.8.2}/tests/test_semantic.py +0 -0
|
@@ -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
|
-
|
|
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" <
|
|
519
|
-
f"<
|
|
520
|
-
f"<
|
|
521
|
-
f"<
|
|
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("<
|
|
560
|
+
HTML("<ansicyan><b>❯</b></ansicyan> "),
|
|
528
561
|
bottom_toolbar=_get_bottom_toolbar,
|
|
529
|
-
|
|
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]
|
|
546
|
-
console.print("
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
console.print(
|
|
567
|
-
console.print("
|
|
568
|
-
console.print("
|
|
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]
|
|
731
|
-
console.print(f"[bold cyan]
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
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
|
|
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
|
|
54
|
-
left_grid.add_row(f"[bold
|
|
55
|
-
left_grid.add_row(f"[
|
|
56
|
-
left_grid.add_row(f"[
|
|
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
|
|
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
|
|
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
|
|
66
|
-
right_grid.add_row("[dim]
|
|
67
|
-
right_grid.add_row("[dim]
|
|
68
|
-
right_grid.add_row("[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
|
|
77
|
+
title=f"[bold cyan]⚡ DB-AGENT [/bold cyan][bold green]v{__version__}[/bold green]",
|
|
78
78
|
title_align="left",
|
|
79
|
-
border_style="
|
|
79
|
+
border_style="cyan",
|
|
80
80
|
padding=(1, 1),
|
|
81
81
|
)
|
|
82
82
|
|
|
83
83
|
console.print(header_panel)
|
|
84
|
-
console.print(f"[bold
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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:.
|
|
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()
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dbagent-cli"
|
|
7
|
-
version = "0.8.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|