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.
Files changed (68) hide show
  1. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/.gitignore +2 -0
  2. sqlsaber-0.10.0/AGENT.md +24 -0
  3. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/CHANGELOG.md +21 -0
  4. sqlsaber-0.10.0/CLAUDE.md +22 -0
  5. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/PKG-INFO +26 -13
  6. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/README.md +24 -11
  7. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/pyproject.toml +2 -3
  8. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/auth.py +9 -10
  9. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/commands.py +83 -55
  10. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/database.py +88 -62
  11. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/memory.py +83 -73
  12. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/models.py +15 -15
  13. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/uv.lock +58 -3
  14. sqlsaber-0.8.2/CLAUDE.md +0 -5
  15. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/.github/workflows/publish.yml +0 -0
  16. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/.python-version +0 -0
  17. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/LICENSE +0 -0
  18. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/pytest.ini +0 -0
  19. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/sqlsaber.svg +0 -0
  20. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/__init__.py +0 -0
  21. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/__main__.py +0 -0
  22. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/agents/__init__.py +0 -0
  23. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/agents/anthropic.py +0 -0
  24. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/agents/base.py +0 -0
  25. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/agents/mcp.py +0 -0
  26. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/agents/streaming.py +0 -0
  27. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/__init__.py +0 -0
  28. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/completers.py +0 -0
  29. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/display.py +0 -0
  30. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/interactive.py +0 -0
  31. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/cli/streaming.py +0 -0
  32. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/clients/__init__.py +0 -0
  33. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/clients/anthropic.py +0 -0
  34. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/clients/base.py +0 -0
  35. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/clients/exceptions.py +0 -0
  36. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/clients/models.py +0 -0
  37. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/clients/streaming.py +0 -0
  38. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/config/__init__.py +0 -0
  39. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/config/api_keys.py +0 -0
  40. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/config/auth.py +0 -0
  41. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/config/database.py +0 -0
  42. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/config/oauth_flow.py +0 -0
  43. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/config/oauth_tokens.py +0 -0
  44. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/config/settings.py +0 -0
  45. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/database/__init__.py +0 -0
  46. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/database/connection.py +0 -0
  47. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/database/schema.py +0 -0
  48. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/mcp/__init__.py +0 -0
  49. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/mcp/mcp.py +0 -0
  50. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/memory/__init__.py +0 -0
  51. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/memory/manager.py +0 -0
  52. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/memory/storage.py +0 -0
  53. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/models/__init__.py +0 -0
  54. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/models/events.py +0 -0
  55. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/src/sqlsaber/models/types.py +0 -0
  56. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/__init__.py +0 -0
  57. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/conftest.py +0 -0
  58. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_agents/test_anthropic_oauth.py +0 -0
  59. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_cli/__init__.py +0 -0
  60. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_cli/test_commands.py +0 -0
  61. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_clients/test_anthropic_client.py +0 -0
  62. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_clients/test_streaming.py +0 -0
  63. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_config/__init__.py +0 -0
  64. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_config/test_database.py +0 -0
  65. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_config/test_oauth.py +0 -0
  66. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_config/test_settings.py +0 -0
  67. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_database/__init__.py +0 -0
  68. {sqlsaber-0.8.2 → sqlsaber-0.10.0}/tests/test_database/test_connection.py +0 -0
@@ -8,3 +8,5 @@ wheels/
8
8
 
9
9
  # Virtual environments
10
10
  .venv
11
+
12
+ *.local.*
@@ -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.8.2
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 query
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 query "show me all users created this month"
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
- # Use named database from config
148
- saber query -d mydb "count all orders"
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 query "what tables are in my database?"
165
+ saber "what tables are in my database?"
156
166
 
157
167
  # Count records
158
- saber query "how many active users do we have?"
168
+ saber "how many active users do we have?"
159
169
 
160
170
  # Complex queries with joins
161
- saber query "show me orders with customer details for this week"
171
+ saber "show me orders with customer details for this week"
162
172
 
163
173
  # Aggregations
164
- saber query "what's the total revenue by product category?"
174
+ saber "what's the total revenue by product category?"
165
175
 
166
176
  # Date filtering
167
- saber query "list users who haven't logged in for 30 days"
177
+ saber "list users who haven't logged in for 30 days"
168
178
 
169
179
  # Data exploration
170
- saber query "show me the distribution of customer ages"
180
+ saber "show me the distribution of customer ages"
171
181
 
172
182
  # Business analytics
173
- saber query "which products had the highest sales growth last quarter?"
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 query
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 query "show me all users created this month"
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
- # Use named database from config
127
- saber query -d mydb "count all orders"
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 query "what tables are in my database?"
144
+ saber "what tables are in my database?"
135
145
 
136
146
  # Count records
137
- saber query "how many active users do we have?"
147
+ saber "how many active users do we have?"
138
148
 
139
149
  # Complex queries with joins
140
- saber query "show me orders with customer details for this week"
150
+ saber "show me orders with customer details for this week"
141
151
 
142
152
  # Aggregations
143
- saber query "what's the total revenue by product category?"
153
+ saber "what's the total revenue by product category?"
144
154
 
145
155
  # Date filtering
146
- saber query "list users who haven't logged in for 30 days"
156
+ saber "list users who haven't logged in for 30 days"
147
157
 
148
158
  # Data exploration
149
- saber query "show me the distribution of customer ages"
159
+ saber "show me the distribution of customer ages"
150
160
 
151
161
  # Business analytics
152
- saber query "which products had the highest sales growth last quarter?"
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.8.2"
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 typer
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 = typer.Typer(
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("setup")
23
- def setup_auth():
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("status")
83
- def show_auth_status():
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("reset")
108
- def reset_auth():
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() -> typer.Typer:
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 typer
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
- app = typer.Typer(
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.callback()
31
- def main_callback(
32
- database: str | None = typer.Option(
33
- None,
34
- "--database",
35
- "-d",
36
- help="Database connection name (uses default if not specified)",
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
- sb query # Start interactive mode
44
- sb query "show me all users" # Run a single query with default database
45
- sb query -d mydb "show me users" # Run a query with specific database
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
- pass
60
+ # Store database in app context for commands to access
61
+ app.meta["database"] = database
48
62
 
49
63
 
50
- @app.command()
64
+ @app.default
51
65
  def query(
52
- query_text: str | None = typer.Argument(
53
- None,
54
- help="SQL query in natural language (if not provided, starts interactive mode)",
55
- ),
56
- database: str | None = typer.Option(
57
- None,
58
- "--database",
59
- "-d",
60
- help="Database connection name (uses default if not specified)",
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
- console.print(
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
- console.print(
83
- f"[bold red]Error:[/bold red] Database connection '{database}' not found."
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
- console.print(
95
- "[bold red]Error:[/bold red] No database connections configured."
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
- console.print(
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 query_text:
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(query_text, agent)
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
- asyncio.run(run_session())
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.add_typer(auth_app, name="auth")
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.add_typer(db_app, name="db")
173
+ app.command(db_app, name="db")
146
174
 
147
175
  # Add memory management commands
148
176
  memory_app = create_memory_app()
149
- app.add_typer(memory_app, name="memory")
177
+ app.command(memory_app, name="memory")
150
178
 
151
179
  # Add model management commands
152
180
  models_app = create_models_app()
153
- app.add_typer(models_app, name="models")
181
+ app.command(models_app, name="models")
154
182
 
155
183
 
156
184
  def main():