sqlsaber 0.8.2__tar.gz → 0.10.0__tar.gz
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.
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/.gitignore +2 -0
- sqlsaber-0.10.0/AGENT.md +24 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/CHANGELOG.md +21 -0
- sqlsaber-0.10.0/CLAUDE.md +22 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/PKG-INFO +26 -13
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/README.md +24 -11
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/pyproject.toml +2 -3
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/auth.py +9 -10
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/commands.py +83 -55
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/database.py +88 -62
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/memory.py +83 -73
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/models.py +15 -15
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/uv.lock +58 -3
- sqlsaber-0.8.2/CLAUDE.md +0 -5
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/.github/workflows/publish.yml +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/.python-version +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/LICENSE +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/pytest.ini +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/sqlsaber.svg +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/__init__.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/__main__.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/agents/__init__.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/agents/anthropic.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/agents/base.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/agents/mcp.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/agents/streaming.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/__init__.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/completers.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/display.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/interactive.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/streaming.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/clients/__init__.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/clients/anthropic.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/clients/base.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/clients/exceptions.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/clients/models.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/clients/streaming.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/config/__init__.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/config/api_keys.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/config/auth.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/config/database.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/config/oauth_flow.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/config/oauth_tokens.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/config/settings.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/database/__init__.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/database/connection.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/database/schema.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/mcp/__init__.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/mcp/mcp.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/memory/__init__.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/memory/manager.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/memory/storage.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/models/__init__.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/models/events.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/models/types.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/__init__.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/conftest.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_agents/test_anthropic_oauth.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_cli/__init__.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_cli/test_commands.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_clients/test_anthropic_client.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_clients/test_streaming.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_config/__init__.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_config/test_database.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_config/test_oauth.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_config/test_settings.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_database/__init__.py +0 -0
- {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_database/test_connection.py +0 -0
sqlsaber-0.10.0/AGENT.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# SQLSaber Agent Guide
|
|
2
|
+
|
|
3
|
+
## Commands
|
|
4
|
+
|
|
5
|
+
- **Run Python**: `uv run python`
|
|
6
|
+
- **Run tests**: `uv run python -m pytest`
|
|
7
|
+
- **Run single test**: `uv run python -m pytest tests/test_path/test_file.py::test_function`
|
|
8
|
+
- **Lint**: `uv run ruff check --fix`
|
|
9
|
+
- **Format**: `uv run ruff format`
|
|
10
|
+
|
|
11
|
+
## Architecture
|
|
12
|
+
|
|
13
|
+
- **CLI App**: Agentic SQL assistant for natural language to SQL
|
|
14
|
+
- **Core modules**: `agents/` (AI logic), `cli/` (commands), `clients/` (LLM clients), `database/` (connections), `mcp/` (Model Context Protocol server)
|
|
15
|
+
- **Database support**: PostgreSQL, SQLite, MySQL via asyncpg/aiosqlite/aiomysql
|
|
16
|
+
- **MCP integration**: Exposes tools via `sqlsaber-mcp` for Claude Code and other MCP clients
|
|
17
|
+
|
|
18
|
+
## Code Style
|
|
19
|
+
|
|
20
|
+
- **Imports**: stdlib → 3rd party → local, use relative imports within modules
|
|
21
|
+
- **Naming**: snake_case functions/vars, PascalCase classes, UPPER_SNAKE constants, `_private` methods
|
|
22
|
+
- **Types**: Always use modern type hints (3.12+), async functions for I/O
|
|
23
|
+
- **Errors**: Custom exception hierarchy from `LLMClientError`, use try/finally for cleanup
|
|
24
|
+
- **Docstrings**: Triple-quoted with Args/Returns sections
|
|
@@ -4,6 +4,27 @@ All notable changes to SQLSaber will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.10.0] - 2025-07-08
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Support for reading queries from stdin via pipe operator
|
|
12
|
+
- `echo 'show me all users' | saber` now works
|
|
13
|
+
- `cat query.txt | saber` reads query from file via stdin
|
|
14
|
+
- Allows integration with other command-line tools and scripts
|
|
15
|
+
|
|
16
|
+
## [0.9.0] - 2025-07-08
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Migrated from Typer to Cyclopts for CLI framework
|
|
21
|
+
- Improved command structure and parameter handling
|
|
22
|
+
- Better support for sub-commands and help documentation
|
|
23
|
+
- Made interactive mode more ergonomic
|
|
24
|
+
- `saber` now directly starts interactive mode (previously `saber query`)
|
|
25
|
+
- `saber "question"` executes a single query (previously `saber query "question"`)
|
|
26
|
+
- Removed the `query` subcommand for a cleaner interface
|
|
27
|
+
|
|
7
28
|
## [0.8.2] - 2025-07-08
|
|
8
29
|
|
|
9
30
|
## Changed
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
## Commands
|
|
2
|
+
|
|
3
|
+
- **Run Python**: `uv run python`
|
|
4
|
+
- **Run tests**: `uv run python -m pytest`
|
|
5
|
+
- **Run single test**: `uv run python -m pytest tests/test_path/test_file.py::test_function`
|
|
6
|
+
- **Lint**: `uv run ruff check --fix`
|
|
7
|
+
- **Format**: `uv run ruff format`
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
- **CLI App**: Agentic SQL assistant for natural language to SQL
|
|
12
|
+
- **Core modules**: `agents/` (AI logic), `cli/` (commands), `clients/` (LLM clients), `database/` (connections), `mcp/` (Model Context Protocol server)
|
|
13
|
+
- **Database support**: PostgreSQL, SQLite, MySQL via asyncpg/aiosqlite/aiomysql
|
|
14
|
+
- **MCP integration**: Exposes tools via `sqlsaber-mcp` for Claude Code and other MCP clients
|
|
15
|
+
|
|
16
|
+
## Code Style
|
|
17
|
+
|
|
18
|
+
- **Imports**: stdlib → 3rd party → local, use relative imports within modules
|
|
19
|
+
- **Naming**: snake_case functions/vars, PascalCase classes, UPPER_SNAKE constants, `_private` methods
|
|
20
|
+
- **Types**: Always use modern type hints (3.12+), async functions for I/O
|
|
21
|
+
- **Errors**: Custom exception hierarchy from `LLMClientError`, use try/finally for cleanup
|
|
22
|
+
- **Docstrings**: Triple-quoted with Args/Returns sections
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlsaber
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.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,14 @@ 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
|
+
```
|
|
141
|
+
|
|
142
|
+
You can also pipe queries from stdin:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
echo "show me all users created this month" | saber
|
|
146
|
+
cat query.txt | saber
|
|
140
147
|
```
|
|
141
148
|
|
|
142
149
|
### Database Selection
|
|
@@ -144,33 +151,39 @@ saber query "show me all users created this month"
|
|
|
144
151
|
Use a specific database connection:
|
|
145
152
|
|
|
146
153
|
```bash
|
|
147
|
-
#
|
|
148
|
-
saber
|
|
154
|
+
# Interactive mode with specific database
|
|
155
|
+
saber -d mydb
|
|
156
|
+
|
|
157
|
+
# Single query with specific database
|
|
158
|
+
saber -d mydb "count all orders"
|
|
149
159
|
```
|
|
150
160
|
|
|
151
161
|
## Examples
|
|
152
162
|
|
|
153
163
|
```bash
|
|
154
164
|
# Show database schema
|
|
155
|
-
saber
|
|
165
|
+
saber "what tables are in my database?"
|
|
156
166
|
|
|
157
167
|
# Count records
|
|
158
|
-
saber
|
|
168
|
+
saber "how many active users do we have?"
|
|
159
169
|
|
|
160
170
|
# Complex queries with joins
|
|
161
|
-
saber
|
|
171
|
+
saber "show me orders with customer details for this week"
|
|
162
172
|
|
|
163
173
|
# Aggregations
|
|
164
|
-
saber
|
|
174
|
+
saber "what's the total revenue by product category?"
|
|
165
175
|
|
|
166
176
|
# Date filtering
|
|
167
|
-
saber
|
|
177
|
+
saber "list users who haven't logged in for 30 days"
|
|
168
178
|
|
|
169
179
|
# Data exploration
|
|
170
|
-
saber
|
|
180
|
+
saber "show me the distribution of customer ages"
|
|
171
181
|
|
|
172
182
|
# Business analytics
|
|
173
|
-
saber
|
|
183
|
+
saber "which products had the highest sales growth last quarter?"
|
|
184
|
+
|
|
185
|
+
# Start interactive mode
|
|
186
|
+
saber
|
|
174
187
|
```
|
|
175
188
|
|
|
176
189
|
## MCP Server Integration
|
|
@@ -105,7 +105,7 @@ saber memory list
|
|
|
105
105
|
Start an interactive session:
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
|
-
saber
|
|
108
|
+
saber
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
> You can also add memories in an interactive session by starting your message with the `#` sign
|
|
@@ -115,7 +115,14 @@ saber query
|
|
|
115
115
|
Execute a single natural language query:
|
|
116
116
|
|
|
117
117
|
```bash
|
|
118
|
-
saber
|
|
118
|
+
saber "show me all users created this month"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
You can also pipe queries from stdin:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
echo "show me all users created this month" | saber
|
|
125
|
+
cat query.txt | saber
|
|
119
126
|
```
|
|
120
127
|
|
|
121
128
|
### Database Selection
|
|
@@ -123,33 +130,39 @@ saber query "show me all users created this month"
|
|
|
123
130
|
Use a specific database connection:
|
|
124
131
|
|
|
125
132
|
```bash
|
|
126
|
-
#
|
|
127
|
-
saber
|
|
133
|
+
# Interactive mode with specific database
|
|
134
|
+
saber -d mydb
|
|
135
|
+
|
|
136
|
+
# Single query with specific database
|
|
137
|
+
saber -d mydb "count all orders"
|
|
128
138
|
```
|
|
129
139
|
|
|
130
140
|
## Examples
|
|
131
141
|
|
|
132
142
|
```bash
|
|
133
143
|
# Show database schema
|
|
134
|
-
saber
|
|
144
|
+
saber "what tables are in my database?"
|
|
135
145
|
|
|
136
146
|
# Count records
|
|
137
|
-
saber
|
|
147
|
+
saber "how many active users do we have?"
|
|
138
148
|
|
|
139
149
|
# Complex queries with joins
|
|
140
|
-
saber
|
|
150
|
+
saber "show me orders with customer details for this week"
|
|
141
151
|
|
|
142
152
|
# Aggregations
|
|
143
|
-
saber
|
|
153
|
+
saber "what's the total revenue by product category?"
|
|
144
154
|
|
|
145
155
|
# Date filtering
|
|
146
|
-
saber
|
|
156
|
+
saber "list users who haven't logged in for 30 days"
|
|
147
157
|
|
|
148
158
|
# Data exploration
|
|
149
|
-
saber
|
|
159
|
+
saber "show me the distribution of customer ages"
|
|
150
160
|
|
|
151
161
|
# Business analytics
|
|
152
|
-
saber
|
|
162
|
+
saber "which products had the highest sales growth last quarter?"
|
|
163
|
+
|
|
164
|
+
# Start interactive mode
|
|
165
|
+
saber
|
|
153
166
|
```
|
|
154
167
|
|
|
155
168
|
## MCP Server Integration
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlsaber"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.10.0"
|
|
4
4
|
description = "SQLSaber - Agentic SQL assistant like Claude Code"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"asyncpg>=0.30.0",
|
|
9
|
-
"typer>=0.16.0",
|
|
10
9
|
"rich>=13.7.0",
|
|
11
10
|
"anthropic>=0.54.0",
|
|
12
11
|
"keyring>=25.6.0",
|
|
@@ -18,6 +17,7 @@ dependencies = [
|
|
|
18
17
|
"pandas>=2.0.0",
|
|
19
18
|
"fastmcp>=2.9.0",
|
|
20
19
|
"uniplot>=0.21.2",
|
|
20
|
+
"cyclopts>=3.22.1",
|
|
21
21
|
]
|
|
22
22
|
|
|
23
23
|
[tool.uv]
|
|
@@ -33,6 +33,5 @@ packages = ["src/sqlsaber"]
|
|
|
33
33
|
[project.scripts]
|
|
34
34
|
sqlsaber = "sqlsaber.cli.commands:main"
|
|
35
35
|
saber = "sqlsaber.cli.commands:main"
|
|
36
|
-
sql = "sqlsaber.cli.commands:main"
|
|
37
36
|
sqlsaber-mcp = "sqlsaber.mcp.mcp:main"
|
|
38
37
|
saber-mcp = "sqlsaber.mcp.mcp:main"
|
|
@@ -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
|
|
@@ -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,77 +37,94 @@ 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
|
|
57
|
+
echo "show me all users" | saber # Read query from stdin
|
|
58
|
+
cat query.txt | saber # Read query from file via stdin
|
|
46
59
|
"""
|
|
47
|
-
|
|
60
|
+
# Store database in app context for commands to access
|
|
61
|
+
app.meta["database"] = database
|
|
48
62
|
|
|
49
63
|
|
|
50
|
-
@app.
|
|
64
|
+
@app.default
|
|
51
65
|
def query(
|
|
52
|
-
query_text:
|
|
53
|
-
None,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
query_text: Annotated[
|
|
67
|
+
str | None,
|
|
68
|
+
cyclopts.Parameter(
|
|
69
|
+
help="SQL query in natural language (if not provided, reads from stdin or starts interactive mode)",
|
|
70
|
+
),
|
|
71
|
+
] = None,
|
|
72
|
+
database: Annotated[
|
|
73
|
+
str | None,
|
|
74
|
+
cyclopts.Parameter(
|
|
75
|
+
["--database", "-d"],
|
|
76
|
+
help="Database connection name (uses default if not specified)",
|
|
77
|
+
),
|
|
78
|
+
] = None,
|
|
62
79
|
):
|
|
63
|
-
"""Run a query against the database or start interactive mode.
|
|
80
|
+
"""Run a query against the database or start interactive mode.
|
|
81
|
+
|
|
82
|
+
When called without arguments:
|
|
83
|
+
- If stdin has data, reads query from stdin
|
|
84
|
+
- Otherwise, starts interactive mode
|
|
85
|
+
|
|
86
|
+
When called with a query string, executes that query and exits.
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
saber # Start interactive mode
|
|
90
|
+
saber "show me all users" # Run a single query
|
|
91
|
+
echo "show me all users" | saber # Read query from stdin
|
|
92
|
+
"""
|
|
64
93
|
|
|
65
94
|
async def run_session():
|
|
95
|
+
# Check if query_text is None and stdin has data
|
|
96
|
+
actual_query = query_text
|
|
97
|
+
if query_text is None and not sys.stdin.isatty():
|
|
98
|
+
# Read from stdin
|
|
99
|
+
actual_query = sys.stdin.read().strip()
|
|
100
|
+
if not actual_query:
|
|
101
|
+
# If stdin was empty, fall back to interactive mode
|
|
102
|
+
actual_query = None
|
|
103
|
+
|
|
66
104
|
# Get database configuration or handle direct CSV file
|
|
67
105
|
if database:
|
|
68
106
|
# Check if this is a direct CSV file path
|
|
69
107
|
if database.endswith(".csv"):
|
|
70
108
|
csv_path = Path(database).expanduser().resolve()
|
|
71
109
|
if not csv_path.exists():
|
|
72
|
-
|
|
73
|
-
f"[bold red]Error:[/bold red] CSV file '{database}' not found."
|
|
74
|
-
)
|
|
75
|
-
raise typer.Exit(1)
|
|
110
|
+
raise CLIError(f"CSV file '{database}' not found.")
|
|
76
111
|
connection_string = f"csv:///{csv_path}"
|
|
77
112
|
db_name = csv_path.stem
|
|
78
113
|
else:
|
|
79
114
|
# Look up configured database connection
|
|
80
115
|
db_config = config_manager.get_database(database)
|
|
81
116
|
if not db_config:
|
|
82
|
-
|
|
83
|
-
f"
|
|
84
|
-
)
|
|
85
|
-
console.print(
|
|
86
|
-
"Use 'sqlsaber db list' to see available connections."
|
|
117
|
+
raise CLIError(
|
|
118
|
+
f"Database connection '{database}' not found. Use 'sqlsaber db list' to see available connections."
|
|
87
119
|
)
|
|
88
|
-
raise typer.Exit(1)
|
|
89
120
|
connection_string = db_config.to_connection_string()
|
|
90
121
|
db_name = db_config.name
|
|
91
122
|
else:
|
|
92
123
|
db_config = config_manager.get_default_database()
|
|
93
124
|
if not db_config:
|
|
94
|
-
|
|
95
|
-
"
|
|
96
|
-
)
|
|
97
|
-
console.print(
|
|
98
|
-
"Use 'sqlsaber db add <name>' to add a database connection."
|
|
125
|
+
raise CLIError(
|
|
126
|
+
"No database connections configured. Use 'sqlsaber db add <name>' to add a database connection."
|
|
99
127
|
)
|
|
100
|
-
raise typer.Exit(1)
|
|
101
128
|
connection_string = db_config.to_connection_string()
|
|
102
129
|
db_name = db_config.name
|
|
103
130
|
|
|
@@ -105,22 +132,19 @@ def query(
|
|
|
105
132
|
try:
|
|
106
133
|
db_conn = DatabaseConnection(connection_string)
|
|
107
134
|
except Exception as e:
|
|
108
|
-
|
|
109
|
-
f"[bold red]Error creating database connection:[/bold red] {e}"
|
|
110
|
-
)
|
|
111
|
-
raise typer.Exit(1)
|
|
135
|
+
raise CLIError(f"Error creating database connection: {e}")
|
|
112
136
|
|
|
113
137
|
# Create agent instance with database name for memory context
|
|
114
138
|
agent = AnthropicSQLAgent(db_conn, db_name)
|
|
115
139
|
|
|
116
140
|
try:
|
|
117
|
-
if
|
|
141
|
+
if actual_query:
|
|
118
142
|
# Single query mode with streaming
|
|
119
143
|
streaming_handler = StreamingQueryHandler(console)
|
|
120
144
|
console.print(
|
|
121
145
|
f"[bold blue]Connected to:[/bold blue] {db_name} {agent._get_database_type_name()}\n"
|
|
122
146
|
)
|
|
123
|
-
await streaming_handler.execute_streaming_query(
|
|
147
|
+
await streaming_handler.execute_streaming_query(actual_query, agent)
|
|
124
148
|
else:
|
|
125
149
|
# Interactive mode
|
|
126
150
|
session = InteractiveSession(console, agent)
|
|
@@ -132,25 +156,29 @@ def query(
|
|
|
132
156
|
await db_conn.close()
|
|
133
157
|
console.print("\n[green]Goodbye![/green]")
|
|
134
158
|
|
|
135
|
-
# Run the async function
|
|
136
|
-
|
|
159
|
+
# Run the async function with proper error handling
|
|
160
|
+
try:
|
|
161
|
+
asyncio.run(run_session())
|
|
162
|
+
except CLIError as e:
|
|
163
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
164
|
+
sys.exit(e.exit_code)
|
|
137
165
|
|
|
138
166
|
|
|
139
167
|
# Add authentication management commands
|
|
140
168
|
auth_app = create_auth_app()
|
|
141
|
-
app.
|
|
169
|
+
app.command(auth_app, name="auth")
|
|
142
170
|
|
|
143
171
|
# Add database management commands after main callback is defined
|
|
144
172
|
db_app = create_db_app()
|
|
145
|
-
app.
|
|
173
|
+
app.command(db_app, name="db")
|
|
146
174
|
|
|
147
175
|
# Add memory management commands
|
|
148
176
|
memory_app = create_memory_app()
|
|
149
|
-
app.
|
|
177
|
+
app.command(memory_app, name="memory")
|
|
150
178
|
|
|
151
179
|
# Add model management commands
|
|
152
180
|
models_app = create_models_app()
|
|
153
|
-
app.
|
|
181
|
+
app.command(models_app, name="models")
|
|
154
182
|
|
|
155
183
|
|
|
156
184
|
def main():
|