sqlsaber 0.15.0__py3-none-any.whl → 0.16.1__py3-none-any.whl

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.

Potentially problematic release.


This version of sqlsaber might be problematic. Click here for more details.

@@ -1,9 +1,7 @@
1
1
  """Agents module for SQLSaber."""
2
2
 
3
- from .anthropic import AnthropicSQLAgent
4
- from .base import BaseSQLAgent
3
+ from .pydantic_ai_agent import build_sqlsaber_agent
5
4
 
6
5
  __all__ = [
7
- "BaseSQLAgent",
8
- "AnthropicSQLAgent",
6
+ "build_sqlsaber_agent",
9
7
  ]
sqlsaber/agents/base.py CHANGED
@@ -14,7 +14,6 @@ from sqlsaber.database.connection import (
14
14
  SQLiteConnection,
15
15
  )
16
16
  from sqlsaber.database.schema import SchemaManager
17
- from sqlsaber.models.events import StreamEvent
18
17
  from sqlsaber.tools import SQLTool, tool_registry
19
18
 
20
19
 
@@ -40,7 +39,7 @@ class BaseSQLAgent(ABC):
40
39
  user_query: str,
41
40
  use_history: bool = True,
42
41
  cancellation_token: asyncio.Event | None = None,
43
- ) -> AsyncIterator[StreamEvent]:
42
+ ) -> AsyncIterator:
44
43
  """Process a user query and stream responses.
45
44
 
46
45
  Args:
sqlsaber/agents/mcp.py CHANGED
@@ -1,9 +1,9 @@
1
1
  """Generic SQL agent implementation for MCP tools."""
2
2
 
3
3
  from typing import AsyncIterator
4
+
4
5
  from sqlsaber.agents.base import BaseSQLAgent
5
6
  from sqlsaber.database.connection import BaseDatabaseConnection
6
- from sqlsaber.models.events import StreamEvent
7
7
 
8
8
 
9
9
  class MCPSQLAgent(BaseSQLAgent):
@@ -14,7 +14,7 @@ class MCPSQLAgent(BaseSQLAgent):
14
14
 
15
15
  async def query_stream(
16
16
  self, user_query: str, use_history: bool = True
17
- ) -> AsyncIterator[StreamEvent]:
17
+ ) -> AsyncIterator:
18
18
  """Not implemented for generic agent as it's only used for tool operations."""
19
19
  raise NotImplementedError(
20
20
  "MCPSQLAgent does not support query streaming. Use specific agent implementations for conversation."
@@ -0,0 +1,170 @@
1
+ """Pydantic-AI Agent for SQLSaber.
2
+
3
+ This replaces the custom AnthropicSQLAgent and uses pydantic-ai's Agent,
4
+ function tools, and streaming event types directly.
5
+ """
6
+
7
+ import httpx
8
+ from pydantic_ai import Agent, RunContext
9
+ from pydantic_ai.models.anthropic import AnthropicModel
10
+ from pydantic_ai.models.google import GoogleModel
11
+ from pydantic_ai.providers.anthropic import AnthropicProvider
12
+ from pydantic_ai.providers.google import GoogleProvider
13
+
14
+ from sqlsaber.config import providers
15
+ from sqlsaber.config.settings import Config
16
+ from sqlsaber.database.connection import (
17
+ BaseDatabaseConnection,
18
+ CSVConnection,
19
+ MySQLConnection,
20
+ PostgreSQLConnection,
21
+ SQLiteConnection,
22
+ )
23
+ from sqlsaber.memory.manager import MemoryManager
24
+ from sqlsaber.tools.instructions import InstructionBuilder
25
+ from sqlsaber.tools.registry import tool_registry
26
+ from sqlsaber.tools.sql_tools import SQLTool
27
+
28
+
29
+ def build_sqlsaber_agent(
30
+ db_connection: BaseDatabaseConnection,
31
+ database_name: str | None,
32
+ ) -> Agent:
33
+ """Create and configure a pydantic-ai Agent for SQLSaber.
34
+
35
+ - Registers function tools that delegate to the existing tool registry
36
+ - Attaches dynamic system prompt built from InstructionBuilder + MemoryManager
37
+ - Ensures SQL tools have the active DB connection
38
+ """
39
+ # Ensure SQL tools receive the active connection
40
+ for tool_name in tool_registry.list_tools(category="sql"):
41
+ tool = tool_registry.get_tool(tool_name)
42
+ if isinstance(tool, SQLTool):
43
+ tool.set_connection(db_connection)
44
+
45
+ cfg = Config()
46
+ # Ensure provider env var is hydrated from keyring for current provider (Config.validate handles it)
47
+ cfg.validate()
48
+
49
+ # Build model/agent. For some providers (e.g., google), construct provider model explicitly to
50
+ # allow arbitrary model IDs even if not in pydantic-ai's KnownModelName.
51
+ model_name_only = (
52
+ cfg.model_name.split(":", 1)[1] if ":" in cfg.model_name else cfg.model_name
53
+ )
54
+
55
+ provider = providers.provider_from_model(cfg.model_name) or ""
56
+ if provider == "google":
57
+ model_obj = GoogleModel(
58
+ model_name_only, provider=GoogleProvider(api_key=cfg.api_key)
59
+ )
60
+ agent = Agent(model_obj, name="sqlsaber")
61
+ elif provider == "anthropic" and bool(getattr(cfg, "oauth_token", None)):
62
+ # Build custom httpx client to inject OAuth headers for Anthropic
63
+ async def add_oauth_headers(request: httpx.Request) -> None: # type: ignore[override]
64
+ # Remove API-key header if present and add OAuth headers
65
+ if "x-api-key" in request.headers:
66
+ del request.headers["x-api-key"]
67
+ request.headers.update(
68
+ {
69
+ "Authorization": f"Bearer {cfg.oauth_token}",
70
+ "anthropic-version": "2023-06-01",
71
+ "anthropic-beta": "oauth-2025-04-20",
72
+ "User-Agent": "ClaudeCode/1.0 (Anthropic Claude Code CLI)",
73
+ "X-Client-Name": "claude-code",
74
+ "X-Client-Version": "1.0.0",
75
+ }
76
+ )
77
+
78
+ http_client = httpx.AsyncClient(event_hooks={"request": [add_oauth_headers]})
79
+ provider_obj = AnthropicProvider(api_key="placeholder", http_client=http_client)
80
+ model_obj = AnthropicModel(model_name_only, provider=provider_obj)
81
+ agent = Agent(model_obj, name="sqlsaber")
82
+ else:
83
+ agent = Agent(cfg.model_name, name="sqlsaber")
84
+
85
+ # Memory + dynamic system prompt
86
+ memory_manager = MemoryManager()
87
+ instruction_builder = InstructionBuilder(tool_registry)
88
+
89
+ is_oauth = provider == "anthropic" and bool(getattr(cfg, "oauth_token", None))
90
+
91
+ if not is_oauth:
92
+
93
+ @agent.system_prompt(dynamic=True)
94
+ async def sqlsaber_system_prompt(ctx: RunContext) -> str:
95
+ db_type = _get_database_type_name(db_connection)
96
+ instructions = instruction_builder.build_instructions(db_type=db_type)
97
+
98
+ # Add memory context if available
99
+ if database_name:
100
+ mem = memory_manager.format_memories_for_prompt(database_name)
101
+ else:
102
+ mem = ""
103
+
104
+ parts = [p for p in (instructions, mem) if p and p.strip()]
105
+ return "\n\n".join(parts) if parts else ""
106
+ else:
107
+
108
+ @agent.system_prompt(dynamic=True)
109
+ async def sqlsaber_system_prompt(ctx: RunContext) -> str:
110
+ # Minimal system prompt in OAuth mode to match Claude Code identity
111
+ return "You are Claude Code, Anthropic's official CLI for Claude."
112
+
113
+ # Expose helpers and context on agent instance
114
+ agent._sqlsaber_memory_manager = memory_manager # type: ignore[attr-defined]
115
+ agent._sqlsaber_database_name = database_name # type: ignore[attr-defined]
116
+ agent._sqlsaber_instruction_builder = instruction_builder # type: ignore[attr-defined]
117
+ agent._sqlsaber_db_type = _get_database_type_name(db_connection) # type: ignore[attr-defined]
118
+ agent._sqlsaber_is_oauth = is_oauth # type: ignore[attr-defined]
119
+
120
+ # Tool wrappers that invoke the registered tools
121
+ @agent.tool(name="list_tables")
122
+ async def list_tables(ctx: RunContext) -> str:
123
+ """
124
+ Get a list of all tables in the database with row counts.
125
+ Use this first to discover available tables.
126
+ """
127
+ tool = tool_registry.get_tool("list_tables")
128
+ return await tool.execute()
129
+
130
+ @agent.tool(name="introspect_schema")
131
+ async def introspect_schema(
132
+ ctx: RunContext, table_pattern: str | None = None
133
+ ) -> str:
134
+ """
135
+ Introspect database schema to understand table structures.
136
+
137
+ Args:
138
+ table_pattern: Optional pattern to filter tables (e.g., 'public.users', 'user%', '%order%')
139
+ """
140
+ tool = tool_registry.get_tool("introspect_schema")
141
+ return await tool.execute(table_pattern=table_pattern)
142
+
143
+ @agent.tool(name="execute_sql")
144
+ async def execute_sql(ctx: RunContext, query: str, limit: int | None = 100) -> str:
145
+ """
146
+ Execute a SQL query and return the results.
147
+
148
+ Args:
149
+ query: SQL query to execute
150
+ limit: Maximum number of rows to return (default: 100)
151
+ """
152
+ tool = tool_registry.get_tool("execute_sql")
153
+ return await tool.execute(query=query, limit=limit)
154
+
155
+ return agent
156
+
157
+
158
+ def _get_database_type_name(db: BaseDatabaseConnection) -> str:
159
+ """Get the human-readable database type name (mirrors BaseSQLAgent)."""
160
+
161
+ if isinstance(db, PostgreSQLConnection):
162
+ return "PostgreSQL"
163
+ elif isinstance(db, MySQLConnection):
164
+ return "MySQL"
165
+ elif isinstance(db, SQLiteConnection):
166
+ return "SQLite"
167
+ elif isinstance(db, CSVConnection):
168
+ return "SQLite"
169
+ else:
170
+ return "database"
sqlsaber/cli/auth.py CHANGED
@@ -1,11 +1,17 @@
1
1
  """Authentication CLI commands."""
2
2
 
3
- import questionary
3
+ import os
4
+
4
5
  import cyclopts
6
+ import keyring
7
+ import questionary
5
8
  from rich.console import Console
6
9
 
10
+ from sqlsaber.config import providers
11
+ from sqlsaber.config.api_keys import APIKeyManager
7
12
  from sqlsaber.config.auth import AuthConfigManager, AuthMethod
8
13
  from sqlsaber.config.oauth_flow import AnthropicOAuthFlow
14
+ from sqlsaber.config.oauth_tokens import OAuthTokenManager
9
15
 
10
16
  # Global instances for CLI commands
11
17
  console = Console()
@@ -20,59 +26,59 @@ auth_app = cyclopts.App(
20
26
 
21
27
  @auth_app.command
22
28
  def setup():
23
- """Configure authentication method for SQLSaber."""
24
- console.print("\n[bold]SQLSaber Authentication Setup[/bold]\n")
25
-
26
- # Use questionary for selection
27
- auth_choice = questionary.select(
28
- "Choose your authentication method:",
29
- choices=[
30
- questionary.Choice(
31
- title="Anthropic API Key",
32
- value=AuthMethod.API_KEY,
33
- description="You can create one by visiting https://console.anthropic.com",
34
- ),
35
- questionary.Choice(
36
- title="Claude Pro or Max Subscription",
37
- value=AuthMethod.CLAUDE_PRO,
38
- description="This does not require creating an API Key, but requires a subscription at https://claude.ai",
39
- ),
40
- ],
29
+ """Configure authentication for SQLsaber (API keys and Anthropic OAuth)."""
30
+ console.print("\n[bold]SQLsaber Authentication Setup[/bold]\n")
31
+
32
+ provider = questionary.select(
33
+ "Select provider to configure:",
34
+ choices=providers.all_keys(),
41
35
  ).ask()
42
36
 
43
- if auth_choice is None:
37
+ if provider is None:
44
38
  console.print("[yellow]Setup cancelled.[/yellow]")
45
39
  return
46
40
 
47
- # Handle auth method setup
48
- if auth_choice == AuthMethod.API_KEY:
49
- console.print("\nTo configure your API key, you can either:")
50
- console.print(" Set the ANTHROPIC_API_KEY environment variable")
51
- console.print(
52
- " Let SQLsaber prompt you for the key when needed (stored securely)"
53
- )
54
-
55
- config_manager.set_auth_method(auth_choice)
56
- console.print("\n[bold green]Authentication method saved![/bold green]")
57
-
58
- elif auth_choice == AuthMethod.CLAUDE_PRO:
59
- oauth_flow = AnthropicOAuthFlow()
60
- try:
61
- success = oauth_flow.authenticate()
62
- if success:
63
- config_manager.set_auth_method(auth_choice)
41
+ if provider == "anthropic":
42
+ # Let user choose API key or OAuth
43
+ method_choice = questionary.select(
44
+ "Select Anthropic authentication method:",
45
+ choices=[
46
+ {"name": "API key", "value": AuthMethod.API_KEY},
47
+ {"name": "Claude Pro/Max (OAuth)", "value": AuthMethod.CLAUDE_PRO},
48
+ ],
49
+ ).ask()
50
+
51
+ if method_choice == AuthMethod.CLAUDE_PRO:
52
+ flow = AnthropicOAuthFlow()
53
+ if flow.authenticate():
54
+ config_manager.set_auth_method(AuthMethod.CLAUDE_PRO)
64
55
  console.print(
65
- "\n[bold green]Authentication setup complete![/bold green]"
56
+ "\n[bold green] Anthropic OAuth configured successfully![/bold green]"
66
57
  )
67
58
  else:
68
- console.print(
69
- "\n[yellow]OAuth authentication failed. Please try again.[/yellow]"
70
- )
71
- return
72
- except Exception as e:
73
- console.print(f"\n[red]Authentication setup failed: {str(e)}[/red]")
59
+ console.print("\n[red]✗ Anthropic OAuth setup failed.[/red]")
60
+ console.print(
61
+ "You can change this anytime by running [cyan]saber auth setup[/cyan] again."
62
+ )
74
63
  return
75
64
 
65
+ # API key flow (all providers + Anthropic when selected above)
66
+ api_key_manager = APIKeyManager()
67
+ env_var = api_key_manager._get_env_var_name(provider)
68
+ console.print("\nTo configure your API key, you can either:")
69
+ console.print(f"• Set the {env_var} environment variable")
70
+ console.print("• Let SQLsaber prompt you for the key when needed (stored securely)")
71
+
72
+ # Fetch/store key (cascades env -> keyring -> prompt)
73
+ api_key = api_key_manager.get_api_key(provider)
74
+ if api_key:
75
+ config_manager.set_auth_method(AuthMethod.API_KEY)
76
+ console.print(
77
+ f"\n[bold green]✓ {provider.title()} API key configured successfully![/bold green]"
78
+ )
79
+ else:
80
+ console.print("\n[yellow]No API key configured.[/yellow]")
81
+
76
82
  console.print(
77
83
  "You can change this anytime by running [cyan]saber auth setup[/cyan] again."
78
84
  )
@@ -80,7 +86,7 @@ def setup():
80
86
 
81
87
  @auth_app.command
82
88
  def status():
83
- """Show current authentication configuration."""
89
+ """Show current authentication configuration and provider key status."""
84
90
  auth_method = config_manager.get_auth_method()
85
91
 
86
92
  console.print("\n[bold blue]Authentication Status[/bold blue]")
@@ -88,52 +94,113 @@ def status():
88
94
  if auth_method is None:
89
95
  console.print("[yellow]No authentication method configured[/yellow]")
90
96
  console.print("Run [cyan]saber auth setup[/cyan] to configure authentication.")
97
+ return
98
+
99
+ # Show configured method summary
100
+ if auth_method == AuthMethod.CLAUDE_PRO:
101
+ console.print("[green]✓ Anthropic Claude Pro/Max (OAuth) configured[/green]\n")
91
102
  else:
92
- if auth_method == AuthMethod.API_KEY:
93
- console.print("[green]✓ API Key authentication configured[/green]")
94
- console.print("Using Anthropic API key for authentication")
95
- elif auth_method == AuthMethod.CLAUDE_PRO:
96
- console.print("[green]✓ Claude Pro/Max subscription configured[/green]")
97
-
98
- # Check OAuth token status
99
- oauth_flow = AnthropicOAuthFlow()
100
- if oauth_flow.has_valid_authentication():
101
- console.print("OAuth token is valid and ready to use")
102
- else:
103
- console.print("[yellow]OAuth token missing or expired[/yellow]")
103
+ console.print("[green]✓ API Key authentication configured[/green]\n")
104
+
105
+ # Show per-provider status without prompting
106
+ api_key_manager = APIKeyManager()
107
+ for provider in providers.all_keys():
108
+ if provider == "anthropic":
109
+ # Include OAuth status
110
+ if OAuthTokenManager().has_oauth_token("anthropic"):
111
+ console.print("> anthropic (oauth): [green]configured[/green]")
112
+ env_var = api_key_manager._get_env_var_name(provider)
113
+ service = api_key_manager._get_service_name(provider)
114
+ from_env = bool(os.getenv(env_var))
115
+ from_keyring = bool(keyring.get_password(service, provider))
116
+ if from_env:
117
+ console.print(f"> {provider}: configured via {env_var}")
118
+ elif from_keyring:
119
+ console.print(f"> {provider}: [green]configured[/green]")
120
+ else:
121
+ console.print(f"> {provider}: [yellow]not configured[/yellow]")
104
122
 
105
123
 
106
124
  @auth_app.command
107
125
  def reset():
108
- """Reset authentication configuration."""
109
- if not config_manager.has_auth_configured():
110
- console.print("[yellow]No authentication configuration to reset.[/yellow]")
126
+ """Reset credentials for a selected provider (API key and/or OAuth)."""
127
+ console.print("\n[bold]SQLsaber Authentication Reset[/bold]\n")
128
+
129
+ # Choose provider to reset (mirrors setup)
130
+ provider = questionary.select(
131
+ "Select provider to reset:",
132
+ choices=providers.all_keys(),
133
+ ).ask()
134
+
135
+ if provider is None:
136
+ console.print("[yellow]Reset cancelled.[/yellow]")
111
137
  return
112
138
 
113
- current_method = config_manager.get_auth_method()
114
- method_name = (
115
- "API Key" if current_method == AuthMethod.API_KEY else "Claude Pro/Max"
139
+ api_key_manager = APIKeyManager()
140
+ service = api_key_manager._get_service_name(provider)
141
+
142
+ # Determine what exists in keyring
143
+ api_key_present = bool(keyring.get_password(service, provider))
144
+ oauth_present = (
145
+ OAuthTokenManager().has_oauth_token("anthropic")
146
+ if provider == "anthropic"
147
+ else False
116
148
  )
117
149
 
118
- if questionary.confirm(
119
- f"Are you sure you want to reset the current authentication method ({method_name})?",
120
- default=False,
121
- ).ask():
122
- # If Claude Pro, also remove OAuth tokens
123
- if current_method == AuthMethod.CLAUDE_PRO:
124
- oauth_flow = AnthropicOAuthFlow()
125
- oauth_flow.remove_authentication()
126
-
127
- # Clear the auth config by setting it to None
128
- config = config_manager._load_config()
129
- config["auth_method"] = None
130
- config_manager._save_config(config)
131
- console.print("[green]Authentication configuration reset.[/green]")
150
+ if not api_key_present and not oauth_present:
132
151
  console.print(
133
- "Run [cyan]saber auth setup[/cyan] to configure authentication again."
152
+ f"[yellow]No stored credentials found for {provider}. Nothing to reset.[/yellow]"
134
153
  )
135
- else:
154
+ return
155
+
156
+ # Build confirmation message
157
+ to_remove: list[str] = []
158
+ if oauth_present:
159
+ to_remove.append("Anthropic OAuth token")
160
+ if api_key_present:
161
+ to_remove.append(f"{provider.title()} API key")
162
+
163
+ summary = ", ".join(to_remove)
164
+ confirmed = questionary.confirm(
165
+ f"Remove the following for {provider}: {summary}?",
166
+ default=False,
167
+ ).ask()
168
+
169
+ if not confirmed:
136
170
  console.print("Reset cancelled.")
171
+ return
172
+
173
+ # Perform deletions
174
+ if oauth_present:
175
+ OAuthTokenManager().remove_oauth_token("anthropic")
176
+ if api_key_present:
177
+ try:
178
+ keyring.delete_password(service, provider)
179
+ console.print(f"Removed {provider} API key from keyring", style="green")
180
+ except keyring.errors.PasswordDeleteError:
181
+ # Already absent; treat as success
182
+ pass
183
+ except Exception as e:
184
+ console.print(f"Warning: Could not remove API key: {e}", style="yellow")
185
+
186
+ # Optionally clear global auth method if removing Anthropic OAuth configuration
187
+ if provider == "anthropic" and oauth_present:
188
+ current_method = config_manager.get_auth_method()
189
+ if current_method == AuthMethod.CLAUDE_PRO:
190
+ also_clear = questionary.confirm(
191
+ "Anthropic OAuth was removed. Also unset the global auth method?",
192
+ default=False,
193
+ ).ask()
194
+ if also_clear:
195
+ config = config_manager._load_config()
196
+ config["auth_method"] = None
197
+ config_manager._save_config(config)
198
+ console.print("Global auth method unset.", style="green")
199
+
200
+ console.print("\n[bold green]✓ Reset complete.[/bold green]")
201
+ console.print(
202
+ "Environment variables are not modified by this command.", style="dim"
203
+ )
137
204
 
138
205
 
139
206
  def create_auth_app() -> cyclopts.App:
sqlsaber/cli/commands.py CHANGED
@@ -7,7 +7,7 @@ from typing import Annotated
7
7
  import cyclopts
8
8
  from rich.console import Console
9
9
 
10
- from sqlsaber.agents.anthropic import AnthropicSQLAgent
10
+ from sqlsaber.agents import build_sqlsaber_agent
11
11
  from sqlsaber.cli.auth import create_auth_app
12
12
  from sqlsaber.cli.database import create_db_app
13
13
  from sqlsaber.cli.interactive import InteractiveSession
@@ -15,7 +15,13 @@ from sqlsaber.cli.memory import create_memory_app
15
15
  from sqlsaber.cli.models import create_models_app
16
16
  from sqlsaber.cli.streaming import StreamingQueryHandler
17
17
  from sqlsaber.config.database import DatabaseConfigManager
18
- from sqlsaber.database.connection import DatabaseConnection
18
+ from sqlsaber.database.connection import (
19
+ CSVConnection,
20
+ DatabaseConnection,
21
+ MySQLConnection,
22
+ PostgreSQLConnection,
23
+ SQLiteConnection,
24
+ )
19
25
  from sqlsaber.database.resolver import DatabaseResolutionError, resolve_database
20
26
 
21
27
 
@@ -29,7 +35,7 @@ class CLIError(Exception):
29
35
 
30
36
  app = cyclopts.App(
31
37
  name="sqlsaber",
32
- help="SQLSaber - Use the agent Luke!\n\nSQL assistant for your database",
38
+ help="SQLsaber - Open-source agentic SQL assistant for your database",
33
39
  )
34
40
 
35
41
 
@@ -123,25 +129,34 @@ def query(
123
129
  except Exception as e:
124
130
  raise CLIError(f"Error creating database connection: {e}")
125
131
 
126
- # Create agent instance with database name for memory context
127
- agent = AnthropicSQLAgent(db_conn, db_name)
132
+ # Create pydantic-ai agent instance with database name for memory context
133
+ agent = build_sqlsaber_agent(db_conn, db_name)
128
134
 
129
135
  try:
130
136
  if actual_query:
131
137
  # Single query mode with streaming
132
138
  streaming_handler = StreamingQueryHandler(console)
139
+ # Compute DB type for the greeting line
140
+ db_type = (
141
+ "PostgreSQL"
142
+ if isinstance(db_conn, PostgreSQLConnection)
143
+ else "MySQL"
144
+ if isinstance(db_conn, MySQLConnection)
145
+ else "SQLite"
146
+ if isinstance(db_conn, (SQLiteConnection, CSVConnection))
147
+ else "database"
148
+ )
133
149
  console.print(
134
- f"[bold blue]Connected to:[/bold blue] {db_name} {agent._get_database_type_name()}\n"
150
+ f"[bold blue]Connected to:[/bold blue] {db_name} ({db_type})\n"
135
151
  )
136
152
  await streaming_handler.execute_streaming_query(actual_query, agent)
137
153
  else:
138
154
  # Interactive mode
139
- session = InteractiveSession(console, agent)
155
+ session = InteractiveSession(console, agent, db_conn, db_name)
140
156
  await session.run()
141
157
 
142
158
  finally:
143
159
  # Clean up
144
- await agent.close() # Close the agent's HTTP client
145
160
  await db_conn.close()
146
161
  console.print("\n[green]Goodbye![/green]")
147
162
 
sqlsaber/cli/database.py CHANGED
@@ -6,8 +6,8 @@ import sys
6
6
  from pathlib import Path
7
7
  from typing import Annotated
8
8
 
9
- import questionary
10
9
  import cyclopts
10
+ import questionary
11
11
  from rich.console import Console
12
12
  from rich.table import Table
13
13