sqlsaber 0.26.0__py3-none-any.whl → 0.28.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.
@@ -0,0 +1,219 @@
1
+ """Theme management for unified theming across Rich and prompt_toolkit."""
2
+
3
+ import os
4
+ import tomllib
5
+ from dataclasses import dataclass
6
+ from functools import lru_cache
7
+ from typing import Dict
8
+
9
+ from platformdirs import user_config_dir
10
+ from prompt_toolkit.styles import Style as PTStyle
11
+ from prompt_toolkit.styles.pygments import style_from_pygments_cls
12
+ from pygments.styles import get_style_by_name
13
+ from rich.console import Console
14
+ from rich.theme import Theme
15
+
16
+ DEFAULT_THEME_NAME = "nord"
17
+
18
+ DEFAULT_ROLE_PALETTE = {
19
+ # base roles
20
+ "primary": "cyan",
21
+ "accent": "magenta",
22
+ "success": "green",
23
+ "warning": "yellow",
24
+ "error": "red",
25
+ "info": "cyan",
26
+ "muted": "dim",
27
+ # components
28
+ "table.header": "bold $primary",
29
+ "panel.border.user": "$info",
30
+ "panel.border.assistant": "$success",
31
+ "panel.border.thread": "$primary",
32
+ "spinner": "$warning",
33
+ "status": "$warning",
34
+ # domain-specific
35
+ "key.primary": "bold $warning",
36
+ "key.foreign": "bold $accent",
37
+ "key.index": "bold $primary",
38
+ "column.schema": "$info",
39
+ "column.name": "white",
40
+ "column.type": "$warning",
41
+ "heading": "bold $primary",
42
+ "section": "bold $accent",
43
+ "title": "bold $success",
44
+ }
45
+
46
+ # Theme presets using exact Pygments colors
47
+ THEME_PRESETS = {
48
+ # Nord - exact colors from pygments nord theme
49
+ "nord": {
50
+ "primary": "#81a1c1", # Keyword (frost)
51
+ "accent": "#b48ead", # Number (aurora purple)
52
+ "success": "#a3be8c", # String (aurora green)
53
+ "warning": "#ebcb8b", # String.Escape (aurora yellow)
54
+ "error": "#bf616a", # Error/Generic.Error (aurora red)
55
+ "info": "#88c0d0", # Name.Function (frost cyan)
56
+ "muted": "dim",
57
+ },
58
+ # Dracula - exact colors from pygments dracula theme
59
+ "dracula": {
60
+ "primary": "#bd93f9", # purple
61
+ "accent": "#ff79c6", # pink
62
+ "success": "#50fa7b", # green
63
+ "warning": "#f1fa8c", # yellow
64
+ "error": "#ff5555", # red
65
+ "info": "#8be9fd", # cyan
66
+ "muted": "dim",
67
+ },
68
+ # Solarized Light - exact colors from pygments solarized-light theme
69
+ "solarized-light": {
70
+ "primary": "#268bd2", # blue
71
+ "accent": "#d33682", # magenta
72
+ "success": "#859900", # green
73
+ "warning": "#b58900", # yellow
74
+ "error": "#dc322f", # red
75
+ "info": "#2aa198", # cyan
76
+ "muted": "dim",
77
+ },
78
+ # VS (Visual Studio Light) - exact colors from pygments vs theme
79
+ "vs": {
80
+ "primary": "#0000ff", # Keyword (blue)
81
+ "accent": "#2b91af", # Keyword.Type/Name.Class
82
+ "success": "#008000", # Comment (green)
83
+ "warning": "#b58900", # (using solarized yellow as fallback)
84
+ "error": "#dc322f", # (using solarized red as fallback)
85
+ "info": "#2aa198", # (using solarized cyan as fallback)
86
+ "muted": "dim",
87
+ },
88
+ # Material (approximation based on material design colors)
89
+ "material": {
90
+ "primary": "#89ddff", # cyan
91
+ "accent": "#f07178", # pink/red
92
+ "success": "#c3e88d", # green
93
+ "warning": "#ffcb6b", # yellow
94
+ "error": "#ff5370", # red
95
+ "info": "#82aaff", # blue
96
+ "muted": "dim",
97
+ },
98
+ # One Dark - exact colors from pygments one-dark theme
99
+ "one-dark": {
100
+ "primary": "#c678dd", # Keyword (purple)
101
+ "accent": "#e06c75", # Name (red)
102
+ "success": "#98c379", # String (green)
103
+ "warning": "#e5c07b", # Keyword.Type (yellow)
104
+ "error": "#e06c75", # Name (red, used for errors)
105
+ "info": "#61afef", # Name.Function (blue)
106
+ "muted": "dim",
107
+ },
108
+ # Lightbulb - exact colors from pygments lightbulb theme (minimal dark)
109
+ "lightbulb": {
110
+ "primary": "#73d0ff", # Keyword.Type/Name.Class (blue_1)
111
+ "accent": "#dfbfff", # Number (magenta_1)
112
+ "success": "#d5ff80", # String (green_1)
113
+ "warning": "#ffd173", # Name.Function (yellow_1)
114
+ "error": "#f88f7f", # Error (red_1)
115
+ "info": "#95e6cb", # Name.Entity (cyan_1)
116
+ "muted": "dim",
117
+ },
118
+ }
119
+
120
+
121
+ def _load_user_theme_config() -> dict:
122
+ """Load theme configuration from user config directory."""
123
+ cfg_dir = user_config_dir("sqlsaber")
124
+ path = os.path.join(cfg_dir, "theme.toml")
125
+ if not os.path.exists(path):
126
+ return {}
127
+ with open(path, "rb") as f:
128
+ return tomllib.load(f)
129
+
130
+
131
+ def _resolve_refs(palette: dict[str, str]) -> dict[str, str]:
132
+ """Resolve $var references in palette values."""
133
+ out = {}
134
+ for k, v in palette.items():
135
+ if isinstance(v, str) and "$" in v:
136
+ parts = v.split()
137
+ resolved = []
138
+ for part in parts:
139
+ if part.startswith("$"):
140
+ ref = part[1:]
141
+ resolved.append(palette.get(ref, ""))
142
+ else:
143
+ resolved.append(part)
144
+ out[k] = " ".join(p for p in resolved if p)
145
+ else:
146
+ out[k] = v
147
+ return out
148
+
149
+
150
+ @dataclass(frozen=True)
151
+ class ThemeConfig:
152
+ """Theme configuration."""
153
+
154
+ name: str
155
+ pygments_style: str
156
+ roles: Dict[str, str]
157
+
158
+
159
+ class ThemeManager:
160
+ """Manages theme configuration and provides themed components."""
161
+
162
+ def __init__(self, cfg: ThemeConfig):
163
+ self._cfg = cfg
164
+ self._roles = _resolve_refs({**DEFAULT_ROLE_PALETTE, **cfg.roles})
165
+ self._rich_theme = Theme(self._roles)
166
+ self._pt_style = None
167
+
168
+ @property
169
+ def rich_theme(self) -> Theme:
170
+ """Get Rich theme with semantic role mappings."""
171
+ return self._rich_theme
172
+
173
+ @property
174
+ def pygments_style_name(self) -> str:
175
+ """Get pygments style name for syntax highlighting."""
176
+ return self._cfg.pygments_style
177
+
178
+ def pt_style(self) -> PTStyle:
179
+ """Get prompt_toolkit style derived from Pygments theme."""
180
+ if self._pt_style is None:
181
+ try:
182
+ # Try to use Pygments style directly
183
+ pygments_style = get_style_by_name(self._cfg.pygments_style)
184
+ self._pt_style = style_from_pygments_cls(pygments_style)
185
+ except Exception:
186
+ # Fallback to basic style if Pygments theme not found
187
+ self._pt_style = PTStyle.from_dict({})
188
+ return self._pt_style
189
+
190
+ def style(self, role: str) -> str:
191
+ """Get style string for a semantic role."""
192
+ return self._roles.get(role, "")
193
+
194
+
195
+ @lru_cache(maxsize=1)
196
+ def get_theme_manager() -> ThemeManager:
197
+ """Get the global theme manager instance."""
198
+ user_cfg = _load_user_theme_config()
199
+ env_name = os.getenv("SQLSABER_THEME")
200
+
201
+ name = (
202
+ env_name or user_cfg.get("theme", {}).get("name") or DEFAULT_THEME_NAME
203
+ ).lower()
204
+ pygments_style = user_cfg.get("theme", {}).get("pygments_style") or name
205
+
206
+ roles = dict(DEFAULT_ROLE_PALETTE)
207
+ roles.update(THEME_PRESETS.get(name, {}))
208
+ roles.update(user_cfg.get("roles", {}))
209
+
210
+ cfg = ThemeConfig(name=name, pygments_style=pygments_style, roles=roles)
211
+ return ThemeManager(cfg)
212
+
213
+
214
+ def create_console(**kwargs):
215
+ """Create a Rich Console with theme applied."""
216
+ # from rich.console import Console
217
+
218
+ tm = get_theme_manager()
219
+ return Console(theme=tm.rich_theme, **kwargs)
@@ -0,0 +1,225 @@
1
+ """SQL query validation and security using sqlglot AST analysis."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Optional
5
+
6
+ import sqlglot
7
+ from sqlglot import exp
8
+ from sqlglot.errors import ParseError
9
+
10
+ # Prohibited AST node types that indicate write/mutation operations
11
+ # Only include expression types that exist in sqlglot
12
+ PROHIBITED_NODES = {
13
+ # DML operations
14
+ exp.Insert,
15
+ exp.Update,
16
+ exp.Delete,
17
+ exp.Merge,
18
+ # DDL operations
19
+ exp.Create,
20
+ exp.Drop,
21
+ exp.Alter,
22
+ exp.TruncateTable,
23
+ exp.AlterRename,
24
+ # MySQL specific
25
+ exp.Replace,
26
+ # Transaction control
27
+ exp.Transaction,
28
+ # Analysis and maintenance
29
+ exp.Analyze,
30
+ # Data loading/copying
31
+ exp.Copy,
32
+ exp.LoadData,
33
+ # Session and configuration
34
+ exp.Set,
35
+ exp.Use,
36
+ exp.Pragma,
37
+ # Security
38
+ exp.Grant,
39
+ exp.Revoke,
40
+ # Database operations
41
+ exp.Attach,
42
+ exp.Detach,
43
+ # Locking and process control
44
+ exp.Lock,
45
+ exp.Kill,
46
+ # Commands
47
+ exp.Command,
48
+ }
49
+
50
+ try:
51
+ # Add optional types that may not exist in all sqlglot versions
52
+ PROHIBITED_NODES.add(exp.Vacuum)
53
+ except AttributeError:
54
+ pass
55
+
56
+ # Dangerous functions by dialect that can read files or execute commands
57
+ DANGEROUS_FUNCTIONS_BY_DIALECT = {
58
+ "postgres": {
59
+ "pg_read_file",
60
+ "pg_read_binary_file",
61
+ "pg_ls_dir",
62
+ "pg_stat_file",
63
+ "pg_logdir_ls",
64
+ "dblink",
65
+ "dblink_exec",
66
+ },
67
+ "mysql": {
68
+ "load_file",
69
+ "sys_eval",
70
+ "sys_exec",
71
+ },
72
+ "sqlite": {
73
+ "readfile",
74
+ "writefile",
75
+ },
76
+ "tsql": {
77
+ "xp_cmdshell",
78
+ },
79
+ }
80
+
81
+
82
+ @dataclass
83
+ class GuardResult:
84
+ """Result of SQL query validation."""
85
+
86
+ allowed: bool
87
+ reason: Optional[str] = None
88
+ is_select: bool = False
89
+
90
+
91
+ def is_select_like(stmt: exp.Expression) -> bool:
92
+ """Check if statement is a SELECT-like query.
93
+
94
+ Handles CTEs (WITH) and set operations (UNION/INTERSECT/EXCEPT).
95
+ """
96
+ root = stmt
97
+ # WITH wraps another statement
98
+ if isinstance(root, exp.With):
99
+ root = root.this
100
+ return isinstance(root, (exp.Select, exp.Union, exp.Except, exp.Intersect))
101
+
102
+
103
+ def has_prohibited_nodes(stmt: exp.Expression) -> Optional[str]:
104
+ """Walk AST to find any prohibited operations.
105
+
106
+ Checks for:
107
+ - Write operations (INSERT/UPDATE/DELETE/etc)
108
+ - DDL operations (CREATE/DROP/ALTER/etc)
109
+ - SELECT INTO
110
+ - Locking clauses (FOR UPDATE/FOR SHARE)
111
+ """
112
+ for node in stmt.walk():
113
+ # Check prohibited node types
114
+ if isinstance(node, tuple(PROHIBITED_NODES)):
115
+ return f"Prohibited operation: {type(node).__name__}"
116
+
117
+ # Block SELECT INTO (Postgres-style table creation)
118
+ if isinstance(node, exp.Select) and node.args.get("into"):
119
+ return "SELECT INTO is not allowed"
120
+
121
+ # Block locking clauses (FOR UPDATE/FOR SHARE)
122
+ if isinstance(node, exp.Select):
123
+ locks = node.args.get("locks")
124
+ if locks:
125
+ return "SELECT with locking clause (FOR UPDATE/SHARE) is not allowed"
126
+
127
+ return None
128
+
129
+
130
+ def has_dangerous_functions(stmt: exp.Expression, dialect: str) -> Optional[str]:
131
+ """Check for dangerous functions that can read files or execute commands."""
132
+ deny_set = DANGEROUS_FUNCTIONS_BY_DIALECT.get(dialect, set())
133
+ if not deny_set:
134
+ return None
135
+
136
+ deny_lower = {f.lower() for f in deny_set}
137
+
138
+ for fn in stmt.find_all(exp.Func):
139
+ name = fn.name
140
+ if name and name.lower() in deny_lower:
141
+ return f"Use of dangerous function '{name}' is not allowed"
142
+
143
+ return None
144
+
145
+
146
+ def validate_read_only(sql: str, dialect: str = "ansi") -> GuardResult:
147
+ """Validate that SQL query is read-only using AST analysis.
148
+
149
+ Args:
150
+ sql: SQL query to validate
151
+ dialect: SQL dialect (postgres, mysql, sqlite, tsql, etc.)
152
+
153
+ Returns:
154
+ GuardResult with validation outcome
155
+ """
156
+ try:
157
+ statements = sqlglot.parse(sql, read=dialect)
158
+ except ParseError as e:
159
+ return GuardResult(False, f"Unable to parse query safely: {e}")
160
+ except Exception as e:
161
+ return GuardResult(False, f"Error parsing query: {e}")
162
+
163
+ # Only allow single statements
164
+ if len(statements) != 1:
165
+ return GuardResult(
166
+ False,
167
+ f"Only single SELECT statements are allowed (got {len(statements)} statements)",
168
+ )
169
+
170
+ stmt = statements[0]
171
+
172
+ # Must be a SELECT-like statement
173
+ if not is_select_like(stmt):
174
+ return GuardResult(False, "Only SELECT-like statements are allowed")
175
+
176
+ # Check for prohibited operations in the AST
177
+ reason = has_prohibited_nodes(stmt)
178
+ if reason:
179
+ return GuardResult(False, reason)
180
+
181
+ # Check for dangerous functions
182
+ reason = has_dangerous_functions(stmt, dialect)
183
+ if reason:
184
+ return GuardResult(False, reason)
185
+
186
+ return GuardResult(True, None, is_select=True)
187
+
188
+
189
+ def add_limit(sql: str, dialect: str = "ansi", limit: int = 100) -> str:
190
+ """Add LIMIT clause to query if not already present.
191
+
192
+ Args:
193
+ sql: SQL query
194
+ dialect: SQL dialect for proper rendering
195
+ limit: Maximum number of rows to return
196
+
197
+ Returns:
198
+ SQL with LIMIT clause added (or original if LIMIT already exists)
199
+ """
200
+ try:
201
+ statements = sqlglot.parse(sql, read=dialect)
202
+ if len(statements) != 1:
203
+ return sql
204
+
205
+ stmt = statements[0]
206
+
207
+ # Check if LIMIT/TOP/FETCH already exists
208
+ has_limit = any(
209
+ isinstance(n, (exp.Limit, exp.Top, exp.Fetch)) for n in stmt.walk()
210
+ )
211
+ if has_limit:
212
+ return stmt.sql(dialect=dialect)
213
+
214
+ # Add LIMIT - sqlglot will render appropriately for dialect
215
+ # (LIMIT for most, TOP for SQL Server, FETCH FIRST for Oracle)
216
+ stmt = stmt.limit(limit)
217
+ return stmt.sql(dialect=dialect)
218
+
219
+ except Exception:
220
+ # If parsing/transformation fails, fall back to simple string append
221
+ # This maintains backward compatibility
222
+ sql_upper = sql.strip().upper()
223
+ if "LIMIT" not in sql_upper:
224
+ return f"{sql.rstrip(';')} LIMIT {limit};"
225
+ return sql
@@ -9,6 +9,7 @@ from sqlsaber.database.schema import SchemaManager
9
9
  from .base import Tool
10
10
  from .enums import ToolCategory, WorkflowPosition
11
11
  from .registry import register_tool
12
+ from .sql_guard import add_limit, validate_read_only
12
13
 
13
14
 
14
15
  class SQLTool(Tool):
@@ -207,13 +208,17 @@ class ExecuteSQLTool(SQLTool):
207
208
  limit = kwargs.get("limit", self.DEFAULT_LIMIT)
208
209
 
209
210
  try:
210
- # Security check - only allow SELECT queries unless write is enabled
211
- write_error = self._validate_write_operation(query)
212
- if write_error:
213
- return json.dumps({"error": write_error})
211
+ # Get the dialect for this database
212
+ dialect = self.db.sqlglot_dialect
213
+
214
+ # Security check using sqlglot AST analysis
215
+ validation_result = validate_read_only(query, dialect)
216
+ if not validation_result.allowed:
217
+ return json.dumps({"error": validation_result.reason})
214
218
 
215
219
  # Add LIMIT if not present and it's a SELECT query
216
- query = self._add_limit_to_query(query, limit)
220
+ if validation_result.is_select and limit:
221
+ query = add_limit(query, dialect, limit)
217
222
 
218
223
  # Execute the query
219
224
  results = await self.db.execute_query(query)
@@ -249,33 +254,3 @@ class ExecuteSQLTool(SQLTool):
249
254
  )
250
255
 
251
256
  return json.dumps({"error": error_msg, "suggestions": suggestions})
252
-
253
- def _validate_write_operation(self, query: str) -> str | None:
254
- """Validate if a write operation is allowed."""
255
- query_upper = query.strip().upper()
256
-
257
- # Check for write operations
258
- write_keywords = [
259
- "INSERT",
260
- "UPDATE",
261
- "DELETE",
262
- "DROP",
263
- "CREATE",
264
- "ALTER",
265
- "TRUNCATE",
266
- ]
267
- is_write_query = any(query_upper.startswith(kw) for kw in write_keywords)
268
-
269
- if is_write_query:
270
- return (
271
- "Write operations are not allowed. Only SELECT queries are permitted."
272
- )
273
-
274
- return None
275
-
276
- def _add_limit_to_query(self, query: str, limit: int = 100) -> str:
277
- """Add LIMIT clause to SELECT queries if not present."""
278
- query_upper = query.strip().upper()
279
- if query_upper.startswith("SELECT") and "LIMIT" not in query_upper:
280
- return f"{query.rstrip(';')} LIMIT {limit};"
281
- return query
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlsaber
3
- Version: 0.26.0
3
+ Version: 0.28.0
4
4
  Summary: SQLsaber - Open-source agentic SQL assistant
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -17,6 +17,7 @@ Requires-Dist: prompt-toolkit>3.0.51
17
17
  Requires-Dist: pydantic-ai
18
18
  Requires-Dist: questionary>=2.1.0
19
19
  Requires-Dist: rich>=13.7.0
20
+ Requires-Dist: sqlglot[rs]>=27.20.0
20
21
  Description-Content-Type: text/markdown
21
22
 
22
23
  # SQLsaber
@@ -0,0 +1,61 @@
1
+ sqlsaber/__init__.py,sha256=HjS8ULtP4MGpnTL7njVY45NKV9Fi4e_yeYuY-hyXWQc,73
2
+ sqlsaber/__main__.py,sha256=RIHxWeWh2QvLfah-2OkhI5IJxojWfy4fXpMnVEJYvxw,78
3
+ sqlsaber/agents/__init__.py,sha256=qYI6rLY4q5AbF47vXH5RVoM08-yQjymBSaePh4lFIW4,116
4
+ sqlsaber/agents/base.py,sha256=40-MKEoz5rGrqVIylV1U2DaAUSPFcC75ohRin4E3-kk,2668
5
+ sqlsaber/agents/mcp.py,sha256=Pn8tdDRUEVLYQyEi5nHRp9MKNePwHVVoeNI-uqWcr0Y,757
6
+ sqlsaber/agents/pydantic_ai_agent.py,sha256=wBxKz0pjOkL-HI-TXV6B67bczZNgu7k26Rr3w5usR3o,10064
7
+ sqlsaber/application/__init__.py,sha256=KY_-d5nEdQyAwNOsK5r-f7Tb69c63XbuEkHPeLpJal8,84
8
+ sqlsaber/application/auth_setup.py,sha256=cbS2VTwL7mwm24UFRz84PhC13XMzC1F7JkN-ze7tApY,5104
9
+ sqlsaber/application/db_setup.py,sha256=qtMxCd_KO7GsD4W_iRBpDRvLriiyvOXPvZdcvm6KVDM,6849
10
+ sqlsaber/application/model_selection.py,sha256=xZI-nvUgYZikaTK38SCmEWvWSfRsDpFu2VthbVdI95g,3187
11
+ sqlsaber/application/prompts.py,sha256=4rMGcWpYJbNWPMzqVWseUMx0nwvXOkWS6GaTAJ5mhfc,3473
12
+ sqlsaber/cli/__init__.py,sha256=qVSLVJLLJYzoC6aj6y9MFrzZvAwc4_OgxU9DlkQnZ4M,86
13
+ sqlsaber/cli/auth.py,sha256=ysDBXEFR8Jz7wYbIP6X7yWA2ivd8SDnUp_jUg_qYNWk,6088
14
+ sqlsaber/cli/commands.py,sha256=-rTxr-kW7j2rR8wAg0tATKoh284pMDPKVMpQKaJwtqk,8540
15
+ sqlsaber/cli/completers.py,sha256=g-hLDq5fiBx7gg8Bte1Lq8GU-ZxCYVs4dcPsmHPIcK4,6574
16
+ sqlsaber/cli/database.py,sha256=hh8PdWnhaD0fO2jwvSSQyxsjwk-JyvmcY7f5tuHfnAQ,10663
17
+ sqlsaber/cli/display.py,sha256=WB5JCumhXadziDEX1EZHG3vN1Chol5FNAaTXHieqFK0,17892
18
+ sqlsaber/cli/interactive.py,sha256=u1gvdMZJkFWelZQ2urOS6-EH1uShKF4u_edfr_BzDNk,13479
19
+ sqlsaber/cli/memory.py,sha256=gKP-JJ0w1ya1YTM_Lk7Gw-7wL9ptyj6cZtg-uoW8K7A,7818
20
+ sqlsaber/cli/models.py,sha256=NozZbnisSjbPKo7PW7CltJMIkGcPqTDpDQEY-C_eLhk,8504
21
+ sqlsaber/cli/onboarding.py,sha256=l6FFWn8J1OVQUxr-xIAzKaFhAz8rFh6IEWwIyPWqR6U,11438
22
+ sqlsaber/cli/streaming.py,sha256=1XoZGPPMoTmBQVgp_Bqk483MR93j9oXxSV6Tx_-TpOg,6923
23
+ sqlsaber/cli/threads.py,sha256=5EV4ckRzKqhWeTKpTfQSNCBuqs3onsJURKT09g4E4XM,13634
24
+ sqlsaber/config/__init__.py,sha256=olwC45k8Nc61yK0WmPUk7XHdbsZH9HuUAbwnmKe3IgA,100
25
+ sqlsaber/config/api_keys.py,sha256=bjogRmIuxNNGusyKXKi0ZpJWeS5Fyn53zrAD8hsoYx4,3671
26
+ sqlsaber/config/auth.py,sha256=b5qB2h1doXyO9Bn8z0CcL8LAR2jF431gGXBGKLgTmtQ,2756
27
+ sqlsaber/config/database.py,sha256=Yec6_0wdzq-ADblMNnbgvouYCimYOY_DWHT9oweaISc,11449
28
+ sqlsaber/config/oauth_flow.py,sha256=VNbq4TZPk0hVJIcOh7JUO5qSxJnNzqDj0vwjCn-59Ok,10316
29
+ sqlsaber/config/oauth_tokens.py,sha256=SC-lXVcKCV7uiWtBiU2mxvx1z7ryW8tOSKHBApPsXtE,5931
30
+ sqlsaber/config/providers.py,sha256=JFjeJv1K5Q93zWSlWq3hAvgch1TlgoF0qFa0KJROkKY,2957
31
+ sqlsaber/config/settings.py,sha256=iB4CnGQ4hw8gxkaa9CVLB_JEy6Y9h9FQTAams5OCVyI,6421
32
+ sqlsaber/database/__init__.py,sha256=Gi9N_NOkD459WRWXDg3hSuGoBs3xWbMDRBvsTVmnGAg,2025
33
+ sqlsaber/database/base.py,sha256=oaipLxlvoylX6oJCITPAWWqRqv09hRELqqEBufsmFic,3703
34
+ sqlsaber/database/csv.py,sha256=41wuP40FaGPfj28HMiD0I69uG0JbUxArpoTLC3MG2uc,4464
35
+ sqlsaber/database/duckdb.py,sha256=8HNKdx208aFK_YtwGjLz6LTne0xEmNevD-f9dRWlrFg,11244
36
+ sqlsaber/database/mysql.py,sha256=wMzDQqq4GFbfEdqXtv_sCb4Qbr9GSWqYAvOLeo5UryY,14472
37
+ sqlsaber/database/postgresql.py,sha256=fuf2Wl29NKXvD3mqsR08PDleNQ1PG-fNvWSxT6HDh2M,13223
38
+ sqlsaber/database/resolver.py,sha256=wSCcn__aCqwIfpt_LCjtW2Zgb8RpG5PlmwwZHli1q_U,3628
39
+ sqlsaber/database/schema.py,sha256=68PrNcA-5eR9PZB3i-TUQw5_E7QatwiDU2wv9GgXgM4,6928
40
+ sqlsaber/database/sqlite.py,sha256=iReEIiSpkhhS1VzITd79ZWqSL3fHMyfe3DRCDpM0DvE,9421
41
+ sqlsaber/mcp/__init__.py,sha256=COdWq7wauPBp5Ew8tfZItFzbcLDSEkHBJSMhxzy8C9c,112
42
+ sqlsaber/mcp/mcp.py,sha256=tpNPHpkaCre1Xjp7c4DHXbTKeuYpDQ8qhmJZvAyr7Vk,3939
43
+ sqlsaber/memory/__init__.py,sha256=GiWkU6f6YYVV0EvvXDmFWe_CxarmDCql05t70MkTEWs,63
44
+ sqlsaber/memory/manager.py,sha256=p3fybMVfH-E4ApT1ZRZUnQIWSk9dkfUPCyfkmA0HALs,2739
45
+ sqlsaber/memory/storage.py,sha256=ne8szLlGj5NELheqLnI7zu21V8YS4rtpYGGC7tOmi-s,5745
46
+ sqlsaber/theme/__init__.py,sha256=qCICX1Cg4B6yCbZ1UrerxglWxcqldRFVSRrSs73na_8,188
47
+ sqlsaber/theme/manager.py,sha256=0DWuVXn7JoC8NvAl5FSqc61eagKFTx5YnoY8SoCTxGM,7236
48
+ sqlsaber/threads/__init__.py,sha256=Hh3dIG1tuC8fXprREUpslCIgPYz8_6o7aRLx4yNeO48,139
49
+ sqlsaber/threads/storage.py,sha256=rsUdxT4CR52D7xtGir9UlsFnBMk11jZeflzDrk2q4ME,11183
50
+ sqlsaber/tools/__init__.py,sha256=x3YdmX_7P0Qq_HtZHAgfIVKTLxYqKk6oc4tGsujQWsc,586
51
+ sqlsaber/tools/base.py,sha256=mHhvAj27BHmckyvuDLCPlAQdzABJyYxd9SJnaYAwwuA,1777
52
+ sqlsaber/tools/enums.py,sha256=CH32mL-0k9ZA18911xLpNtsgpV6tB85TktMj6uqGz54,411
53
+ sqlsaber/tools/instructions.py,sha256=X-x8maVkkyi16b6Tl0hcAFgjiYceZaSwyWTfmrvx8U8,9024
54
+ sqlsaber/tools/registry.py,sha256=HWOQMsNIdL4XZS6TeNUyrL-5KoSDH6PHsWd3X66o-18,3211
55
+ sqlsaber/tools/sql_guard.py,sha256=dTDwcZP-N4xPGzcr7MQtKUxKrlDzlc1irr9aH5a4wvk,6182
56
+ sqlsaber/tools/sql_tools.py,sha256=ujmAcfLkNaBrb5LWEgWcINQEQSX0LRPX3VK5Dag1Sj4,9178
57
+ sqlsaber-0.28.0.dist-info/METADATA,sha256=oY9Awl9jLkdeJLa1oeTPHPGH94KktJ_HmBVUVLQs-do,7174
58
+ sqlsaber-0.28.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
59
+ sqlsaber-0.28.0.dist-info/entry_points.txt,sha256=qEbOB7OffXPFgyJc7qEIJlMEX5RN9xdzLmWZa91zCQQ,162
60
+ sqlsaber-0.28.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
61
+ sqlsaber-0.28.0.dist-info/RECORD,,
@@ -1,52 +0,0 @@
1
- sqlsaber/__init__.py,sha256=HjS8ULtP4MGpnTL7njVY45NKV9Fi4e_yeYuY-hyXWQc,73
2
- sqlsaber/__main__.py,sha256=RIHxWeWh2QvLfah-2OkhI5IJxojWfy4fXpMnVEJYvxw,78
3
- sqlsaber/agents/__init__.py,sha256=qYI6rLY4q5AbF47vXH5RVoM08-yQjymBSaePh4lFIW4,116
4
- sqlsaber/agents/base.py,sha256=40-MKEoz5rGrqVIylV1U2DaAUSPFcC75ohRin4E3-kk,2668
5
- sqlsaber/agents/mcp.py,sha256=Pn8tdDRUEVLYQyEi5nHRp9MKNePwHVVoeNI-uqWcr0Y,757
6
- sqlsaber/agents/pydantic_ai_agent.py,sha256=wBxKz0pjOkL-HI-TXV6B67bczZNgu7k26Rr3w5usR3o,10064
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=n25CErTLgLeRSkoJI0Ickwtns5EH6O7RLVPgPs6UBxA,7986
10
- sqlsaber/cli/completers.py,sha256=g-hLDq5fiBx7gg8Bte1Lq8GU-ZxCYVs4dcPsmHPIcK4,6574
11
- sqlsaber/cli/database.py,sha256=qil7nZGWKm3tULL0cUsAQ_KvhU1oikK0XVh9MibrvP0,13413
12
- sqlsaber/cli/display.py,sha256=32QaNS0RDgRz93AVy6nPo9blahvMPEoVMFC5spzh0-Y,17041
13
- sqlsaber/cli/interactive.py,sha256=jGbWNNcEgZuQRZamc5tX5eIf1Rv1T6Sj5NI_WvonTrA,13624
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=YViLCxUv-7WN5TCphLYtAR02HXvuHYuPttGGDZKDUKU,6921
17
- sqlsaber/cli/threads.py,sha256=zVlbOuD3GjjEVNebXwANKeKt4I_Lunf6itiBUL0TaKA,12877
18
- sqlsaber/config/__init__.py,sha256=olwC45k8Nc61yK0WmPUk7XHdbsZH9HuUAbwnmKe3IgA,100
19
- sqlsaber/config/api_keys.py,sha256=RqWQCko1tY7sES7YOlexgBH5Hd5ne_kGXHdBDNqcV2U,3649
20
- sqlsaber/config/auth.py,sha256=b5qB2h1doXyO9Bn8z0CcL8LAR2jF431gGXBGKLgTmtQ,2756
21
- sqlsaber/config/database.py,sha256=Yec6_0wdzq-ADblMNnbgvouYCimYOY_DWHT9oweaISc,11449
22
- sqlsaber/config/oauth_flow.py,sha256=A3bSXaBLzuAfXV2ZPA94m9NV33c2MyL6M4ii9oEkswQ,10291
23
- sqlsaber/config/oauth_tokens.py,sha256=C9z35hyx-PvSAYdC1LNf3rg9_wsEIY56hkEczelbad0,6015
24
- sqlsaber/config/providers.py,sha256=JFjeJv1K5Q93zWSlWq3hAvgch1TlgoF0qFa0KJROkKY,2957
25
- sqlsaber/config/settings.py,sha256=iB4CnGQ4hw8gxkaa9CVLB_JEy6Y9h9FQTAams5OCVyI,6421
26
- sqlsaber/database/__init__.py,sha256=Gi9N_NOkD459WRWXDg3hSuGoBs3xWbMDRBvsTVmnGAg,2025
27
- sqlsaber/database/base.py,sha256=yxYcfeNhRPbO5jFRVZH7eRUGj_up-y3p1ZX_obZXi0w,3552
28
- sqlsaber/database/csv.py,sha256=45eH9mAkBtwSu1Rc_vvG1Z40L4xvfHWSb8OMG15TbCA,4340
29
- sqlsaber/database/duckdb.py,sha256=v6gFUhih5NMbHHpUv7By2nXyl9aqdPtLt0zhqS4-OKE,11120
30
- sqlsaber/database/mysql.py,sha256=5qd9gnSCP3umtBJcQDTzzJfMzwqYCJhWlbOeJZ9_-6c,14349
31
- sqlsaber/database/postgresql.py,sha256=R8I3Y-w0P9qPe47-lmae0X17syIwI8saxEG3etx6Rqc,13097
32
- sqlsaber/database/resolver.py,sha256=wSCcn__aCqwIfpt_LCjtW2Zgb8RpG5PlmwwZHli1q_U,3628
33
- sqlsaber/database/schema.py,sha256=68PrNcA-5eR9PZB3i-TUQw5_E7QatwiDU2wv9GgXgM4,6928
34
- sqlsaber/database/sqlite.py,sha256=zdNj5i4mLJK21sWgftAHDHVihRUWevn__tVF9_nnLfQ,9297
35
- sqlsaber/mcp/__init__.py,sha256=COdWq7wauPBp5Ew8tfZItFzbcLDSEkHBJSMhxzy8C9c,112
36
- sqlsaber/mcp/mcp.py,sha256=tpNPHpkaCre1Xjp7c4DHXbTKeuYpDQ8qhmJZvAyr7Vk,3939
37
- sqlsaber/memory/__init__.py,sha256=GiWkU6f6YYVV0EvvXDmFWe_CxarmDCql05t70MkTEWs,63
38
- sqlsaber/memory/manager.py,sha256=p3fybMVfH-E4ApT1ZRZUnQIWSk9dkfUPCyfkmA0HALs,2739
39
- sqlsaber/memory/storage.py,sha256=ne8szLlGj5NELheqLnI7zu21V8YS4rtpYGGC7tOmi-s,5745
40
- sqlsaber/threads/__init__.py,sha256=Hh3dIG1tuC8fXprREUpslCIgPYz8_6o7aRLx4yNeO48,139
41
- sqlsaber/threads/storage.py,sha256=rsUdxT4CR52D7xtGir9UlsFnBMk11jZeflzDrk2q4ME,11183
42
- sqlsaber/tools/__init__.py,sha256=x3YdmX_7P0Qq_HtZHAgfIVKTLxYqKk6oc4tGsujQWsc,586
43
- sqlsaber/tools/base.py,sha256=mHhvAj27BHmckyvuDLCPlAQdzABJyYxd9SJnaYAwwuA,1777
44
- sqlsaber/tools/enums.py,sha256=CH32mL-0k9ZA18911xLpNtsgpV6tB85TktMj6uqGz54,411
45
- sqlsaber/tools/instructions.py,sha256=X-x8maVkkyi16b6Tl0hcAFgjiYceZaSwyWTfmrvx8U8,9024
46
- sqlsaber/tools/registry.py,sha256=HWOQMsNIdL4XZS6TeNUyrL-5KoSDH6PHsWd3X66o-18,3211
47
- sqlsaber/tools/sql_tools.py,sha256=2xLD_pkd0t8wKndQAKIr4c9UpWzVWeHbAFpkwo5j4kY,9954
48
- sqlsaber-0.26.0.dist-info/METADATA,sha256=o4vaJVAG_1U5Tybcx2MY3lX0FvYjBEdKxFDWhFC9xYs,7138
49
- sqlsaber-0.26.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
50
- sqlsaber-0.26.0.dist-info/entry_points.txt,sha256=qEbOB7OffXPFgyJc7qEIJlMEX5RN9xdzLmWZa91zCQQ,162
51
- sqlsaber-0.26.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
52
- sqlsaber-0.26.0.dist-info/RECORD,,