sqlsaber 0.18.0__py3-none-any.whl → 0.20.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.
- sqlsaber/cli/commands.py +11 -41
- sqlsaber/cli/database.py +3 -1
- sqlsaber/cli/interactive.py +61 -20
- sqlsaber/cli/threads.py +14 -7
- {sqlsaber-0.18.0.dist-info → sqlsaber-0.20.0.dist-info}/METADATA +2 -1
- {sqlsaber-0.18.0.dist-info → sqlsaber-0.20.0.dist-info}/RECORD +9 -9
- {sqlsaber-0.18.0.dist-info → sqlsaber-0.20.0.dist-info}/WHEEL +0 -0
- {sqlsaber-0.18.0.dist-info → sqlsaber-0.20.0.dist-info}/entry_points.txt +0 -0
- {sqlsaber-0.18.0.dist-info → sqlsaber-0.20.0.dist-info}/licenses/LICENSE +0 -0
sqlsaber/cli/commands.py
CHANGED
|
@@ -7,6 +7,12 @@ from typing import Annotated
|
|
|
7
7
|
import cyclopts
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
|
|
10
|
+
from sqlsaber.cli.auth import create_auth_app
|
|
11
|
+
from sqlsaber.cli.database import create_db_app
|
|
12
|
+
from sqlsaber.cli.memory import create_memory_app
|
|
13
|
+
from sqlsaber.cli.models import create_models_app
|
|
14
|
+
from sqlsaber.cli.threads import create_threads_app
|
|
15
|
+
|
|
10
16
|
# Lazy imports - only import what's needed for CLI parsing
|
|
11
17
|
from sqlsaber.config.database import DatabaseConfigManager
|
|
12
18
|
|
|
@@ -24,6 +30,11 @@ app = cyclopts.App(
|
|
|
24
30
|
help="SQLsaber - Open-source agentic SQL assistant for your database",
|
|
25
31
|
)
|
|
26
32
|
|
|
33
|
+
app.command(create_auth_app(), name="auth")
|
|
34
|
+
app.command(create_db_app(), name="db")
|
|
35
|
+
app.command(create_memory_app(), name="memory")
|
|
36
|
+
app.command(create_models_app(), name="models")
|
|
37
|
+
app.command(create_threads_app(), name="threads")
|
|
27
38
|
|
|
28
39
|
console = Console()
|
|
29
40
|
config_manager = DatabaseConfigManager()
|
|
@@ -195,47 +206,6 @@ def query(
|
|
|
195
206
|
sys.exit(e.exit_code)
|
|
196
207
|
|
|
197
208
|
|
|
198
|
-
# Use lazy imports for fast CLI startup time
|
|
199
|
-
@app.command(name="auth")
|
|
200
|
-
def auth(*args, **kwargs):
|
|
201
|
-
"""Manage authentication configuration."""
|
|
202
|
-
from sqlsaber.cli.auth import create_auth_app
|
|
203
|
-
|
|
204
|
-
return create_auth_app()(*args, **kwargs)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
@app.command(name="db")
|
|
208
|
-
def db(*args, **kwargs):
|
|
209
|
-
"""Manage database connections."""
|
|
210
|
-
from sqlsaber.cli.database import create_db_app
|
|
211
|
-
|
|
212
|
-
return create_db_app()(*args, **kwargs)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
@app.command(name="memory")
|
|
216
|
-
def memory(*args, **kwargs):
|
|
217
|
-
"""Manage database-specific memories."""
|
|
218
|
-
from sqlsaber.cli.memory import create_memory_app
|
|
219
|
-
|
|
220
|
-
return create_memory_app()(*args, **kwargs)
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
@app.command(name="models")
|
|
224
|
-
def models(*args, **kwargs):
|
|
225
|
-
"""Select and manage models."""
|
|
226
|
-
from sqlsaber.cli.models import create_models_app
|
|
227
|
-
|
|
228
|
-
return create_models_app()(*args, **kwargs)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
@app.command(name="threads")
|
|
232
|
-
def threads(*args, **kwargs):
|
|
233
|
-
"""Manage SQLsaber threads."""
|
|
234
|
-
from sqlsaber.cli.threads import create_threads_app
|
|
235
|
-
|
|
236
|
-
return create_threads_app()(*args, **kwargs)
|
|
237
|
-
|
|
238
|
-
|
|
239
209
|
def main():
|
|
240
210
|
"""Entry point for the CLI application."""
|
|
241
211
|
app()
|
sqlsaber/cli/database.py
CHANGED
|
@@ -12,7 +12,6 @@ from rich.console import Console
|
|
|
12
12
|
from rich.table import Table
|
|
13
13
|
|
|
14
14
|
from sqlsaber.config.database import DatabaseConfig, DatabaseConfigManager
|
|
15
|
-
from sqlsaber.database.connection import DatabaseConnection
|
|
16
15
|
|
|
17
16
|
# Global instances for CLI commands
|
|
18
17
|
console = Console()
|
|
@@ -343,6 +342,9 @@ def test(
|
|
|
343
342
|
"""Test a database connection."""
|
|
344
343
|
|
|
345
344
|
async def test_connection():
|
|
345
|
+
# Lazy import to keep CLI startup fast
|
|
346
|
+
from sqlsaber.database.connection import DatabaseConnection
|
|
347
|
+
|
|
346
348
|
if name:
|
|
347
349
|
db_config = config_manager.get_database(name)
|
|
348
350
|
if not db_config:
|
sqlsaber/cli/interactive.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
"""Interactive mode handling for the CLI."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
|
|
5
|
-
import
|
|
6
|
+
import platformdirs
|
|
7
|
+
from prompt_toolkit import PromptSession
|
|
8
|
+
from prompt_toolkit.history import FileHistory
|
|
9
|
+
from prompt_toolkit.patch_stdout import patch_stdout
|
|
10
|
+
from prompt_toolkit.styles import Style
|
|
6
11
|
from pydantic_ai import Agent
|
|
7
12
|
from rich.console import Console
|
|
8
13
|
from rich.panel import Panel
|
|
@@ -24,6 +29,23 @@ from sqlsaber.database.schema import SchemaManager
|
|
|
24
29
|
from sqlsaber.threads import ThreadStorage
|
|
25
30
|
|
|
26
31
|
|
|
32
|
+
def bottom_toolbar():
|
|
33
|
+
return [
|
|
34
|
+
(
|
|
35
|
+
"class:bottom-toolbar",
|
|
36
|
+
" Use 'Esc-Enter' or 'Meta-Enter' to submit.",
|
|
37
|
+
)
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
style = Style.from_dict(
|
|
42
|
+
{
|
|
43
|
+
"frame.border": "#ebbcba",
|
|
44
|
+
"bottom-toolbar": "#ebbcba bg:#21202e",
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
27
49
|
class InteractiveSession:
|
|
28
50
|
"""Manages interactive CLI sessions."""
|
|
29
51
|
|
|
@@ -82,7 +104,7 @@ class InteractiveSession:
|
|
|
82
104
|
self.console.print(
|
|
83
105
|
"\n",
|
|
84
106
|
"[dim] > Use '/clear' to reset conversation",
|
|
85
|
-
"[dim] > Use '/exit' or '/quit' to leave[/dim]",
|
|
107
|
+
"[dim] > Use 'Ctrl+D', '/exit' or '/quit' to leave[/dim]",
|
|
86
108
|
"[dim] > Use 'Ctrl+C' to interrupt and return to prompt\n\n",
|
|
87
109
|
"[dim] > Start message with '#' to add something to agent's memory for this database",
|
|
88
110
|
"[dim] > Type '@' to get table name completions",
|
|
@@ -97,6 +119,15 @@ class InteractiveSession:
|
|
|
97
119
|
if self._thread_id:
|
|
98
120
|
self.console.print(f"[dim]Resuming thread:[/dim] {self._thread_id}\n")
|
|
99
121
|
|
|
122
|
+
async def _end_thread_and_display_resume_hint(self):
|
|
123
|
+
"""End thread and display command to resume thread"""
|
|
124
|
+
# Print resume hint if there is an active thread
|
|
125
|
+
if self._thread_id:
|
|
126
|
+
await self._threads.end_thread(self._thread_id)
|
|
127
|
+
self.console.print(
|
|
128
|
+
f"[dim]You can continue this thread using:[/dim] saber threads resume {self._thread_id}"
|
|
129
|
+
)
|
|
130
|
+
|
|
100
131
|
async def _update_table_cache(self):
|
|
101
132
|
"""Update the table completer cache with fresh data."""
|
|
102
133
|
try:
|
|
@@ -137,7 +168,10 @@ class InteractiveSession:
|
|
|
137
168
|
# Create the query task
|
|
138
169
|
query_task = asyncio.create_task(
|
|
139
170
|
self.streaming_handler.execute_streaming_query(
|
|
140
|
-
user_query,
|
|
171
|
+
user_query,
|
|
172
|
+
self.agent,
|
|
173
|
+
self.cancellation_token,
|
|
174
|
+
self.message_history,
|
|
141
175
|
)
|
|
142
176
|
)
|
|
143
177
|
self.current_task = query_task
|
|
@@ -183,32 +217,35 @@ class InteractiveSession:
|
|
|
183
217
|
# Initialize table cache
|
|
184
218
|
await self._update_table_cache()
|
|
185
219
|
|
|
220
|
+
session = PromptSession(
|
|
221
|
+
history=FileHistory(
|
|
222
|
+
Path(platformdirs.user_config_dir("sqlsaber")) / "history"
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
|
|
186
226
|
while True:
|
|
187
227
|
try:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
228
|
+
with patch_stdout():
|
|
229
|
+
user_query = await session.prompt_async(
|
|
230
|
+
"",
|
|
231
|
+
multiline=True,
|
|
232
|
+
completer=CompositeCompleter(
|
|
233
|
+
SlashCommandCompleter(), self.table_completer
|
|
234
|
+
),
|
|
235
|
+
show_frame=True,
|
|
236
|
+
bottom_toolbar=bottom_toolbar,
|
|
237
|
+
style=style,
|
|
238
|
+
)
|
|
197
239
|
|
|
198
240
|
if not user_query:
|
|
199
241
|
continue
|
|
200
242
|
|
|
201
243
|
if (
|
|
202
|
-
user_query in ["/exit", "/quit"]
|
|
244
|
+
user_query in ["/exit", "/quit", "exit", "quit"]
|
|
203
245
|
or user_query.startswith("/exit")
|
|
204
246
|
or user_query.startswith("/quit")
|
|
205
247
|
):
|
|
206
|
-
|
|
207
|
-
if self._thread_id:
|
|
208
|
-
await self._threads.end_thread(self._thread_id)
|
|
209
|
-
self.console.print(
|
|
210
|
-
f"[dim]You can continue this thread using:[/dim] saber threads resume {self._thread_id}"
|
|
211
|
-
)
|
|
248
|
+
await self._end_thread_and_display_resume_hint()
|
|
212
249
|
break
|
|
213
250
|
|
|
214
251
|
if user_query == "/clear":
|
|
@@ -276,7 +313,11 @@ class InteractiveSession:
|
|
|
276
313
|
self.console.print("\n[yellow]Query interrupted[/yellow]")
|
|
277
314
|
else:
|
|
278
315
|
self.console.print(
|
|
279
|
-
"\n[yellow]
|
|
316
|
+
"\n[yellow]Press Ctrl+D to exit. Or use '/exit' or '/quit' slash command.[/yellow]"
|
|
280
317
|
)
|
|
318
|
+
except EOFError:
|
|
319
|
+
# Exit when Ctrl+D is pressed
|
|
320
|
+
await self._end_thread_and_display_resume_hint()
|
|
321
|
+
break
|
|
281
322
|
except Exception as e:
|
|
282
323
|
self.console.print(f"[bold red]Error:[/bold red] {str(e)}")
|
sqlsaber/cli/threads.py
CHANGED
|
@@ -12,17 +12,10 @@ from rich.markdown import Markdown
|
|
|
12
12
|
from rich.panel import Panel
|
|
13
13
|
from rich.table import Table
|
|
14
14
|
|
|
15
|
-
from sqlsaber.agents import build_sqlsaber_agent
|
|
16
|
-
from sqlsaber.cli.display import DisplayManager
|
|
17
|
-
from sqlsaber.cli.interactive import InteractiveSession
|
|
18
|
-
from sqlsaber.config.database import DatabaseConfigManager
|
|
19
|
-
from sqlsaber.database.connection import DatabaseConnection
|
|
20
|
-
from sqlsaber.database.resolver import DatabaseResolutionError, resolve_database
|
|
21
15
|
from sqlsaber.threads import ThreadStorage
|
|
22
16
|
|
|
23
17
|
# Globals consistent with other CLI modules
|
|
24
18
|
console = Console()
|
|
25
|
-
config_manager = DatabaseConfigManager()
|
|
26
19
|
|
|
27
20
|
|
|
28
21
|
threads_app = cyclopts.App(
|
|
@@ -41,6 +34,9 @@ def _render_transcript(
|
|
|
41
34
|
console: Console, all_msgs: list[ModelMessage], last_n: int | None = None
|
|
42
35
|
) -> None:
|
|
43
36
|
"""Render conversation turns from ModelMessage[] using DisplayManager."""
|
|
37
|
+
# Lazy import to avoid pulling UI helpers at startup
|
|
38
|
+
from sqlsaber.cli.display import DisplayManager
|
|
39
|
+
|
|
44
40
|
dm = DisplayManager(console)
|
|
45
41
|
|
|
46
42
|
# Locate indices of user prompts
|
|
@@ -237,6 +233,16 @@ def resume(
|
|
|
237
233
|
store = ThreadStorage()
|
|
238
234
|
|
|
239
235
|
async def _run() -> None:
|
|
236
|
+
# Lazy imports to avoid heavy modules at CLI startup
|
|
237
|
+
from sqlsaber.agents import build_sqlsaber_agent
|
|
238
|
+
from sqlsaber.cli.interactive import InteractiveSession
|
|
239
|
+
from sqlsaber.config.database import DatabaseConfigManager
|
|
240
|
+
from sqlsaber.database.connection import DatabaseConnection
|
|
241
|
+
from sqlsaber.database.resolver import (
|
|
242
|
+
DatabaseResolutionError,
|
|
243
|
+
resolve_database,
|
|
244
|
+
)
|
|
245
|
+
|
|
240
246
|
thread = await store.get_thread(thread_id)
|
|
241
247
|
if not thread:
|
|
242
248
|
console.print(f"[red]Thread not found:[/red] {thread_id}")
|
|
@@ -248,6 +254,7 @@ def resume(
|
|
|
248
254
|
)
|
|
249
255
|
return
|
|
250
256
|
try:
|
|
257
|
+
config_manager = DatabaseConfigManager()
|
|
251
258
|
resolved = resolve_database(db_selector, config_manager)
|
|
252
259
|
connection_string = resolved.connection_string
|
|
253
260
|
db_name = resolved.name
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlsaber
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.0
|
|
4
4
|
Summary: SQLsaber - Open-source agentic SQL assistant
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -13,6 +13,7 @@ Requires-Dist: httpx>=0.28.1
|
|
|
13
13
|
Requires-Dist: keyring>=25.6.0
|
|
14
14
|
Requires-Dist: pandas>=2.0.0
|
|
15
15
|
Requires-Dist: platformdirs>=4.0.0
|
|
16
|
+
Requires-Dist: prompt-toolkit>3.0.51
|
|
16
17
|
Requires-Dist: pydantic-ai
|
|
17
18
|
Requires-Dist: questionary>=2.1.0
|
|
18
19
|
Requires-Dist: rich>=13.7.0
|
|
@@ -6,15 +6,15 @@ sqlsaber/agents/mcp.py,sha256=GcJTx7YDYH6aaxIADEIxSgcWAdWakUx395JIzVnf17U,768
|
|
|
6
6
|
sqlsaber/agents/pydantic_ai_agent.py,sha256=dGdsgyxCZvfK-v-MH8KimKOr-xb2aSfSWY8CMcOUCT8,6795
|
|
7
7
|
sqlsaber/cli/__init__.py,sha256=qVSLVJLLJYzoC6aj6y9MFrzZvAwc4_OgxU9DlkQnZ4M,86
|
|
8
8
|
sqlsaber/cli/auth.py,sha256=jTsRgbmlGPlASSuIKmdjjwfqtKvjfKd_cTYxX0-QqaQ,7400
|
|
9
|
-
sqlsaber/cli/commands.py,sha256=
|
|
9
|
+
sqlsaber/cli/commands.py,sha256=mjLG9i1bXf0TEroxkIxq5O7Hhjufz3Ad72cyJz7vE1k,8128
|
|
10
10
|
sqlsaber/cli/completers.py,sha256=HsUPjaZweLSeYCWkAcgMl8FylQ1xjWBWYTEL_9F6xfU,6430
|
|
11
|
-
sqlsaber/cli/database.py,sha256=
|
|
11
|
+
sqlsaber/cli/database.py,sha256=JKtHSN-BFzBa14REf0phFVQB7d67m1M5FFaD8N6DdrY,12966
|
|
12
12
|
sqlsaber/cli/display.py,sha256=wa7BjTBwXwqLT145Q1AEL0C28pQJTrvDN10mnFMjqsg,8554
|
|
13
|
-
sqlsaber/cli/interactive.py,sha256=
|
|
13
|
+
sqlsaber/cli/interactive.py,sha256=suTZ-EvbaB21BsFsRc4MkjM89lZ2iJlYH4G1iYjW7PI,13213
|
|
14
14
|
sqlsaber/cli/memory.py,sha256=OufHFJFwV0_GGn7LvKRTJikkWhV1IwNIUDOxFPHXOaQ,7794
|
|
15
15
|
sqlsaber/cli/models.py,sha256=ZewtwGQwhd9b-yxBAPKePolvI1qQG-EkmeWAGMqtWNQ,8986
|
|
16
16
|
sqlsaber/cli/streaming.py,sha256=WNqBYYbWtL5CNQkRg5YWhYpWKI8qz7JmqneB2DXTOHY,5259
|
|
17
|
-
sqlsaber/cli/threads.py,sha256=
|
|
17
|
+
sqlsaber/cli/threads.py,sha256=XUnLcCUe2wa_85IKdKmryqfiHTQu_IylET2Qo8oy1nk,11324
|
|
18
18
|
sqlsaber/config/__init__.py,sha256=olwC45k8Nc61yK0WmPUk7XHdbsZH9HuUAbwnmKe3IgA,100
|
|
19
19
|
sqlsaber/config/api_keys.py,sha256=RqWQCko1tY7sES7YOlexgBH5Hd5ne_kGXHdBDNqcV2U,3649
|
|
20
20
|
sqlsaber/config/auth.py,sha256=b5qB2h1doXyO9Bn8z0CcL8LAR2jF431gGXBGKLgTmtQ,2756
|
|
@@ -40,8 +40,8 @@ sqlsaber/tools/enums.py,sha256=CH32mL-0k9ZA18911xLpNtsgpV6tB85TktMj6uqGz54,411
|
|
|
40
40
|
sqlsaber/tools/instructions.py,sha256=X-x8maVkkyi16b6Tl0hcAFgjiYceZaSwyWTfmrvx8U8,9024
|
|
41
41
|
sqlsaber/tools/registry.py,sha256=HWOQMsNIdL4XZS6TeNUyrL-5KoSDH6PHsWd3X66o-18,3211
|
|
42
42
|
sqlsaber/tools/sql_tools.py,sha256=hM6tKqW5MDhFUt6MesoqhTUqIpq_5baIIDoN1MjDCXY,9647
|
|
43
|
-
sqlsaber-0.
|
|
44
|
-
sqlsaber-0.
|
|
45
|
-
sqlsaber-0.
|
|
46
|
-
sqlsaber-0.
|
|
47
|
-
sqlsaber-0.
|
|
43
|
+
sqlsaber-0.20.0.dist-info/METADATA,sha256=LJgfLWIWvI8ZgeLjZMLakKCmHn4EywL0MS9XeKLy63E,6178
|
|
44
|
+
sqlsaber-0.20.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
45
|
+
sqlsaber-0.20.0.dist-info/entry_points.txt,sha256=qEbOB7OffXPFgyJc7qEIJlMEX5RN9xdzLmWZa91zCQQ,162
|
|
46
|
+
sqlsaber-0.20.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
47
|
+
sqlsaber-0.20.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|