sqlsaber 0.8.2__py3-none-any.whl → 0.9.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/auth.py +9 -10
- sqlsaber/cli/commands.py +62 -53
- sqlsaber/cli/database.py +88 -62
- sqlsaber/cli/memory.py +83 -73
- sqlsaber/cli/models.py +15 -15
- {sqlsaber-0.8.2.dist-info → sqlsaber-0.9.0.dist-info}/METADATA +19 -13
- {sqlsaber-0.8.2.dist-info → sqlsaber-0.9.0.dist-info}/RECORD +10 -10
- {sqlsaber-0.8.2.dist-info → sqlsaber-0.9.0.dist-info}/entry_points.txt +0 -1
- {sqlsaber-0.8.2.dist-info → sqlsaber-0.9.0.dist-info}/WHEEL +0 -0
- {sqlsaber-0.8.2.dist-info → sqlsaber-0.9.0.dist-info}/licenses/LICENSE +0 -0
sqlsaber/cli/auth.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Authentication CLI commands."""
|
|
2
2
|
|
|
3
3
|
import questionary
|
|
4
|
-
import
|
|
4
|
+
import cyclopts
|
|
5
5
|
from rich.console import Console
|
|
6
6
|
|
|
7
7
|
from sqlsaber.config.auth import AuthConfigManager, AuthMethod
|
|
@@ -12,15 +12,14 @@ console = Console()
|
|
|
12
12
|
config_manager = AuthConfigManager()
|
|
13
13
|
|
|
14
14
|
# Create the authentication management CLI app
|
|
15
|
-
auth_app =
|
|
15
|
+
auth_app = cyclopts.App(
|
|
16
16
|
name="auth",
|
|
17
17
|
help="Manage authentication configuration",
|
|
18
|
-
add_completion=True,
|
|
19
18
|
)
|
|
20
19
|
|
|
21
20
|
|
|
22
|
-
@auth_app.command
|
|
23
|
-
def
|
|
21
|
+
@auth_app.command
|
|
22
|
+
def setup():
|
|
24
23
|
"""Configure authentication method for SQLSaber."""
|
|
25
24
|
console.print("\n[bold]SQLSaber Authentication Setup[/bold]\n")
|
|
26
25
|
|
|
@@ -79,8 +78,8 @@ def setup_auth():
|
|
|
79
78
|
)
|
|
80
79
|
|
|
81
80
|
|
|
82
|
-
@auth_app.command
|
|
83
|
-
def
|
|
81
|
+
@auth_app.command
|
|
82
|
+
def status():
|
|
84
83
|
"""Show current authentication configuration."""
|
|
85
84
|
auth_method = config_manager.get_auth_method()
|
|
86
85
|
|
|
@@ -104,8 +103,8 @@ def show_auth_status():
|
|
|
104
103
|
console.print("[yellow]OAuth token missing or expired[/yellow]")
|
|
105
104
|
|
|
106
105
|
|
|
107
|
-
@auth_app.command
|
|
108
|
-
def
|
|
106
|
+
@auth_app.command
|
|
107
|
+
def reset():
|
|
109
108
|
"""Reset authentication configuration."""
|
|
110
109
|
if not config_manager.has_auth_configured():
|
|
111
110
|
console.print("[yellow]No authentication configuration to reset.[/yellow]")
|
|
@@ -137,6 +136,6 @@ def reset_auth():
|
|
|
137
136
|
console.print("Reset cancelled.")
|
|
138
137
|
|
|
139
138
|
|
|
140
|
-
def create_auth_app() ->
|
|
139
|
+
def create_auth_app() -> cyclopts.App:
|
|
141
140
|
"""Return the authentication management CLI app."""
|
|
142
141
|
return auth_app
|
sqlsaber/cli/commands.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"""CLI command definitions and handlers."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import sys
|
|
4
5
|
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
5
7
|
|
|
6
|
-
import
|
|
8
|
+
import cyclopts
|
|
7
9
|
from rich.console import Console
|
|
8
10
|
|
|
9
11
|
from sqlsaber.agents.anthropic import AnthropicSQLAgent
|
|
@@ -16,10 +18,18 @@ from sqlsaber.cli.streaming import StreamingQueryHandler
|
|
|
16
18
|
from sqlsaber.config.database import DatabaseConfigManager
|
|
17
19
|
from sqlsaber.database.connection import DatabaseConnection
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
|
|
22
|
+
class CLIError(Exception):
|
|
23
|
+
"""Exception raised for CLI errors that should result in exit."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, message: str, exit_code: int = 1):
|
|
26
|
+
super().__init__(message)
|
|
27
|
+
self.exit_code = exit_code
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
app = cyclopts.App(
|
|
20
31
|
name="sqlsaber",
|
|
21
32
|
help="SQLSaber - Use the agent Luke!\n\nSQL assistant for your database",
|
|
22
|
-
add_completion=True,
|
|
23
33
|
)
|
|
24
34
|
|
|
25
35
|
|
|
@@ -27,40 +37,49 @@ console = Console()
|
|
|
27
37
|
config_manager = DatabaseConfigManager()
|
|
28
38
|
|
|
29
39
|
|
|
30
|
-
@app.
|
|
31
|
-
def
|
|
32
|
-
database:
|
|
33
|
-
None,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
@app.meta.default
|
|
41
|
+
def meta_handler(
|
|
42
|
+
database: Annotated[
|
|
43
|
+
str | None,
|
|
44
|
+
cyclopts.Parameter(
|
|
45
|
+
["--database", "-d"],
|
|
46
|
+
help="Database connection name (uses default if not specified)",
|
|
47
|
+
),
|
|
48
|
+
] = None,
|
|
38
49
|
):
|
|
39
50
|
"""
|
|
40
51
|
Query your database using natural language.
|
|
41
52
|
|
|
42
53
|
Examples:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
saber # Start interactive mode
|
|
55
|
+
saber "show me all users" # Run a single query with default database
|
|
56
|
+
saber -d mydb "show me users" # Run a query with specific database
|
|
46
57
|
"""
|
|
47
|
-
|
|
58
|
+
# Store database in app context for commands to access
|
|
59
|
+
app.meta["database"] = database
|
|
48
60
|
|
|
49
61
|
|
|
50
|
-
@app.
|
|
62
|
+
@app.default
|
|
51
63
|
def query(
|
|
52
|
-
query_text:
|
|
53
|
-
None,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
query_text: Annotated[
|
|
65
|
+
str | None,
|
|
66
|
+
cyclopts.Parameter(
|
|
67
|
+
help="SQL query in natural language (if not provided, starts interactive mode)",
|
|
68
|
+
),
|
|
69
|
+
] = None,
|
|
70
|
+
database: Annotated[
|
|
71
|
+
str | None,
|
|
72
|
+
cyclopts.Parameter(
|
|
73
|
+
["--database", "-d"],
|
|
74
|
+
help="Database connection name (uses default if not specified)",
|
|
75
|
+
),
|
|
76
|
+
] = None,
|
|
62
77
|
):
|
|
63
|
-
"""Run a query against the database or start interactive mode.
|
|
78
|
+
"""Run a query against the database or start interactive mode.
|
|
79
|
+
|
|
80
|
+
When called without arguments, starts interactive mode.
|
|
81
|
+
When called with a query string, executes that query and exits.
|
|
82
|
+
"""
|
|
64
83
|
|
|
65
84
|
async def run_session():
|
|
66
85
|
# Get database configuration or handle direct CSV file
|
|
@@ -69,35 +88,24 @@ def query(
|
|
|
69
88
|
if database.endswith(".csv"):
|
|
70
89
|
csv_path = Path(database).expanduser().resolve()
|
|
71
90
|
if not csv_path.exists():
|
|
72
|
-
|
|
73
|
-
f"[bold red]Error:[/bold red] CSV file '{database}' not found."
|
|
74
|
-
)
|
|
75
|
-
raise typer.Exit(1)
|
|
91
|
+
raise CLIError(f"CSV file '{database}' not found.")
|
|
76
92
|
connection_string = f"csv:///{csv_path}"
|
|
77
93
|
db_name = csv_path.stem
|
|
78
94
|
else:
|
|
79
95
|
# Look up configured database connection
|
|
80
96
|
db_config = config_manager.get_database(database)
|
|
81
97
|
if not db_config:
|
|
82
|
-
|
|
83
|
-
f"
|
|
84
|
-
)
|
|
85
|
-
console.print(
|
|
86
|
-
"Use 'sqlsaber db list' to see available connections."
|
|
98
|
+
raise CLIError(
|
|
99
|
+
f"Database connection '{database}' not found. Use 'sqlsaber db list' to see available connections."
|
|
87
100
|
)
|
|
88
|
-
raise typer.Exit(1)
|
|
89
101
|
connection_string = db_config.to_connection_string()
|
|
90
102
|
db_name = db_config.name
|
|
91
103
|
else:
|
|
92
104
|
db_config = config_manager.get_default_database()
|
|
93
105
|
if not db_config:
|
|
94
|
-
|
|
95
|
-
"
|
|
96
|
-
)
|
|
97
|
-
console.print(
|
|
98
|
-
"Use 'sqlsaber db add <name>' to add a database connection."
|
|
106
|
+
raise CLIError(
|
|
107
|
+
"No database connections configured. Use 'sqlsaber db add <name>' to add a database connection."
|
|
99
108
|
)
|
|
100
|
-
raise typer.Exit(1)
|
|
101
109
|
connection_string = db_config.to_connection_string()
|
|
102
110
|
db_name = db_config.name
|
|
103
111
|
|
|
@@ -105,10 +113,7 @@ def query(
|
|
|
105
113
|
try:
|
|
106
114
|
db_conn = DatabaseConnection(connection_string)
|
|
107
115
|
except Exception as e:
|
|
108
|
-
|
|
109
|
-
f"[bold red]Error creating database connection:[/bold red] {e}"
|
|
110
|
-
)
|
|
111
|
-
raise typer.Exit(1)
|
|
116
|
+
raise CLIError(f"Error creating database connection: {e}")
|
|
112
117
|
|
|
113
118
|
# Create agent instance with database name for memory context
|
|
114
119
|
agent = AnthropicSQLAgent(db_conn, db_name)
|
|
@@ -132,25 +137,29 @@ def query(
|
|
|
132
137
|
await db_conn.close()
|
|
133
138
|
console.print("\n[green]Goodbye![/green]")
|
|
134
139
|
|
|
135
|
-
# Run the async function
|
|
136
|
-
|
|
140
|
+
# Run the async function with proper error handling
|
|
141
|
+
try:
|
|
142
|
+
asyncio.run(run_session())
|
|
143
|
+
except CLIError as e:
|
|
144
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
145
|
+
sys.exit(e.exit_code)
|
|
137
146
|
|
|
138
147
|
|
|
139
148
|
# Add authentication management commands
|
|
140
149
|
auth_app = create_auth_app()
|
|
141
|
-
app.
|
|
150
|
+
app.command(auth_app, name="auth")
|
|
142
151
|
|
|
143
152
|
# Add database management commands after main callback is defined
|
|
144
153
|
db_app = create_db_app()
|
|
145
|
-
app.
|
|
154
|
+
app.command(db_app, name="db")
|
|
146
155
|
|
|
147
156
|
# Add memory management commands
|
|
148
157
|
memory_app = create_memory_app()
|
|
149
|
-
app.
|
|
158
|
+
app.command(memory_app, name="memory")
|
|
150
159
|
|
|
151
160
|
# Add model management commands
|
|
152
161
|
models_app = create_models_app()
|
|
153
|
-
app.
|
|
162
|
+
app.command(models_app, name="models")
|
|
154
163
|
|
|
155
164
|
|
|
156
165
|
def main():
|
sqlsaber/cli/database.py
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import getpass
|
|
5
|
+
import sys
|
|
5
6
|
from pathlib import Path
|
|
7
|
+
from typing import Annotated
|
|
6
8
|
|
|
7
9
|
import questionary
|
|
8
|
-
import
|
|
10
|
+
import cyclopts
|
|
9
11
|
from rich.console import Console
|
|
10
12
|
from rich.table import Table
|
|
11
13
|
|
|
@@ -17,45 +19,64 @@ console = Console()
|
|
|
17
19
|
config_manager = DatabaseConfigManager()
|
|
18
20
|
|
|
19
21
|
# Create the database management CLI app
|
|
20
|
-
db_app =
|
|
22
|
+
db_app = cyclopts.App(
|
|
21
23
|
name="db",
|
|
22
24
|
help="Manage database connections",
|
|
23
|
-
add_completion=True,
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
@db_app.command
|
|
28
|
-
def
|
|
29
|
-
name: str
|
|
30
|
-
type:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
None,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
28
|
+
@db_app.command
|
|
29
|
+
def add(
|
|
30
|
+
name: Annotated[str, cyclopts.Parameter(help="Name for the database connection")],
|
|
31
|
+
type: Annotated[
|
|
32
|
+
str,
|
|
33
|
+
cyclopts.Parameter(
|
|
34
|
+
["--type", "-t"],
|
|
35
|
+
help="Database type (postgresql, mysql, sqlite)",
|
|
36
|
+
),
|
|
37
|
+
] = "postgresql",
|
|
38
|
+
host: Annotated[
|
|
39
|
+
str | None,
|
|
40
|
+
cyclopts.Parameter(["--host", "-h"], help="Database host"),
|
|
41
|
+
] = None,
|
|
42
|
+
port: Annotated[
|
|
43
|
+
int | None,
|
|
44
|
+
cyclopts.Parameter(["--port", "-p"], help="Database port"),
|
|
45
|
+
] = None,
|
|
46
|
+
database: Annotated[
|
|
47
|
+
str | None,
|
|
48
|
+
cyclopts.Parameter(["--database", "--db"], help="Database name"),
|
|
49
|
+
] = None,
|
|
50
|
+
username: Annotated[
|
|
51
|
+
str | None,
|
|
52
|
+
cyclopts.Parameter(["--username", "-u"], help="Username"),
|
|
53
|
+
] = None,
|
|
54
|
+
ssl_mode: Annotated[
|
|
55
|
+
str | None,
|
|
56
|
+
cyclopts.Parameter(
|
|
57
|
+
["--ssl-mode"],
|
|
58
|
+
help="SSL mode (disable, allow, prefer, require, verify-ca, verify-full for PostgreSQL; DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY for MySQL)",
|
|
59
|
+
),
|
|
60
|
+
] = None,
|
|
61
|
+
ssl_ca: Annotated[
|
|
62
|
+
str | None,
|
|
63
|
+
cyclopts.Parameter(["--ssl-ca"], help="SSL CA certificate file path"),
|
|
64
|
+
] = None,
|
|
65
|
+
ssl_cert: Annotated[
|
|
66
|
+
str | None,
|
|
67
|
+
cyclopts.Parameter(["--ssl-cert"], help="SSL client certificate file path"),
|
|
68
|
+
] = None,
|
|
69
|
+
ssl_key: Annotated[
|
|
70
|
+
str | None,
|
|
71
|
+
cyclopts.Parameter(["--ssl-key"], help="SSL client private key file path"),
|
|
72
|
+
] = None,
|
|
73
|
+
interactive: Annotated[
|
|
74
|
+
bool,
|
|
75
|
+
cyclopts.Parameter(
|
|
76
|
+
["--interactive", "--no-interactive"],
|
|
77
|
+
help="Use interactive mode",
|
|
78
|
+
),
|
|
79
|
+
] = True,
|
|
59
80
|
):
|
|
60
81
|
"""Add a new database connection."""
|
|
61
82
|
|
|
@@ -157,7 +178,7 @@ def add_database(
|
|
|
157
178
|
console.print(
|
|
158
179
|
"[bold red]Error:[/bold red] Database file path is required for SQLite"
|
|
159
180
|
)
|
|
160
|
-
|
|
181
|
+
sys.exit(1)
|
|
161
182
|
host = "localhost"
|
|
162
183
|
port = 0
|
|
163
184
|
username = "sqlite"
|
|
@@ -167,7 +188,7 @@ def add_database(
|
|
|
167
188
|
console.print(
|
|
168
189
|
"[bold red]Error:[/bold red] Host, database, and username are required"
|
|
169
190
|
)
|
|
170
|
-
|
|
191
|
+
sys.exit(1)
|
|
171
192
|
|
|
172
193
|
if port is None:
|
|
173
194
|
port = 5432 if type == "postgresql" else 3306
|
|
@@ -210,11 +231,11 @@ def add_database(
|
|
|
210
231
|
|
|
211
232
|
except Exception as e:
|
|
212
233
|
console.print(f"[bold red]Error adding database:[/bold red] {e}")
|
|
213
|
-
|
|
234
|
+
sys.exit(1)
|
|
214
235
|
|
|
215
236
|
|
|
216
|
-
@db_app.command
|
|
217
|
-
def
|
|
237
|
+
@db_app.command
|
|
238
|
+
def list():
|
|
218
239
|
"""List all configured database connections."""
|
|
219
240
|
databases = config_manager.list_databases()
|
|
220
241
|
default_name = config_manager.get_default_name()
|
|
@@ -260,16 +281,18 @@ def list_databases():
|
|
|
260
281
|
console.print(table)
|
|
261
282
|
|
|
262
283
|
|
|
263
|
-
@db_app.command
|
|
264
|
-
def
|
|
265
|
-
name:
|
|
284
|
+
@db_app.command
|
|
285
|
+
def remove(
|
|
286
|
+
name: Annotated[
|
|
287
|
+
str, cyclopts.Parameter(help="Name of the database connection to remove")
|
|
288
|
+
],
|
|
266
289
|
):
|
|
267
290
|
"""Remove a database connection."""
|
|
268
291
|
if not config_manager.get_database(name):
|
|
269
292
|
console.print(
|
|
270
293
|
f"[bold red]Error:[/bold red] Database connection '{name}' not found"
|
|
271
294
|
)
|
|
272
|
-
|
|
295
|
+
sys.exit(1)
|
|
273
296
|
|
|
274
297
|
if questionary.confirm(
|
|
275
298
|
f"Are you sure you want to remove database connection '{name}'?"
|
|
@@ -282,37 +305,40 @@ def remove_database(
|
|
|
282
305
|
console.print(
|
|
283
306
|
f"[bold red]Error:[/bold red] Failed to remove database connection '{name}'"
|
|
284
307
|
)
|
|
285
|
-
|
|
308
|
+
sys.exit(1)
|
|
286
309
|
else:
|
|
287
310
|
console.print("Operation cancelled")
|
|
288
311
|
|
|
289
312
|
|
|
290
|
-
@db_app.command
|
|
291
|
-
def
|
|
292
|
-
name:
|
|
293
|
-
|
|
294
|
-
|
|
313
|
+
@db_app.command
|
|
314
|
+
def set_default(
|
|
315
|
+
name: Annotated[
|
|
316
|
+
str,
|
|
317
|
+
cyclopts.Parameter(help="Name of the database connection to set as default"),
|
|
318
|
+
],
|
|
295
319
|
):
|
|
296
320
|
"""Set the default database connection."""
|
|
297
321
|
if not config_manager.get_database(name):
|
|
298
322
|
console.print(
|
|
299
323
|
f"[bold red]Error:[/bold red] Database connection '{name}' not found"
|
|
300
324
|
)
|
|
301
|
-
|
|
325
|
+
sys.exit(1)
|
|
302
326
|
|
|
303
327
|
if config_manager.set_default_database(name):
|
|
304
328
|
console.print(f"[green]Successfully set '{name}' as default database[/green]")
|
|
305
329
|
else:
|
|
306
330
|
console.print(f"[bold red]Error:[/bold red] Failed to set '{name}' as default")
|
|
307
|
-
|
|
331
|
+
sys.exit(1)
|
|
308
332
|
|
|
309
333
|
|
|
310
|
-
@db_app.command
|
|
311
|
-
def
|
|
312
|
-
name:
|
|
313
|
-
None,
|
|
314
|
-
|
|
315
|
-
|
|
334
|
+
@db_app.command
|
|
335
|
+
def test(
|
|
336
|
+
name: Annotated[
|
|
337
|
+
str | None,
|
|
338
|
+
cyclopts.Parameter(
|
|
339
|
+
help="Name of the database connection to test (uses default if not specified)",
|
|
340
|
+
),
|
|
341
|
+
] = None,
|
|
316
342
|
):
|
|
317
343
|
"""Test a database connection."""
|
|
318
344
|
|
|
@@ -323,7 +349,7 @@ def test_database(
|
|
|
323
349
|
console.print(
|
|
324
350
|
f"[bold red]Error:[/bold red] Database connection '{name}' not found"
|
|
325
351
|
)
|
|
326
|
-
|
|
352
|
+
sys.exit(1)
|
|
327
353
|
else:
|
|
328
354
|
db_config = config_manager.get_default_database()
|
|
329
355
|
if not db_config:
|
|
@@ -333,7 +359,7 @@ def test_database(
|
|
|
333
359
|
console.print(
|
|
334
360
|
"Use 'sqlsaber db add <name>' to add a database connection"
|
|
335
361
|
)
|
|
336
|
-
|
|
362
|
+
sys.exit(1)
|
|
337
363
|
|
|
338
364
|
console.print(f"[blue]Testing connection to '{db_config.name}'...[/blue]")
|
|
339
365
|
|
|
@@ -351,11 +377,11 @@ def test_database(
|
|
|
351
377
|
|
|
352
378
|
except Exception as e:
|
|
353
379
|
console.print(f"[bold red]✗ Connection failed:[/bold red] {e}")
|
|
354
|
-
|
|
380
|
+
sys.exit(1)
|
|
355
381
|
|
|
356
382
|
asyncio.run(test_connection())
|
|
357
383
|
|
|
358
384
|
|
|
359
|
-
def create_db_app() ->
|
|
385
|
+
def create_db_app() -> cyclopts.App:
|
|
360
386
|
"""Return the database management CLI app."""
|
|
361
387
|
return db_app
|
sqlsaber/cli/memory.py
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"""Memory management CLI commands."""
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import cyclopts
|
|
7
|
+
import questionary
|
|
4
8
|
from rich.console import Console
|
|
5
9
|
from rich.table import Table
|
|
6
10
|
|
|
@@ -13,10 +17,9 @@ config_manager = DatabaseConfigManager()
|
|
|
13
17
|
memory_manager = MemoryManager()
|
|
14
18
|
|
|
15
19
|
# Create the memory management CLI app
|
|
16
|
-
memory_app =
|
|
20
|
+
memory_app = cyclopts.App(
|
|
17
21
|
name="memory",
|
|
18
22
|
help="Manage database-specific memories",
|
|
19
|
-
add_completion=True,
|
|
20
23
|
)
|
|
21
24
|
|
|
22
25
|
|
|
@@ -28,7 +31,7 @@ def _get_database_name(database: str | None = None) -> str:
|
|
|
28
31
|
console.print(
|
|
29
32
|
f"[bold red]Error:[/bold red] Database connection '{database}' not found."
|
|
30
33
|
)
|
|
31
|
-
|
|
34
|
+
sys.exit(1)
|
|
32
35
|
return database
|
|
33
36
|
else:
|
|
34
37
|
db_config = config_manager.get_default_database()
|
|
@@ -37,19 +40,20 @@ def _get_database_name(database: str | None = None) -> str:
|
|
|
37
40
|
"[bold red]Error:[/bold red] No database connections configured."
|
|
38
41
|
)
|
|
39
42
|
console.print("Use 'sqlsaber db add <name>' to add a database connection.")
|
|
40
|
-
|
|
43
|
+
sys.exit(1)
|
|
41
44
|
return db_config.name
|
|
42
45
|
|
|
43
46
|
|
|
44
|
-
@memory_app.command
|
|
45
|
-
def
|
|
46
|
-
content: str
|
|
47
|
-
database:
|
|
48
|
-
None,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
@memory_app.command
|
|
48
|
+
def add(
|
|
49
|
+
content: Annotated[str, cyclopts.Parameter(help="Memory content to add")],
|
|
50
|
+
database: Annotated[
|
|
51
|
+
str | None,
|
|
52
|
+
cyclopts.Parameter(
|
|
53
|
+
["--database", "-d"],
|
|
54
|
+
help="Database connection name (uses default if not specified)",
|
|
55
|
+
),
|
|
56
|
+
] = None,
|
|
53
57
|
):
|
|
54
58
|
"""Add a new memory for the specified database."""
|
|
55
59
|
database_name = _get_database_name(database)
|
|
@@ -61,17 +65,18 @@ def add_memory(
|
|
|
61
65
|
console.print(f"[dim]Content:[/dim] {memory.content}")
|
|
62
66
|
except Exception as e:
|
|
63
67
|
console.print(f"[bold red]Error adding memory:[/bold red] {e}")
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@memory_app.command
|
|
68
|
-
def
|
|
69
|
-
database:
|
|
70
|
-
None,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@memory_app.command
|
|
72
|
+
def list(
|
|
73
|
+
database: Annotated[
|
|
74
|
+
str | None,
|
|
75
|
+
cyclopts.Parameter(
|
|
76
|
+
["--database", "-d"],
|
|
77
|
+
help="Database connection name (uses default if not specified)",
|
|
78
|
+
),
|
|
79
|
+
] = None,
|
|
75
80
|
):
|
|
76
81
|
"""List all memories for the specified database."""
|
|
77
82
|
database_name = _get_database_name(database)
|
|
@@ -102,15 +107,16 @@ def list_memories(
|
|
|
102
107
|
console.print(f"\n[dim]Total memories: {len(memories)}[/dim]")
|
|
103
108
|
|
|
104
109
|
|
|
105
|
-
@memory_app.command
|
|
106
|
-
def
|
|
107
|
-
memory_id: str
|
|
108
|
-
database:
|
|
109
|
-
None,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
@memory_app.command
|
|
111
|
+
def show(
|
|
112
|
+
memory_id: Annotated[str, cyclopts.Parameter(help="Memory ID to show")],
|
|
113
|
+
database: Annotated[
|
|
114
|
+
str | None,
|
|
115
|
+
cyclopts.Parameter(
|
|
116
|
+
["--database", "-d"],
|
|
117
|
+
help="Database connection name (uses default if not specified)",
|
|
118
|
+
),
|
|
119
|
+
] = None,
|
|
114
120
|
):
|
|
115
121
|
"""Show the full content of a specific memory."""
|
|
116
122
|
database_name = _get_database_name(database)
|
|
@@ -121,7 +127,7 @@ def show_memory(
|
|
|
121
127
|
console.print(
|
|
122
128
|
f"[bold red]Error:[/bold red] Memory with ID '{memory_id}' not found for database '{database_name}'"
|
|
123
129
|
)
|
|
124
|
-
|
|
130
|
+
sys.exit(1)
|
|
125
131
|
|
|
126
132
|
console.print(f"[bold]Memory ID:[/bold] {memory.id}")
|
|
127
133
|
console.print(f"[bold]Database:[/bold] {memory.database}")
|
|
@@ -130,15 +136,16 @@ def show_memory(
|
|
|
130
136
|
console.print(f"{memory.content}")
|
|
131
137
|
|
|
132
138
|
|
|
133
|
-
@memory_app.command
|
|
134
|
-
def
|
|
135
|
-
memory_id: str
|
|
136
|
-
database:
|
|
137
|
-
None,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
@memory_app.command
|
|
140
|
+
def remove(
|
|
141
|
+
memory_id: Annotated[str, cyclopts.Parameter(help="Memory ID to remove")],
|
|
142
|
+
database: Annotated[
|
|
143
|
+
str | None,
|
|
144
|
+
cyclopts.Parameter(
|
|
145
|
+
["--database", "-d"],
|
|
146
|
+
help="Database connection name (uses default if not specified)",
|
|
147
|
+
),
|
|
148
|
+
] = None,
|
|
142
149
|
):
|
|
143
150
|
"""Remove a specific memory by ID."""
|
|
144
151
|
database_name = _get_database_name(database)
|
|
@@ -149,7 +156,7 @@ def remove_memory(
|
|
|
149
156
|
console.print(
|
|
150
157
|
f"[bold red]Error:[/bold red] Memory with ID '{memory_id}' not found for database '{database_name}'"
|
|
151
158
|
)
|
|
152
|
-
|
|
159
|
+
sys.exit(1)
|
|
153
160
|
|
|
154
161
|
# Show memory content before removal
|
|
155
162
|
console.print("[yellow]Removing memory:[/yellow]")
|
|
@@ -163,23 +170,25 @@ def remove_memory(
|
|
|
163
170
|
console.print(
|
|
164
171
|
f"[bold red]Error:[/bold red] Failed to remove memory '{memory_id}'"
|
|
165
172
|
)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
@memory_app.command
|
|
170
|
-
def
|
|
171
|
-
database:
|
|
172
|
-
None,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
173
|
+
sys.exit(1)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@memory_app.command
|
|
177
|
+
def clear(
|
|
178
|
+
database: Annotated[
|
|
179
|
+
str | None,
|
|
180
|
+
cyclopts.Parameter(
|
|
181
|
+
["--database", "-d"],
|
|
182
|
+
help="Database connection name (uses default if not specified)",
|
|
183
|
+
),
|
|
184
|
+
] = None,
|
|
185
|
+
force: Annotated[
|
|
186
|
+
bool,
|
|
187
|
+
cyclopts.Parameter(
|
|
188
|
+
["--force", "-f"],
|
|
189
|
+
help="Skip confirmation prompt",
|
|
190
|
+
),
|
|
191
|
+
] = False,
|
|
183
192
|
):
|
|
184
193
|
"""Clear all memories for the specified database."""
|
|
185
194
|
database_name = _get_database_name(database)
|
|
@@ -198,8 +207,8 @@ def clear_memories(
|
|
|
198
207
|
console.print(
|
|
199
208
|
f"[yellow]About to clear {memories_count} memories for database '{database_name}'[/yellow]"
|
|
200
209
|
)
|
|
201
|
-
|
|
202
|
-
if not confirm:
|
|
210
|
+
|
|
211
|
+
if not questionary.confirm("Are you sure you want to proceed?").ask():
|
|
203
212
|
console.print("Operation cancelled")
|
|
204
213
|
return
|
|
205
214
|
|
|
@@ -209,14 +218,15 @@ def clear_memories(
|
|
|
209
218
|
)
|
|
210
219
|
|
|
211
220
|
|
|
212
|
-
@memory_app.command
|
|
213
|
-
def
|
|
214
|
-
database:
|
|
215
|
-
None,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
221
|
+
@memory_app.command
|
|
222
|
+
def summary(
|
|
223
|
+
database: Annotated[
|
|
224
|
+
str | None,
|
|
225
|
+
cyclopts.Parameter(
|
|
226
|
+
["--database", "-d"],
|
|
227
|
+
help="Database connection name (uses default if not specified)",
|
|
228
|
+
),
|
|
229
|
+
] = None,
|
|
220
230
|
):
|
|
221
231
|
"""Show memory summary for the specified database."""
|
|
222
232
|
database_name = _get_database_name(database)
|
|
@@ -232,6 +242,6 @@ def memory_summary(
|
|
|
232
242
|
console.print(f"[dim]{memory['timestamp']}[/dim] - {memory['content']}")
|
|
233
243
|
|
|
234
244
|
|
|
235
|
-
def create_memory_app() ->
|
|
245
|
+
def create_memory_app() -> cyclopts.App:
|
|
236
246
|
"""Return the memory management CLI app."""
|
|
237
247
|
return memory_app
|
sqlsaber/cli/models.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""Model management CLI commands."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import sys
|
|
4
5
|
|
|
5
6
|
import httpx
|
|
6
7
|
import questionary
|
|
7
|
-
import
|
|
8
|
+
import cyclopts
|
|
8
9
|
from rich.console import Console
|
|
9
10
|
from rich.table import Table
|
|
10
11
|
|
|
@@ -14,10 +15,9 @@ from sqlsaber.config.settings import Config
|
|
|
14
15
|
console = Console()
|
|
15
16
|
|
|
16
17
|
# Create the model management CLI app
|
|
17
|
-
models_app =
|
|
18
|
+
models_app = cyclopts.App(
|
|
18
19
|
name="models",
|
|
19
20
|
help="Select and manage models",
|
|
20
|
-
add_completion=True,
|
|
21
21
|
)
|
|
22
22
|
|
|
23
23
|
|
|
@@ -96,8 +96,8 @@ class ModelManager:
|
|
|
96
96
|
model_manager = ModelManager()
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
@models_app.command
|
|
100
|
-
def
|
|
99
|
+
@models_app.command
|
|
100
|
+
def list():
|
|
101
101
|
"""List available AI models."""
|
|
102
102
|
|
|
103
103
|
async def fetch_and_display():
|
|
@@ -146,8 +146,8 @@ def list_models():
|
|
|
146
146
|
asyncio.run(fetch_and_display())
|
|
147
147
|
|
|
148
148
|
|
|
149
|
-
@models_app.command
|
|
150
|
-
def
|
|
149
|
+
@models_app.command
|
|
150
|
+
def set():
|
|
151
151
|
"""Set the AI model to use."""
|
|
152
152
|
|
|
153
153
|
async def interactive_set():
|
|
@@ -156,7 +156,7 @@ def set_model():
|
|
|
156
156
|
|
|
157
157
|
if not models:
|
|
158
158
|
console.print("[red]Failed to fetch models. Cannot set model.[/red]")
|
|
159
|
-
|
|
159
|
+
sys.exit(1)
|
|
160
160
|
|
|
161
161
|
# Create choices for questionary
|
|
162
162
|
choices = []
|
|
@@ -190,22 +190,22 @@ def set_model():
|
|
|
190
190
|
console.print(f"[green]✓ Model set to: {selected_model}[/green]")
|
|
191
191
|
else:
|
|
192
192
|
console.print("[red]✗ Failed to set model[/red]")
|
|
193
|
-
|
|
193
|
+
sys.exit(1)
|
|
194
194
|
else:
|
|
195
195
|
console.print("[yellow]Operation cancelled[/yellow]")
|
|
196
196
|
|
|
197
197
|
asyncio.run(interactive_set())
|
|
198
198
|
|
|
199
199
|
|
|
200
|
-
@models_app.command
|
|
201
|
-
def
|
|
200
|
+
@models_app.command
|
|
201
|
+
def current():
|
|
202
202
|
"""Show the currently configured model."""
|
|
203
203
|
current = model_manager.get_current_model()
|
|
204
204
|
console.print(f"Current model: [cyan]{current}[/cyan]")
|
|
205
205
|
|
|
206
206
|
|
|
207
|
-
@models_app.command
|
|
208
|
-
def
|
|
207
|
+
@models_app.command
|
|
208
|
+
def reset():
|
|
209
209
|
"""Reset to the default model."""
|
|
210
210
|
|
|
211
211
|
async def interactive_reset():
|
|
@@ -218,13 +218,13 @@ def reset_model():
|
|
|
218
218
|
)
|
|
219
219
|
else:
|
|
220
220
|
console.print("[red]✗ Failed to reset model[/red]")
|
|
221
|
-
|
|
221
|
+
sys.exit(1)
|
|
222
222
|
else:
|
|
223
223
|
console.print("[yellow]Operation cancelled[/yellow]")
|
|
224
224
|
|
|
225
225
|
asyncio.run(interactive_reset())
|
|
226
226
|
|
|
227
227
|
|
|
228
|
-
def create_models_app() ->
|
|
228
|
+
def create_models_app() -> cyclopts.App:
|
|
229
229
|
"""Return the model management CLI app."""
|
|
230
230
|
return models_app
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlsaber
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: SQLSaber - Agentic SQL assistant like Claude Code
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -8,6 +8,7 @@ Requires-Dist: aiomysql>=0.2.0
|
|
|
8
8
|
Requires-Dist: aiosqlite>=0.21.0
|
|
9
9
|
Requires-Dist: anthropic>=0.54.0
|
|
10
10
|
Requires-Dist: asyncpg>=0.30.0
|
|
11
|
+
Requires-Dist: cyclopts>=3.22.1
|
|
11
12
|
Requires-Dist: fastmcp>=2.9.0
|
|
12
13
|
Requires-Dist: httpx>=0.28.1
|
|
13
14
|
Requires-Dist: keyring>=25.6.0
|
|
@@ -15,7 +16,6 @@ Requires-Dist: pandas>=2.0.0
|
|
|
15
16
|
Requires-Dist: platformdirs>=4.0.0
|
|
16
17
|
Requires-Dist: questionary>=2.1.0
|
|
17
18
|
Requires-Dist: rich>=13.7.0
|
|
18
|
-
Requires-Dist: typer>=0.16.0
|
|
19
19
|
Requires-Dist: uniplot>=0.21.2
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
|
|
@@ -126,7 +126,7 @@ saber memory list
|
|
|
126
126
|
Start an interactive session:
|
|
127
127
|
|
|
128
128
|
```bash
|
|
129
|
-
saber
|
|
129
|
+
saber
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
> You can also add memories in an interactive session by starting your message with the `#` sign
|
|
@@ -136,7 +136,7 @@ saber query
|
|
|
136
136
|
Execute a single natural language query:
|
|
137
137
|
|
|
138
138
|
```bash
|
|
139
|
-
saber
|
|
139
|
+
saber "show me all users created this month"
|
|
140
140
|
```
|
|
141
141
|
|
|
142
142
|
### Database Selection
|
|
@@ -144,33 +144,39 @@ saber query "show me all users created this month"
|
|
|
144
144
|
Use a specific database connection:
|
|
145
145
|
|
|
146
146
|
```bash
|
|
147
|
-
#
|
|
148
|
-
saber
|
|
147
|
+
# Interactive mode with specific database
|
|
148
|
+
saber -d mydb
|
|
149
|
+
|
|
150
|
+
# Single query with specific database
|
|
151
|
+
saber -d mydb "count all orders"
|
|
149
152
|
```
|
|
150
153
|
|
|
151
154
|
## Examples
|
|
152
155
|
|
|
153
156
|
```bash
|
|
154
157
|
# Show database schema
|
|
155
|
-
saber
|
|
158
|
+
saber "what tables are in my database?"
|
|
156
159
|
|
|
157
160
|
# Count records
|
|
158
|
-
saber
|
|
161
|
+
saber "how many active users do we have?"
|
|
159
162
|
|
|
160
163
|
# Complex queries with joins
|
|
161
|
-
saber
|
|
164
|
+
saber "show me orders with customer details for this week"
|
|
162
165
|
|
|
163
166
|
# Aggregations
|
|
164
|
-
saber
|
|
167
|
+
saber "what's the total revenue by product category?"
|
|
165
168
|
|
|
166
169
|
# Date filtering
|
|
167
|
-
saber
|
|
170
|
+
saber "list users who haven't logged in for 30 days"
|
|
168
171
|
|
|
169
172
|
# Data exploration
|
|
170
|
-
saber
|
|
173
|
+
saber "show me the distribution of customer ages"
|
|
171
174
|
|
|
172
175
|
# Business analytics
|
|
173
|
-
saber
|
|
176
|
+
saber "which products had the highest sales growth last quarter?"
|
|
177
|
+
|
|
178
|
+
# Start interactive mode
|
|
179
|
+
saber
|
|
174
180
|
```
|
|
175
181
|
|
|
176
182
|
## MCP Server Integration
|
|
@@ -6,14 +6,14 @@ sqlsaber/agents/base.py,sha256=Cl5ZV4dfgjslOAq8jbrnt5kX-NM_8QmjacWzb0hvbzs,10527
|
|
|
6
6
|
sqlsaber/agents/mcp.py,sha256=FKtXgDrPZ2-xqUYCw2baI5JzrWekXaC5fjkYW1_Mg50,827
|
|
7
7
|
sqlsaber/agents/streaming.py,sha256=LaSeMTlxuJFRArJVqDly5-_KgcePiCCKPKfMxfB4oGs,521
|
|
8
8
|
sqlsaber/cli/__init__.py,sha256=qVSLVJLLJYzoC6aj6y9MFrzZvAwc4_OgxU9DlkQnZ4M,86
|
|
9
|
-
sqlsaber/cli/auth.py,sha256=
|
|
10
|
-
sqlsaber/cli/commands.py,sha256=
|
|
9
|
+
sqlsaber/cli/auth.py,sha256=1yvawtS5NGj9dWMRZ8I5T6sqBiRqZpPsjEPrJSQBJAs,4979
|
|
10
|
+
sqlsaber/cli/commands.py,sha256=ABmw9U6HwSJI61d9iK0X3nsFKLMqfcQGQEYE9ZoZHlw,5494
|
|
11
11
|
sqlsaber/cli/completers.py,sha256=HsUPjaZweLSeYCWkAcgMl8FylQ1xjWBWYTEL_9F6xfU,6430
|
|
12
|
-
sqlsaber/cli/database.py,sha256=
|
|
12
|
+
sqlsaber/cli/database.py,sha256=tJ8rqGrafZpg3VgDmSiq7eZoPscoGAW3XLTYGoQw8LE,12910
|
|
13
13
|
sqlsaber/cli/display.py,sha256=wC-xYmmD21XyAkpRdMW7Ch2Mn5SlM1X34pbpka2ZIX8,10083
|
|
14
14
|
sqlsaber/cli/interactive.py,sha256=NjrhzXt6YK4WL17DMkJTY5dA1v9QMxLdkI1XgPKK8YA,8017
|
|
15
|
-
sqlsaber/cli/memory.py,sha256=
|
|
16
|
-
sqlsaber/cli/models.py,sha256=
|
|
15
|
+
sqlsaber/cli/memory.py,sha256=OufHFJFwV0_GGn7LvKRTJikkWhV1IwNIUDOxFPHXOaQ,7794
|
|
16
|
+
sqlsaber/cli/models.py,sha256=HByezaeKqj65GzB_FmWuugjkgTq2Pvab_mzNZnHxya0,7690
|
|
17
17
|
sqlsaber/cli/streaming.py,sha256=WfhFd5ntq2HStpJZwWJ0C5uyXKc3aU14eo8HdjzW1o0,3767
|
|
18
18
|
sqlsaber/clients/__init__.py,sha256=jcMoVsT92U6nQrfotCp1h0ggskJPAcgeYarqQl1qEBg,171
|
|
19
19
|
sqlsaber/clients/anthropic.py,sha256=umRmuzpmJdYO7hO3biAZXO9T_sb6Vv010o6zqn03is8,9947
|
|
@@ -39,8 +39,8 @@ sqlsaber/memory/storage.py,sha256=ne8szLlGj5NELheqLnI7zu21V8YS4rtpYGGC7tOmi-s,57
|
|
|
39
39
|
sqlsaber/models/__init__.py,sha256=RJ7p3WtuSwwpFQ1Iw4_DHV2zzCtHqIzsjJzxv8kUjUE,287
|
|
40
40
|
sqlsaber/models/events.py,sha256=89SXKb5GGpH01yTr2kPEBhzp9xv35RFIYuFdAZSIPoE,721
|
|
41
41
|
sqlsaber/models/types.py,sha256=w-zk81V2dtveuteej36_o1fDK3So428j3P2rAejU62U,862
|
|
42
|
-
sqlsaber-0.
|
|
43
|
-
sqlsaber-0.
|
|
44
|
-
sqlsaber-0.
|
|
45
|
-
sqlsaber-0.
|
|
46
|
-
sqlsaber-0.
|
|
42
|
+
sqlsaber-0.9.0.dist-info/METADATA,sha256=F2LyQDJS7tE1PfA2LhHJfjNU1qo35MbloSjvJyXgCs4,6023
|
|
43
|
+
sqlsaber-0.9.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
44
|
+
sqlsaber-0.9.0.dist-info/entry_points.txt,sha256=qEbOB7OffXPFgyJc7qEIJlMEX5RN9xdzLmWZa91zCQQ,162
|
|
45
|
+
sqlsaber-0.9.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
46
|
+
sqlsaber-0.9.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|