sqlsaber 0.14.0__py3-none-any.whl → 0.16.0__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.

Files changed (38) hide show
  1. sqlsaber/agents/__init__.py +2 -4
  2. sqlsaber/agents/base.py +18 -221
  3. sqlsaber/agents/mcp.py +2 -2
  4. sqlsaber/agents/pydantic_ai_agent.py +170 -0
  5. sqlsaber/cli/auth.py +146 -79
  6. sqlsaber/cli/commands.py +22 -7
  7. sqlsaber/cli/database.py +1 -1
  8. sqlsaber/cli/interactive.py +65 -30
  9. sqlsaber/cli/models.py +58 -29
  10. sqlsaber/cli/streaming.py +114 -77
  11. sqlsaber/config/api_keys.py +9 -11
  12. sqlsaber/config/providers.py +116 -0
  13. sqlsaber/config/settings.py +50 -30
  14. sqlsaber/database/connection.py +3 -3
  15. sqlsaber/mcp/mcp.py +43 -51
  16. sqlsaber/models/__init__.py +0 -3
  17. sqlsaber/tools/__init__.py +25 -0
  18. sqlsaber/tools/base.py +85 -0
  19. sqlsaber/tools/enums.py +21 -0
  20. sqlsaber/tools/instructions.py +251 -0
  21. sqlsaber/tools/registry.py +130 -0
  22. sqlsaber/tools/sql_tools.py +275 -0
  23. sqlsaber/tools/visualization_tools.py +144 -0
  24. {sqlsaber-0.14.0.dist-info → sqlsaber-0.16.0.dist-info}/METADATA +20 -39
  25. sqlsaber-0.16.0.dist-info/RECORD +51 -0
  26. sqlsaber/agents/anthropic.py +0 -579
  27. sqlsaber/agents/streaming.py +0 -16
  28. sqlsaber/clients/__init__.py +0 -6
  29. sqlsaber/clients/anthropic.py +0 -285
  30. sqlsaber/clients/base.py +0 -31
  31. sqlsaber/clients/exceptions.py +0 -117
  32. sqlsaber/clients/models.py +0 -282
  33. sqlsaber/clients/streaming.py +0 -257
  34. sqlsaber/models/events.py +0 -28
  35. sqlsaber-0.14.0.dist-info/RECORD +0 -51
  36. {sqlsaber-0.14.0.dist-info → sqlsaber-0.16.0.dist-info}/WHEEL +0 -0
  37. {sqlsaber-0.14.0.dist-info → sqlsaber-0.16.0.dist-info}/entry_points.txt +0 -0
  38. {sqlsaber-0.14.0.dist-info → sqlsaber-0.16.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,275 @@
1
+ """SQL-related tools for database operations."""
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ from sqlsaber.database.connection import BaseDatabaseConnection
7
+ from sqlsaber.database.schema import SchemaManager
8
+
9
+ from .base import Tool
10
+ from .enums import ToolCategory, WorkflowPosition
11
+ from .registry import register_tool
12
+
13
+
14
+ class SQLTool(Tool):
15
+ """Base class for SQL tools that need database access."""
16
+
17
+ def __init__(self, db_connection: BaseDatabaseConnection | None = None):
18
+ """Initialize with optional database connection."""
19
+ super().__init__()
20
+ self.db = db_connection
21
+ self.schema_manager = SchemaManager(db_connection) if db_connection else None
22
+
23
+ def set_connection(self, db_connection: BaseDatabaseConnection) -> None:
24
+ """Set the database connection after initialization."""
25
+ self.db = db_connection
26
+ self.schema_manager = SchemaManager(db_connection)
27
+
28
+ @property
29
+ def category(self) -> ToolCategory:
30
+ """SQL tools belong to the 'sql' category."""
31
+ return ToolCategory.SQL
32
+
33
+
34
+ @register_tool
35
+ class ListTablesTool(SQLTool):
36
+ """Tool for listing database tables."""
37
+
38
+ @property
39
+ def name(self) -> str:
40
+ return "list_tables"
41
+
42
+ @property
43
+ def description(self) -> str:
44
+ return "Get a list of all tables in the database with row counts. Use this first to discover available tables."
45
+
46
+ @property
47
+ def input_schema(self) -> dict[str, Any]:
48
+ return {
49
+ "type": "object",
50
+ "properties": {},
51
+ "required": [],
52
+ }
53
+
54
+ def get_usage_instructions(self) -> str | None:
55
+ """Return usage instructions for this tool."""
56
+ return "ALWAYS start with 'list_tables' to see available tables and row counts. Use this first to discover available tables."
57
+
58
+ def get_priority(self) -> int:
59
+ """Return priority for tool ordering."""
60
+ return 10 # High priority - should be used first
61
+
62
+ def get_workflow_position(self) -> WorkflowPosition:
63
+ """Return workflow position."""
64
+ return WorkflowPosition.DISCOVERY
65
+
66
+ async def execute(self, **kwargs) -> str:
67
+ """List all tables in the database."""
68
+ if not self.db or not self.schema_manager:
69
+ return json.dumps({"error": "No database connection available"})
70
+
71
+ try:
72
+ tables_info = await self.schema_manager.list_tables()
73
+ return json.dumps(tables_info)
74
+ except Exception as e:
75
+ return json.dumps({"error": f"Error listing tables: {str(e)}"})
76
+
77
+
78
+ @register_tool
79
+ class IntrospectSchemaTool(SQLTool):
80
+ """Tool for introspecting database schema."""
81
+
82
+ @property
83
+ def name(self) -> str:
84
+ return "introspect_schema"
85
+
86
+ @property
87
+ def description(self) -> str:
88
+ return "Introspect database schema to understand table structures."
89
+
90
+ @property
91
+ def input_schema(self) -> dict[str, Any]:
92
+ return {
93
+ "type": "object",
94
+ "properties": {
95
+ "table_pattern": {
96
+ "type": "string",
97
+ "description": "Optional pattern to filter tables (e.g., 'public.users', 'user%', '%order%')",
98
+ }
99
+ },
100
+ "required": [],
101
+ }
102
+
103
+ def get_usage_instructions(self) -> str | None:
104
+ """Return usage instructions for this tool."""
105
+ return "Use 'introspect_schema' with a table_pattern to get details ONLY for relevant tables. Use table patterns like 'sample%' or '%experiment%' to filter related tables."
106
+
107
+ def get_priority(self) -> int:
108
+ """Return priority for tool ordering."""
109
+ return 20 # Should come after list_tables
110
+
111
+ def get_workflow_position(self) -> WorkflowPosition:
112
+ """Return workflow position."""
113
+ return WorkflowPosition.ANALYSIS
114
+
115
+ async def execute(self, **kwargs) -> str:
116
+ """Introspect database schema."""
117
+ if not self.db or not self.schema_manager:
118
+ return json.dumps({"error": "No database connection available"})
119
+
120
+ try:
121
+ table_pattern = kwargs.get("table_pattern")
122
+ schema_info = await self.schema_manager.get_schema_info(table_pattern)
123
+
124
+ # Format the schema information
125
+ formatted_info = {}
126
+ for table_name, table_info in schema_info.items():
127
+ formatted_info[table_name] = {
128
+ "columns": {
129
+ col_name: {
130
+ "type": col_info["data_type"],
131
+ "nullable": col_info["nullable"],
132
+ "default": col_info["default"],
133
+ }
134
+ for col_name, col_info in table_info["columns"].items()
135
+ },
136
+ "primary_keys": table_info["primary_keys"],
137
+ "foreign_keys": [
138
+ f"{fk['column']} -> {fk['references']['table']}.{fk['references']['column']}"
139
+ for fk in table_info["foreign_keys"]
140
+ ],
141
+ }
142
+
143
+ return json.dumps(formatted_info)
144
+ except Exception as e:
145
+ return json.dumps({"error": f"Error introspecting schema: {str(e)}"})
146
+
147
+
148
+ @register_tool
149
+ class ExecuteSQLTool(SQLTool):
150
+ """Tool for executing SQL queries."""
151
+
152
+ DEFAULT_LIMIT = 100
153
+
154
+ @property
155
+ def name(self) -> str:
156
+ return "execute_sql"
157
+
158
+ @property
159
+ def description(self) -> str:
160
+ return "Execute a SQL query against the database."
161
+
162
+ @property
163
+ def input_schema(self) -> dict[str, Any]:
164
+ return {
165
+ "type": "object",
166
+ "properties": {
167
+ "query": {
168
+ "type": "string",
169
+ "description": "SQL query to execute",
170
+ },
171
+ "limit": {
172
+ "type": "integer",
173
+ "description": f"Maximum number of rows to return (default: {self.DEFAULT_LIMIT})",
174
+ "default": self.DEFAULT_LIMIT,
175
+ },
176
+ },
177
+ "required": ["query"],
178
+ }
179
+
180
+ def get_usage_instructions(self) -> str | None:
181
+ """Return usage instructions for this tool."""
182
+ return "Execute SQL queries safely with automatic LIMIT clauses for SELECT statements. Only SELECT queries are permitted for security."
183
+
184
+ def get_priority(self) -> int:
185
+ """Return priority for tool ordering."""
186
+ return 30 # Should come after schema tools
187
+
188
+ def get_workflow_position(self) -> WorkflowPosition:
189
+ """Return workflow position."""
190
+ return WorkflowPosition.EXECUTION
191
+
192
+ async def execute(self, **kwargs) -> str:
193
+ """Execute a SQL query."""
194
+ if not self.db:
195
+ return json.dumps({"error": "No database connection available"})
196
+
197
+ query = kwargs.get("query")
198
+ if not query:
199
+ return json.dumps({"error": "No query provided"})
200
+
201
+ limit = kwargs.get("limit", self.DEFAULT_LIMIT)
202
+
203
+ try:
204
+ # Security check - only allow SELECT queries unless write is enabled
205
+ write_error = self._validate_write_operation(query)
206
+ if write_error:
207
+ return json.dumps({"error": write_error})
208
+
209
+ # Add LIMIT if not present and it's a SELECT query
210
+ query = self._add_limit_to_query(query, limit)
211
+
212
+ # Execute the query
213
+ results = await self.db.execute_query(query)
214
+
215
+ # Format results
216
+ actual_limit = limit if limit is not None else len(results)
217
+
218
+ return json.dumps(
219
+ {
220
+ "success": True,
221
+ "row_count": len(results),
222
+ "results": results[:actual_limit],
223
+ "truncated": len(results) > actual_limit,
224
+ }
225
+ )
226
+
227
+ except Exception as e:
228
+ error_msg = str(e)
229
+
230
+ # Provide helpful error messages
231
+ suggestions = []
232
+ if "column" in error_msg.lower() and "does not exist" in error_msg.lower():
233
+ suggestions.append(
234
+ "Check column names using the schema introspection tool"
235
+ )
236
+ elif "table" in error_msg.lower() and "does not exist" in error_msg.lower():
237
+ suggestions.append(
238
+ "Check table names using the schema introspection tool"
239
+ )
240
+ elif "syntax error" in error_msg.lower():
241
+ suggestions.append(
242
+ "Review SQL syntax, especially JOIN conditions and WHERE clauses"
243
+ )
244
+
245
+ return json.dumps({"error": error_msg, "suggestions": suggestions})
246
+
247
+ def _validate_write_operation(self, query: str) -> str | None:
248
+ """Validate if a write operation is allowed."""
249
+ query_upper = query.strip().upper()
250
+
251
+ # Check for write operations
252
+ write_keywords = [
253
+ "INSERT",
254
+ "UPDATE",
255
+ "DELETE",
256
+ "DROP",
257
+ "CREATE",
258
+ "ALTER",
259
+ "TRUNCATE",
260
+ ]
261
+ is_write_query = any(query_upper.startswith(kw) for kw in write_keywords)
262
+
263
+ if is_write_query:
264
+ return (
265
+ "Write operations are not allowed. Only SELECT queries are permitted."
266
+ )
267
+
268
+ return None
269
+
270
+ def _add_limit_to_query(self, query: str, limit: int = 100) -> str:
271
+ """Add LIMIT clause to SELECT queries if not present."""
272
+ query_upper = query.strip().upper()
273
+ if query_upper.startswith("SELECT") and "LIMIT" not in query_upper:
274
+ return f"{query.rstrip(';')} LIMIT {limit};"
275
+ return query
@@ -0,0 +1,144 @@
1
+ """Visualization tools for data plotting."""
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ from uniplot import histogram, plot
7
+
8
+ from .base import Tool
9
+ from .enums import ToolCategory, WorkflowPosition
10
+ from .registry import register_tool
11
+
12
+
13
+ @register_tool
14
+ class PlotDataTool(Tool):
15
+ """Tool for creating terminal plots using uniplot."""
16
+
17
+ @property
18
+ def name(self) -> str:
19
+ return "plot_data"
20
+
21
+ @property
22
+ def description(self) -> str:
23
+ return "Create a plot of query results."
24
+
25
+ @property
26
+ def input_schema(self) -> dict[str, Any]:
27
+ return {
28
+ "type": "object",
29
+ "properties": {
30
+ "y_values": {
31
+ "type": "array",
32
+ "items": {"type": ["number", "null"]},
33
+ "description": "Y-axis data points (required)",
34
+ },
35
+ "x_values": {
36
+ "type": "array",
37
+ "items": {"type": ["number", "null"]},
38
+ "description": "X-axis data points (optional, will use indices if not provided)",
39
+ },
40
+ "plot_type": {
41
+ "type": "string",
42
+ "enum": ["line", "scatter", "histogram"],
43
+ "description": "Type of plot to create (default: line)",
44
+ "default": "line",
45
+ },
46
+ "title": {
47
+ "type": "string",
48
+ "description": "Title for the plot",
49
+ },
50
+ "x_label": {
51
+ "type": "string",
52
+ "description": "Label for X-axis",
53
+ },
54
+ "y_label": {
55
+ "type": "string",
56
+ "description": "Label for Y-axis",
57
+ },
58
+ },
59
+ "required": ["y_values"],
60
+ }
61
+
62
+ @property
63
+ def category(self) -> ToolCategory:
64
+ return ToolCategory.VISUALIZATION
65
+
66
+ def get_usage_instructions(self) -> str | None:
67
+ """Return usage instructions for this tool."""
68
+ return "Create terminal plots from query results when visualization would enhance understanding of the data."
69
+
70
+ def get_priority(self) -> int:
71
+ """Return priority for tool ordering."""
72
+ return 40 # Should come after SQL execution
73
+
74
+ def get_workflow_position(self) -> WorkflowPosition:
75
+ """Return workflow position."""
76
+ return WorkflowPosition.VISUALIZATION
77
+
78
+ async def execute(self, **kwargs) -> str:
79
+ """Create a terminal plot."""
80
+ y_values = kwargs.get("y_values", [])
81
+ x_values = kwargs.get("x_values")
82
+ plot_type = kwargs.get("plot_type", "line")
83
+ title = kwargs.get("title")
84
+ x_label = kwargs.get("x_label")
85
+ y_label = kwargs.get("y_label")
86
+
87
+ try:
88
+ # Validate inputs
89
+ if not y_values:
90
+ return json.dumps({"error": "No data provided for plotting"})
91
+
92
+ # Convert to floats if needed
93
+ try:
94
+ y_values = [float(v) if v is not None else None for v in y_values]
95
+ if x_values:
96
+ x_values = [float(v) if v is not None else None for v in x_values]
97
+ except (ValueError, TypeError) as e:
98
+ return json.dumps({"error": f"Invalid data format: {str(e)}"})
99
+
100
+ # Create the plot
101
+ if plot_type == "histogram":
102
+ # For histogram, we only need y_values
103
+ histogram(
104
+ y_values,
105
+ title=title,
106
+ bins=min(20, len(set(y_values))), # Adaptive bin count
107
+ )
108
+ plot_info = {
109
+ "type": "histogram",
110
+ "data_points": len(y_values),
111
+ "title": title or "Histogram",
112
+ }
113
+ elif plot_type in ["line", "scatter"]:
114
+ # For line/scatter plots
115
+ plot_kwargs = {
116
+ "ys": y_values,
117
+ "title": title,
118
+ "lines": plot_type == "line",
119
+ }
120
+
121
+ if x_values:
122
+ plot_kwargs["xs"] = x_values
123
+ if x_label:
124
+ plot_kwargs["x_unit"] = x_label
125
+ if y_label:
126
+ plot_kwargs["y_unit"] = y_label
127
+
128
+ plot(**plot_kwargs)
129
+
130
+ plot_info = {
131
+ "type": plot_type,
132
+ "data_points": len(y_values),
133
+ "title": title or f"{plot_type.capitalize()} Plot",
134
+ "has_x_values": x_values is not None,
135
+ }
136
+ else:
137
+ return json.dumps({"error": f"Unsupported plot type: {plot_type}"})
138
+
139
+ return json.dumps(
140
+ {"success": True, "plot_rendered": True, "plot_info": plot_info}
141
+ )
142
+
143
+ except Exception as e:
144
+ return json.dumps({"error": f"Error creating plot: {str(e)}"})
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlsaber
3
- Version: 0.14.0
4
- Summary: SQLSaber - Agentic SQL assistant like Claude Code
3
+ Version: 0.16.0
4
+ Summary: SQLsaber - Open-source agentic SQL assistant
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
7
  Requires-Dist: aiomysql>=0.2.0
8
8
  Requires-Dist: aiosqlite>=0.21.0
9
- Requires-Dist: anthropic>=0.54.0
10
9
  Requires-Dist: asyncpg>=0.30.0
11
10
  Requires-Dist: cyclopts>=3.22.1
12
11
  Requires-Dist: fastmcp>=2.9.0
@@ -14,27 +13,21 @@ Requires-Dist: httpx>=0.28.1
14
13
  Requires-Dist: keyring>=25.6.0
15
14
  Requires-Dist: pandas>=2.0.0
16
15
  Requires-Dist: platformdirs>=4.0.0
16
+ Requires-Dist: pydantic-ai
17
17
  Requires-Dist: questionary>=2.1.0
18
18
  Requires-Dist: rich>=13.7.0
19
19
  Requires-Dist: uniplot>=0.21.2
20
20
  Description-Content-Type: text/markdown
21
21
 
22
- # SQLSaber
22
+ # SQLsaber
23
23
 
24
- ```
25
- ███████ ██████ ██ ███████ █████ ██████ ███████ ██████
26
- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
27
- ███████ ██ ██ ██ ███████ ███████ ██████ █████ ██████
28
- ██ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
29
- ███████ ██████ ███████ ███████ ██ ██ ██████ ███████ ██ ██
30
- ▀▀
31
- ```
24
+ > SQLsaber is an open-source agentic SQL assistant. Think Claude Code but for SQL.
32
25
 
33
- > Use the agent Luke!
26
+ ![demo](./sqlsaber.gif)
34
27
 
35
- SQLSaber is an agentic SQL assistant. Think Claude Code but for SQL.
28
+ Stop fighting your database.
36
29
 
37
- Ask your questions in natural language and it will gather the right context automatically and answer your query by writing SQL and analyzing the results.
30
+ Ask your questions in natural language and `sqlsaber` will gather the right context automatically and answer your query by writing SQL and analyzing the results.
38
31
 
39
32
  ## Table of Contents
40
33
 
@@ -59,14 +52,13 @@ Ask your questions in natural language and it will gather the right context auto
59
52
 
60
53
  ## Features
61
54
 
62
- - Natural language to SQL conversion
63
55
  - 🔍 Automatic database schema introspection
64
56
  - 🛡️ Safe query execution (read-only by default)
65
57
  - 🧠 Memory management
66
58
  - 💬 Interactive REPL mode
67
- - 🎨 Beautiful formatted output with syntax highlighting
68
59
  - 🗄️ Support for PostgreSQL, SQLite, and MySQL
69
60
  - 🔌 MCP (Model Context Protocol) server support
61
+ - 🎨 Beautiful formatted output
70
62
 
71
63
  ## Installation
72
64
 
@@ -165,34 +157,21 @@ saber -d mydb
165
157
 
166
158
  # Single query with specific database
167
159
  saber -d mydb "count all orders"
160
+
161
+ # You can also pass a connection string
162
+ saber -d "postgresql://user:password@localhost:5432/mydb" "count all orders"
168
163
  ```
169
164
 
170
165
  ## Examples
171
166
 
172
167
  ```bash
173
- # Show database schema
174
- saber "what tables are in my database?"
175
-
176
- # Count records
177
- saber "how many active users do we have?"
168
+ # Start interactive mode
169
+ saber
178
170
 
179
- # Complex queries with joins
171
+ # Non-interactive mode
180
172
  saber "show me orders with customer details for this week"
181
173
 
182
- # Aggregations
183
- saber "what's the total revenue by product category?"
184
-
185
- # Date filtering
186
- saber "list users who haven't logged in for 30 days"
187
-
188
- # Data exploration
189
- saber "show me the distribution of customer ages"
190
-
191
- # Business analytics
192
174
  saber "which products had the highest sales growth last quarter?"
193
-
194
- # Start interactive mode
195
- saber
196
175
  ```
197
176
 
198
177
  ## MCP Server Integration
@@ -214,7 +193,7 @@ uvx --from sqlsaber saber-mcp
214
193
  Add SQLSaber as an MCP server in Claude Code:
215
194
 
216
195
  ```bash
217
- claude mcp add -- uvx --from sqlsaber saber-mcp
196
+ claude mcp add sqlsaber -- uvx --from sqlsaber saber-mcp
218
197
  ```
219
198
 
220
199
  #### Other MCP Clients
@@ -234,7 +213,7 @@ The MCP server uses your existing SQLSaber database configurations, so make sure
234
213
 
235
214
  ## How It Works
236
215
 
237
- SQLSaber uses a multi-step process to gather the right context, provide it to the model, and execute SQL queries to get the right answers:
216
+ SQLsaber uses a multi-step agentic process to gather the right context and execute SQL queries to answer your questions:
238
217
 
239
218
  ![](./sqlsaber.svg)
240
219
 
@@ -255,7 +234,9 @@ SQLSaber uses a multi-step process to gather the right context, provide it to th
255
234
 
256
235
  ## Contributing
257
236
 
258
- Contributions are welcome! Please feel free to open an issue to discuss your ideas or report bugs.
237
+ If you like the project, starring the repo is a great way to show your support!
238
+
239
+ Other contributions are welcome! Please feel free to open an issue to discuss your ideas or report bugs.
259
240
 
260
241
  ## License
261
242
 
@@ -0,0 +1,51 @@
1
+ sqlsaber/__init__.py,sha256=HjS8ULtP4MGpnTL7njVY45NKV9Fi4e_yeYuY-hyXWQc,73
2
+ sqlsaber/__main__.py,sha256=RIHxWeWh2QvLfah-2OkhI5IJxojWfy4fXpMnVEJYvxw,78
3
+ sqlsaber/agents/__init__.py,sha256=i_MI2eWMQaVzGikKU71FPCmSQxNDKq36Imq1PrYoIPU,130
4
+ sqlsaber/agents/base.py,sha256=2JX7UKW7tbqxPaK4aQLpXIwT3uszdBQHdSsOyML2U7I,6353
5
+ sqlsaber/agents/mcp.py,sha256=GcJTx7YDYH6aaxIADEIxSgcWAdWakUx395JIzVnf17U,768
6
+ sqlsaber/agents/pydantic_ai_agent.py,sha256=dGdsgyxCZvfK-v-MH8KimKOr-xb2aSfSWY8CMcOUCT8,6795
7
+ sqlsaber/cli/__init__.py,sha256=qVSLVJLLJYzoC6aj6y9MFrzZvAwc4_OgxU9DlkQnZ4M,86
8
+ sqlsaber/cli/auth.py,sha256=jTsRgbmlGPlASSuIKmdjjwfqtKvjfKd_cTYxX0-QqaQ,7400
9
+ sqlsaber/cli/commands.py,sha256=caWyMqOyCJVM0gpC4M_7DMuI7fzsf4Kcm_BJxOQrkM0,6718
10
+ sqlsaber/cli/completers.py,sha256=HsUPjaZweLSeYCWkAcgMl8FylQ1xjWBWYTEL_9F6xfU,6430
11
+ sqlsaber/cli/database.py,sha256=atwg3l8acQ3YTDuhq7vNrBN6tpOv0syz6V62KTF-Bh8,12910
12
+ sqlsaber/cli/display.py,sha256=HtXwPe3VPUh2EJpyvpJVWyisCanu9O7w-rkqq7Y4UaY,9791
13
+ sqlsaber/cli/interactive.py,sha256=SNOV_MaqJt2NQr6H8GbbWzMyuGx2NLFEXetjKtFAefw,9635
14
+ sqlsaber/cli/memory.py,sha256=OufHFJFwV0_GGn7LvKRTJikkWhV1IwNIUDOxFPHXOaQ,7794
15
+ sqlsaber/cli/models.py,sha256=ZewtwGQwhd9b-yxBAPKePolvI1qQG-EkmeWAGMqtWNQ,8986
16
+ sqlsaber/cli/streaming.py,sha256=3KXDu8_IJDkt-q6HoO7LYtLXjliNwWy_pOrgV8BQ6Bg,5453
17
+ sqlsaber/config/__init__.py,sha256=olwC45k8Nc61yK0WmPUk7XHdbsZH9HuUAbwnmKe3IgA,100
18
+ sqlsaber/config/api_keys.py,sha256=RqWQCko1tY7sES7YOlexgBH5Hd5ne_kGXHdBDNqcV2U,3649
19
+ sqlsaber/config/auth.py,sha256=b5qB2h1doXyO9Bn8z0CcL8LAR2jF431gGXBGKLgTmtQ,2756
20
+ sqlsaber/config/database.py,sha256=c6q3l4EvoBch1ckYHA70hf6L7fSOY-sItnLCpvJiPrA,11357
21
+ sqlsaber/config/oauth_flow.py,sha256=A3bSXaBLzuAfXV2ZPA94m9NV33c2MyL6M4ii9oEkswQ,10291
22
+ sqlsaber/config/oauth_tokens.py,sha256=C9z35hyx-PvSAYdC1LNf3rg9_wsEIY56hkEczelbad0,6015
23
+ sqlsaber/config/providers.py,sha256=JFjeJv1K5Q93zWSlWq3hAvgch1TlgoF0qFa0KJROkKY,2957
24
+ sqlsaber/config/settings.py,sha256=vgb_RXaM-7DgbxYDmWNw1cSyMqwys4j3qNCvM4bljwI,5586
25
+ sqlsaber/conversation/__init__.py,sha256=xa-1gX6NsZpVGg_LDrsZAtDtsDo5FZc1SO8gwtm_IPk,302
26
+ sqlsaber/conversation/manager.py,sha256=LDfmKGIMvTzsL7S0aXGWw6Ve54CHIeTGLU4qwes2NgU,7046
27
+ sqlsaber/conversation/models.py,sha256=fq4wpIB2yxLCQtsXhdpDji4FpscG2ayrOBACrNvgF14,3510
28
+ sqlsaber/conversation/storage.py,sha256=phpGEnZjXVFTmV5PalCKZpiO9VFHubMMfWA9OJCDbwc,11626
29
+ sqlsaber/database/__init__.py,sha256=a_gtKRJnZVO8-fEZI7g3Z8YnGa6Nio-5Y50PgVp07ss,176
30
+ sqlsaber/database/connection.py,sha256=sJtIIe0GVbo-1Py9-j66UxJoY1aKL9gqk68jkDL-Kvk,15123
31
+ sqlsaber/database/resolver.py,sha256=RPXF5EoKzvQDDLmPGNHYd2uG_oNICH8qvUjBp6iXmNY,3348
32
+ sqlsaber/database/schema.py,sha256=OC93dnZkijCoVNqb6itSpQ2XsiZ85PjVUW-VZDwrPrk,25989
33
+ sqlsaber/mcp/__init__.py,sha256=COdWq7wauPBp5Ew8tfZItFzbcLDSEkHBJSMhxzy8C9c,112
34
+ sqlsaber/mcp/mcp.py,sha256=X12oCMZYAtgJ7MNuh5cqz8y3lALrOzkXWcfpuY0Ijxk,3950
35
+ sqlsaber/memory/__init__.py,sha256=GiWkU6f6YYVV0EvvXDmFWe_CxarmDCql05t70MkTEWs,63
36
+ sqlsaber/memory/manager.py,sha256=p3fybMVfH-E4ApT1ZRZUnQIWSk9dkfUPCyfkmA0HALs,2739
37
+ sqlsaber/memory/storage.py,sha256=ne8szLlGj5NELheqLnI7zu21V8YS4rtpYGGC7tOmi-s,5745
38
+ sqlsaber/models/__init__.py,sha256=b3KVyLN7Y7p2rT1-TlIExpkO26bTG04-VCFwDmBO6i0,204
39
+ sqlsaber/models/types.py,sha256=w-zk81V2dtveuteej36_o1fDK3So428j3P2rAejU62U,862
40
+ sqlsaber/tools/__init__.py,sha256=a-JNOhHsC7WEVhTsQY_IHckaPOmswoM3Q85YOd2iC_E,652
41
+ sqlsaber/tools/base.py,sha256=XLaHrAzZc4OUiOsKBvEfZCtWKDBd5WqgNAnmRufnOp4,2174
42
+ sqlsaber/tools/enums.py,sha256=TnlvEOpkGtuzEzg_JBdSb_S38jg4INhtxw4qZ20kU_E,483
43
+ sqlsaber/tools/instructions.py,sha256=nnItVvJBtN-FLB3-PSR6Y23ix6AiOB5hNX3r4TtYFKw,9869
44
+ sqlsaber/tools/registry.py,sha256=decVRNf50JPNT4i-OHZIL2B8cmoOMDaE4y0Av6q6v0I,3619
45
+ sqlsaber/tools/sql_tools.py,sha256=hM6tKqW5MDhFUt6MesoqhTUqIpq_5baIIDoN1MjDCXY,9647
46
+ sqlsaber/tools/visualization_tools.py,sha256=059Pe3aOZvgpqT9487Ydv2PhY7T1pVmfALPTvfqPisI,4973
47
+ sqlsaber-0.16.0.dist-info/METADATA,sha256=wa2OqUBvJu-KGPMsH0vhULYksY9sDcn-nRJihroMzA8,5966
48
+ sqlsaber-0.16.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
49
+ sqlsaber-0.16.0.dist-info/entry_points.txt,sha256=qEbOB7OffXPFgyJc7qEIJlMEX5RN9xdzLmWZa91zCQQ,162
50
+ sqlsaber-0.16.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
51
+ sqlsaber-0.16.0.dist-info/RECORD,,