sqlsaber 0.33.0__tar.gz → 0.34.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.
Potentially problematic release.
This version of sqlsaber might be problematic. Click here for more details.
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/PKG-INFO +1 -1
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/src/content/docs/changelog.md +12 -1
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/src/content/docs/guides/database-setup.mdx +29 -4
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/src/content/docs/guides/getting-started.mdx +23 -45
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/src/content/docs/guides/models.mdx +7 -6
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/src/content/docs/reference/commands.md +9 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/pyproject.toml +1 -1
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/application/auth_setup.py +83 -11
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/cli/auth.py +3 -1
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/config/api_keys.py +23 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/config/auth.py +6 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/config/logging.py +0 -1
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/database/postgresql.py +63 -11
- sqlsaber-0.34.0/tests/test_application/test_auth_setup.py +122 -0
- sqlsaber-0.34.0/tests/test_database/test_postgresql_module.py +95 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/uv.lock +1 -1
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/.github/workflows/claude-code-review.yml +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/.github/workflows/claude.yml +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/.github/workflows/deploy-docs.yml +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/.github/workflows/publish.yml +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/.github/workflows/test.yml +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/.gitignore +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/.python-version +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/AGENTS.md +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/CLAUDE.md +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/LICENSE +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/README.md +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/.gitignore +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/.vscode/extensions.json +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/.vscode/launch.json +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/CLAUDE.md +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/astro.config.mjs +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/package-lock.json +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/package.json +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/public/CNAME +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/public/favicon.svg +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/src/assets/sqlsaber.gif +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/src/content/docs/guides/authentication.mdx +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/src/content/docs/guides/memory.mdx +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/src/content/docs/guides/queries.mdx +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/src/content/docs/guides/threads.md +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/src/content/docs/index.mdx +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/src/content/docs/installation.mdx +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/src/content.config.ts +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/src/styles/global.css +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/docs/tsconfig.json +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/legislators.db +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/pytest.ini +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/sqlsaber.gif +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/sqlsaber.svg +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/__main__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/agents/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/agents/base.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/agents/pydantic_ai_agent.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/application/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/application/db_setup.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/application/model_selection.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/application/prompts.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/cli/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/cli/commands.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/cli/completers.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/cli/database.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/cli/display.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/cli/interactive.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/cli/memory.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/cli/models.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/cli/onboarding.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/cli/streaming.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/cli/theme.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/cli/threads.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/config/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/config/database.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/config/oauth_flow.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/config/oauth_tokens.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/config/providers.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/config/settings.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/database/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/database/base.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/database/csv.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/database/duckdb.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/database/mysql.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/database/resolver.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/database/schema.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/database/sqlite.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/memory/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/memory/manager.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/memory/storage.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/prompts/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/prompts/claude.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/prompts/memory.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/prompts/openai.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/theme/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/theme/manager.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/threads/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/threads/storage.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/tools/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/tools/base.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/tools/registry.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/tools/sql_guard.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/src/sqlsaber/tools/sql_tools.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/conftest.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_cli/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_cli/test_auth_reset.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_cli/test_commands.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_cli/test_threads.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_config/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_config/test_database.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_config/test_oauth.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_config/test_providers.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_config/test_settings.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_database/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_database/test_connection.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_database/test_csv_connection.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_database/test_csv_module.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_database/test_duckdb_module.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_database/test_schema.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_database/test_schema_display.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_database/test_sqlite_module.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_database/test_timeout.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_database_resolver.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_theme/test_manager.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_threads_storage.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_tools/__init__.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_tools/test_base.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_tools/test_registry.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_tools/test_sql_guard.py +0 -0
- {sqlsaber-0.33.0 → sqlsaber-0.34.0}/tests/test_tools/test_sql_tools.py +0 -0
|
@@ -9,6 +9,16 @@ All notable changes to SQLsaber will be documented here.
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
### v0.34.0 - 2025-10-17
|
|
13
|
+
|
|
14
|
+
#### Changed
|
|
15
|
+
|
|
16
|
+
- Improved `auth setup` command: Reworked to select the provider first, offer to reset existing credentials per provider, and allow configuring multiple providers in one session without manual cleanup.
|
|
17
|
+
- PostgreSQL schema introspection: Exclude TimescaleDB internal schemas by default and add env-based filtering
|
|
18
|
+
- Default exclusions now include `pg_catalog`, `information_schema`, `_timescaledb_internal`, `_timescaledb_cache`, `_timescaledb_config`, `_timescaledb_catalog`
|
|
19
|
+
- New environment variable `SQLSABER_PG_EXCLUDE_SCHEMAS` allows excluding additional schemas (comma-separated)
|
|
20
|
+
- Applies to both `list_tables` and `introspect_schema` tools
|
|
21
|
+
|
|
12
22
|
### v0.33.0 - 2025-10-16
|
|
13
23
|
|
|
14
24
|
#### Changed
|
|
@@ -61,7 +71,7 @@ All notable changes to SQLsaber will be documented here.
|
|
|
61
71
|
|
|
62
72
|
> `sqlsaber` can still be used as a cli in coding agents like Claude Code, Codex, or Amp.
|
|
63
73
|
>
|
|
64
|
-
> Just
|
|
74
|
+
> Just _ask_ the coding agent to invoke `sqlsaber` cli with your question.
|
|
65
75
|
|
|
66
76
|
### v0.29.1 - 2025-10-05
|
|
67
77
|
|
|
@@ -356,6 +366,7 @@ All notable changes to SQLsaber will be documented here.
|
|
|
356
366
|
#### Added
|
|
357
367
|
|
|
358
368
|
- Table name autocomplete with "@" prefix in interactive mode
|
|
369
|
+
|
|
359
370
|
- Type "@" followed by table name to get fuzzy matching completions
|
|
360
371
|
- Supports schema-aware completions (e.g., "@sample" matches "public.sample")
|
|
361
372
|
|
|
@@ -3,7 +3,7 @@ title: Database Setup
|
|
|
3
3
|
description: Configure database connections in SQLsaber
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
import { Aside } from
|
|
6
|
+
import { Aside } from "@astrojs/starlight/components";
|
|
7
7
|
|
|
8
8
|
SQLsaber supports PostgreSQL, MySQL, SQLite, DuckDB, and CSV data sources. This guide covers all the ways to configure database connections.
|
|
9
9
|
|
|
@@ -66,7 +66,8 @@ saber -d "./customers.csv" "How many customers are from each state?"
|
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
<Aside type="note">
|
|
69
|
-
For CSV files, first row should contain column headers. SQLsaber will infer
|
|
69
|
+
For CSV files, first row should contain column headers. SQLsaber will infer
|
|
70
|
+
data types automatically.
|
|
70
71
|
</Aside>
|
|
71
72
|
|
|
72
73
|
### Managing Connections
|
|
@@ -86,7 +87,7 @@ saber db set-default my-database
|
|
|
86
87
|
```
|
|
87
88
|
|
|
88
89
|
<Aside type="note">
|
|
89
|
-
By default, SQLsaber will set the first added connection as default.
|
|
90
|
+
By default, SQLsaber will set the first added connection as default.
|
|
90
91
|
</Aside>
|
|
91
92
|
|
|
92
93
|
#### Test Connections
|
|
@@ -115,10 +116,13 @@ While SQLsaber ensures, via checks before executing queries, that only read quer
|
|
|
115
116
|
If you have a read-replica of your production database, consider using those credentials to setup connection.
|
|
116
117
|
|
|
117
118
|
In addition to the read-only role, this will provide additional layer of security and ensure no data modifying queries are executed.
|
|
119
|
+
|
|
118
120
|
</Aside>
|
|
119
121
|
|
|
120
122
|
<Aside type="note">
|
|
121
|
-
For development setups, this isn't required but is a good practice if you wish
|
|
123
|
+
For development setups, this isn't required but is a good practice if you wish
|
|
124
|
+
to ensure with absolute certainty that no data modifying queries are ever
|
|
125
|
+
executed.
|
|
122
126
|
</Aside>
|
|
123
127
|
|
|
124
128
|
#### Password Storage
|
|
@@ -129,6 +133,27 @@ SQLsaber stores database passwords securely using your operating system's creden
|
|
|
129
133
|
|
|
130
134
|
Store SSL certificates in a secure location and use absolute paths when configuring connections.
|
|
131
135
|
|
|
136
|
+
### Schema Introspection Filters (PostgreSQL)
|
|
137
|
+
|
|
138
|
+
By default, SQLsaber excludes system and TimescaleDB internal schemas during schema discovery to keep results focused and fast.
|
|
139
|
+
|
|
140
|
+
Excluded by default:
|
|
141
|
+
|
|
142
|
+
- `pg_catalog`
|
|
143
|
+
- `information_schema`
|
|
144
|
+
- `_timescaledb_internal`
|
|
145
|
+
- `_timescaledb_cache`
|
|
146
|
+
- `_timescaledb_config`
|
|
147
|
+
- `_timescaledb_catalog`
|
|
148
|
+
|
|
149
|
+
You can exclude additional schemas via environment variable before running `saber`:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
export SQLSABER_PG_EXCLUDE_SCHEMAS="schema1,schema2"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Both the `list_tables` and `introspect_schema` tools respect these filters.
|
|
156
|
+
|
|
132
157
|
### Troubleshooting
|
|
133
158
|
|
|
134
159
|
Always test new connections after adding them.
|
|
@@ -15,72 +15,50 @@ Before you begin, make sure you have:
|
|
|
15
15
|
|
|
16
16
|
### Quick Setup
|
|
17
17
|
|
|
18
|
-
#### 1.
|
|
18
|
+
#### 1. Launch the Onboarding Flow
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
SQLsaber uses Claude Sonnet 4 as the model by default, but supports models from [multiple providers](/guides/models).
|
|
20
|
+
Run SQLsaber with no arguments to start the guided setup:
|
|
23
21
|
|
|
24
22
|
```bash
|
|
25
|
-
saber
|
|
23
|
+
saber
|
|
26
24
|
```
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
On your first run, SQLsaber walks you through two guided steps:
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
1. **Database connection**:
|
|
29
|
+
Choose the database type and provide connection details.
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
is one of the most cost-effective options.
|
|
36
|
-
</Aside>
|
|
31
|
+
2. **Authentication**:
|
|
32
|
+
Sign in to your preferred AI provider and pick a model. We recommend Sonnet 4.5, 4 or GPT-5.
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
If you choose a provider other than Anthropic, please select the appropriate
|
|
40
|
-
model for the provider by running `saber models set`.
|
|
41
|
-
</Aside>
|
|
34
|
+
`sqlsaber` will store your API key securely using your OS credential store.
|
|
42
35
|
|
|
43
|
-
|
|
36
|
+
When both steps succeed you'll see a confirmation screen, and SQLsaber immediately opens an interactive session.
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
This interactive command will ask you for:
|
|
38
|
+
<Aside type="tip">
|
|
39
|
+
If you have a Claude Pro or Max subscription, you can use it with SQLsaber for
|
|
40
|
+
a cost-effective setup.
|
|
41
|
+
</Aside>
|
|
52
42
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
- SQLite
|
|
57
|
-
- DuckDB
|
|
58
|
-
- Connection details (host, port, database name, username)
|
|
59
|
-
- SSL configuration (if needed)
|
|
43
|
+
<Aside type="tip">
|
|
44
|
+
If you already have a connection string handy, pass it via `saber -d <connection-string>` to skip the onboarding flow.
|
|
45
|
+
</Aside>
|
|
60
46
|
|
|
61
47
|
<Aside type="note">
|
|
62
|
-
|
|
48
|
+
Need a sample database?
|
|
49
|
+
|
|
50
|
+
Download the `legislators` SQLite file, then choose `sqlite` when the onboarding flow prompts for a database location (or add it with `saber db add`):
|
|
63
51
|
|
|
64
52
|
```bash
|
|
65
53
|
curl -L -o legislators.db https://github.com/SarthakJariwala/sqlsaber/raw/refs/heads/main/legislators.db
|
|
66
|
-
|
|
67
|
-
# Add it to SQLsaber
|
|
68
|
-
saber db add legislators --type sqlite --database ./legislators.db
|
|
69
54
|
```
|
|
70
55
|
|
|
71
56
|
</Aside>
|
|
72
57
|
|
|
73
|
-
####
|
|
74
|
-
|
|
75
|
-
Now you're ready to ask your first question!
|
|
76
|
-
|
|
77
|
-
Start SQLsaber in interactive mode:
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
saber
|
|
81
|
-
```
|
|
58
|
+
#### 2. Ask Your First Question
|
|
82
59
|
|
|
83
|
-
Try asking a
|
|
60
|
+
Once onboarding completes, you're already in interactive mode. Try asking a
|
|
61
|
+
question:
|
|
84
62
|
|
|
85
63
|
```
|
|
86
64
|
> How many VPs have become presidents via election in 20th century?
|
|
@@ -3,7 +3,7 @@ title: Models
|
|
|
3
3
|
description: Configure and select AI models for SQLsaber
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
import { Aside } from
|
|
6
|
+
import { Aside } from "@astrojs/starlight/components";
|
|
7
7
|
|
|
8
8
|
SQLsaber supports many LLM models across different providers.
|
|
9
9
|
|
|
@@ -11,19 +11,19 @@ This guide covers model selection, configuration, and recommendations for the be
|
|
|
11
11
|
|
|
12
12
|
### Default Model
|
|
13
13
|
|
|
14
|
-
SQLsaber uses **Claude Sonnet 4** by default, which provides excellent SQL generation capabilities with good speed and accuracy.
|
|
14
|
+
SQLsaber uses **Claude Sonnet 4.5** by default, which provides excellent SQL generation capabilities with good speed and accuracy.
|
|
15
15
|
|
|
16
16
|
### Recommended Models
|
|
17
17
|
|
|
18
18
|
While SQLsaber supports a lot of models, we recommend the following for best results:
|
|
19
19
|
|
|
20
|
-
- Claude Sonnet 4, 3.7, 3.5
|
|
21
|
-
- Claude Opus 4, 4.1
|
|
22
20
|
- GPT-5
|
|
21
|
+
- Claude Sonnet 4.5, 4.0, 3.7
|
|
22
|
+
- Claude Opus 4, 4.1
|
|
23
23
|
- Gemini 2.5 Pro
|
|
24
24
|
|
|
25
25
|
<Aside type="tip">
|
|
26
|
-
Consider using one of the recommended models for best results.
|
|
26
|
+
Consider using one of the recommended models for best results.
|
|
27
27
|
</Aside>
|
|
28
28
|
|
|
29
29
|
### Model Management
|
|
@@ -43,12 +43,13 @@ saber models set
|
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
This interactive command lets you:
|
|
46
|
+
|
|
46
47
|
1. Choose from configured providers
|
|
47
48
|
2. Select a specific model
|
|
48
49
|
3. Set it as your default
|
|
49
50
|
|
|
50
51
|
<Aside type="tip">
|
|
51
|
-
|
|
52
|
+
This list is long - type to search and filter the list.
|
|
52
53
|
</Aside>
|
|
53
54
|
|
|
54
55
|
#### Reset to Default
|
|
@@ -436,3 +436,12 @@ When in interactive mode (`saber` with no arguments), you have access to a few a
|
|
|
436
436
|
|
|
437
437
|
- **Table names** - Type `@table_name[TAB]` for completions
|
|
438
438
|
- **Slash commands** - Type `/[TAB]` for command completions
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
### Environment Variables
|
|
443
|
+
|
|
444
|
+
These environment variables adjust runtime behavior:
|
|
445
|
+
|
|
446
|
+
- `SQLSABER_THEME` — Override the configured theme for the session.
|
|
447
|
+
- `SQLSABER_PG_EXCLUDE_SCHEMAS` — Comma-separated list of PostgreSQL schemas to exclude from schema discovery and introspection. Defaults already exclude `pg_catalog`, `information_schema`, `_timescaledb_internal`, `_timescaledb_cache`, `_timescaledb_config`, `_timescaledb_catalog`.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Shared auth setup logic for onboarding and CLI."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import os
|
|
4
5
|
|
|
5
6
|
from questionary import Choice
|
|
6
7
|
|
|
@@ -9,6 +10,7 @@ from sqlsaber.config import providers
|
|
|
9
10
|
from sqlsaber.config.api_keys import APIKeyManager
|
|
10
11
|
from sqlsaber.config.auth import AuthConfigManager, AuthMethod
|
|
11
12
|
from sqlsaber.config.oauth_flow import AnthropicOAuthFlow
|
|
13
|
+
from sqlsaber.config.oauth_tokens import OAuthTokenManager
|
|
12
14
|
from sqlsaber.theme.manager import create_console
|
|
13
15
|
|
|
14
16
|
console = create_console()
|
|
@@ -102,24 +104,49 @@ async def setup_auth(
|
|
|
102
104
|
Returns:
|
|
103
105
|
Tuple of (success: bool, provider: str | None)
|
|
104
106
|
"""
|
|
105
|
-
|
|
106
|
-
if auth_manager.has_auth_configured():
|
|
107
|
-
console.print("[success]✓ Authentication already configured![/success]")
|
|
108
|
-
return True, None
|
|
107
|
+
oauth_manager = OAuthTokenManager()
|
|
109
108
|
|
|
110
|
-
# Select provider
|
|
111
109
|
provider = await select_provider(prompter, default=default_provider)
|
|
112
110
|
|
|
113
111
|
if provider is None:
|
|
114
112
|
return False, None
|
|
115
113
|
|
|
114
|
+
env_var = api_key_manager.get_env_var_name(provider)
|
|
115
|
+
api_key_in_env = bool(os.getenv(env_var))
|
|
116
|
+
api_key_in_keyring = api_key_manager.has_stored_api_key(provider)
|
|
117
|
+
has_oauth = (
|
|
118
|
+
oauth_manager.has_oauth_token("anthropic")
|
|
119
|
+
if provider == "anthropic" and allow_oauth
|
|
120
|
+
else False
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if api_key_in_env or api_key_in_keyring or has_oauth:
|
|
124
|
+
parts: list[str] = []
|
|
125
|
+
if api_key_in_keyring:
|
|
126
|
+
parts.append("stored API key")
|
|
127
|
+
if api_key_in_env:
|
|
128
|
+
parts.append(f"{env_var} environment variable")
|
|
129
|
+
if has_oauth:
|
|
130
|
+
parts.append("OAuth token")
|
|
131
|
+
summary = ", ".join(parts)
|
|
132
|
+
console.print(
|
|
133
|
+
f"[info]Existing authentication found for {provider}: {summary}[/info]"
|
|
134
|
+
)
|
|
135
|
+
|
|
116
136
|
# For Anthropic, offer OAuth or API key
|
|
117
137
|
if provider == "anthropic" and allow_oauth:
|
|
138
|
+
api_key_label = "API Key"
|
|
139
|
+
if api_key_in_keyring or api_key_in_env:
|
|
140
|
+
api_key_label += " [configured]"
|
|
141
|
+
oauth_label = "Claude Pro/Max (OAuth)"
|
|
142
|
+
if has_oauth:
|
|
143
|
+
oauth_label += " [configured]"
|
|
144
|
+
|
|
118
145
|
method_choice = await prompter.select(
|
|
119
146
|
"Authentication method:",
|
|
120
147
|
choices=[
|
|
121
|
-
Choice(
|
|
122
|
-
Choice(
|
|
148
|
+
Choice(api_key_label, value=AuthMethod.API_KEY),
|
|
149
|
+
Choice(oauth_label, value=AuthMethod.CLAUDE_PRO),
|
|
123
150
|
],
|
|
124
151
|
)
|
|
125
152
|
|
|
@@ -127,6 +154,28 @@ async def setup_auth(
|
|
|
127
154
|
return False, None
|
|
128
155
|
|
|
129
156
|
if method_choice == AuthMethod.CLAUDE_PRO:
|
|
157
|
+
if has_oauth:
|
|
158
|
+
reset = await prompter.confirm(
|
|
159
|
+
"Anthropic OAuth is already configured. Reset before continuing?",
|
|
160
|
+
default=False,
|
|
161
|
+
)
|
|
162
|
+
if not reset:
|
|
163
|
+
console.print(
|
|
164
|
+
"[warning]No changes made to Anthropic OAuth credentials.[/warning]"
|
|
165
|
+
)
|
|
166
|
+
return True, None
|
|
167
|
+
|
|
168
|
+
removal_success = oauth_manager.remove_oauth_token("anthropic")
|
|
169
|
+
if not removal_success:
|
|
170
|
+
console.print(
|
|
171
|
+
"[error]Failed to remove existing Anthropic OAuth credentials.[/error]"
|
|
172
|
+
)
|
|
173
|
+
return False, None
|
|
174
|
+
|
|
175
|
+
current_method = auth_manager.get_auth_method()
|
|
176
|
+
if current_method == AuthMethod.CLAUDE_PRO:
|
|
177
|
+
auth_manager.clear_auth_method()
|
|
178
|
+
|
|
130
179
|
console.print()
|
|
131
180
|
oauth_success = await configure_oauth_anthropic(
|
|
132
181
|
auth_manager, run_in_thread=run_oauth_in_thread
|
|
@@ -136,12 +185,35 @@ async def setup_auth(
|
|
|
136
185
|
"[green]✓ Anthropic OAuth configured successfully![/green]"
|
|
137
186
|
)
|
|
138
187
|
return True, provider
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
188
|
+
|
|
189
|
+
console.print("[error]✗ Anthropic OAuth setup failed.[/error]")
|
|
190
|
+
return False, None
|
|
142
191
|
|
|
143
192
|
# API key flow
|
|
144
|
-
|
|
193
|
+
if api_key_in_keyring:
|
|
194
|
+
reset_api_key = await prompter.confirm(
|
|
195
|
+
f"{provider.title()} API key is stored in your keyring. Reset before continuing?",
|
|
196
|
+
default=False,
|
|
197
|
+
)
|
|
198
|
+
if not reset_api_key:
|
|
199
|
+
console.print(
|
|
200
|
+
"[warning]No changes made to stored API key credentials.[/warning]"
|
|
201
|
+
)
|
|
202
|
+
return True, None
|
|
203
|
+
if not api_key_manager.delete_api_key(provider):
|
|
204
|
+
console.print(
|
|
205
|
+
"[error]Failed to remove existing API key credentials.[/error]"
|
|
206
|
+
)
|
|
207
|
+
return False, None
|
|
208
|
+
console.print(
|
|
209
|
+
f"[muted]{provider.title()} API key removed from keyring.[/muted]"
|
|
210
|
+
)
|
|
211
|
+
api_key_in_keyring = False
|
|
212
|
+
|
|
213
|
+
if api_key_in_env:
|
|
214
|
+
console.print(
|
|
215
|
+
f"[muted]{env_var} is set in your environment. Update it there if you need a new value.[/muted]"
|
|
216
|
+
)
|
|
145
217
|
|
|
146
218
|
console.print()
|
|
147
219
|
console.print(f"[dim]To use {provider.title()}, you need an API key.[/dim]")
|
|
@@ -167,7 +167,9 @@ def reset():
|
|
|
167
167
|
pass
|
|
168
168
|
except Exception as e:
|
|
169
169
|
console.print(f"Warning: Could not remove API key: {e}", style="warning")
|
|
170
|
-
logger.warning(
|
|
170
|
+
logger.warning(
|
|
171
|
+
"auth.reset.api_key_remove_failed", provider=provider, error=str(e)
|
|
172
|
+
)
|
|
171
173
|
|
|
172
174
|
# Optionally clear global auth method if removing Anthropic OAuth configuration
|
|
173
175
|
if provider == "anthropic" and oauth_present:
|
|
@@ -41,6 +41,29 @@ class APIKeyManager:
|
|
|
41
41
|
# 3. Prompt user for API key
|
|
42
42
|
return self._prompt_and_store_key(provider, env_var_name, service_name)
|
|
43
43
|
|
|
44
|
+
def has_stored_api_key(self, provider: str) -> bool:
|
|
45
|
+
"""Check if an API key is stored for the provider."""
|
|
46
|
+
service_name = self._get_service_name(provider)
|
|
47
|
+
try:
|
|
48
|
+
return keyring.get_password(service_name, provider) is not None
|
|
49
|
+
except Exception:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
def delete_api_key(self, provider: str) -> bool:
|
|
53
|
+
"""Remove stored API key for the provider."""
|
|
54
|
+
service_name = self._get_service_name(provider)
|
|
55
|
+
try:
|
|
56
|
+
keyring.delete_password(service_name, provider)
|
|
57
|
+
return True
|
|
58
|
+
except keyring.errors.PasswordDeleteError:
|
|
59
|
+
return True
|
|
60
|
+
except Exception as e:
|
|
61
|
+
console.print(
|
|
62
|
+
f"Warning: Could not remove API key: {e}",
|
|
63
|
+
style="warning",
|
|
64
|
+
)
|
|
65
|
+
return False
|
|
66
|
+
|
|
44
67
|
def get_env_var_name(self, provider: str) -> str:
|
|
45
68
|
"""Get the expected environment variable name for a provider."""
|
|
46
69
|
# Normalize aliases to canonical provider keys
|
|
@@ -81,6 +81,12 @@ class AuthConfigManager:
|
|
|
81
81
|
config["auth_method"] = auth_method.value
|
|
82
82
|
self._save_config(config)
|
|
83
83
|
|
|
84
|
+
def clear_auth_method(self) -> None:
|
|
85
|
+
"""Clear any configured authentication method."""
|
|
86
|
+
config = self._load_config()
|
|
87
|
+
config["auth_method"] = None
|
|
88
|
+
self._save_config(config)
|
|
89
|
+
|
|
84
90
|
def has_auth_configured(self) -> bool:
|
|
85
91
|
"""Check if authentication method is configured."""
|
|
86
92
|
return self.get_auth_method() is not None
|
|
@@ -135,6 +135,35 @@ class PostgreSQLConnection(BaseDatabaseConnection):
|
|
|
135
135
|
class PostgreSQLSchemaIntrospector(BaseSchemaIntrospector):
|
|
136
136
|
"""PostgreSQL-specific schema introspection."""
|
|
137
137
|
|
|
138
|
+
def _get_excluded_schemas(self) -> list[str]:
|
|
139
|
+
"""Return schemas to exclude during introspection.
|
|
140
|
+
|
|
141
|
+
Defaults include PostgreSQL system schemas and TimescaleDB internal
|
|
142
|
+
partitions schema. Additional schemas can be excluded by setting the
|
|
143
|
+
environment variable `SQLSABER_PG_EXCLUDE_SCHEMAS` to a comma-separated
|
|
144
|
+
list of schema names.
|
|
145
|
+
"""
|
|
146
|
+
import os
|
|
147
|
+
|
|
148
|
+
# Base exclusions: system schemas and TimescaleDB internal partitions
|
|
149
|
+
excluded = [
|
|
150
|
+
"pg_catalog",
|
|
151
|
+
"information_schema",
|
|
152
|
+
"_timescaledb_internal",
|
|
153
|
+
"_timescaledb_cache",
|
|
154
|
+
"_timescaledb_config",
|
|
155
|
+
"_timescaledb_catalog",
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
extra = os.getenv("SQLSABER_PG_EXCLUDE_SCHEMAS", "")
|
|
159
|
+
if extra:
|
|
160
|
+
for item in extra.split(","):
|
|
161
|
+
name = item.strip()
|
|
162
|
+
if name and name not in excluded:
|
|
163
|
+
excluded.append(name)
|
|
164
|
+
|
|
165
|
+
return excluded
|
|
166
|
+
|
|
138
167
|
def _build_table_filter_clause(self, tables: list) -> tuple[str, list]:
|
|
139
168
|
"""Build VALUES clause with bind parameters for table filtering.
|
|
140
169
|
|
|
@@ -160,23 +189,35 @@ class PostgreSQLSchemaIntrospector(BaseSchemaIntrospector):
|
|
|
160
189
|
"""Get tables information for PostgreSQL."""
|
|
161
190
|
pool = await connection.get_pool()
|
|
162
191
|
async with pool.acquire() as conn:
|
|
163
|
-
# Build WHERE clause for filtering
|
|
164
|
-
where_conditions = [
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
192
|
+
# Build WHERE clause for filtering with bind params
|
|
193
|
+
where_conditions: list[str] = []
|
|
194
|
+
params: list[Any] = []
|
|
195
|
+
|
|
196
|
+
excluded = self._get_excluded_schemas()
|
|
197
|
+
if excluded:
|
|
198
|
+
placeholders = ", ".join(f"${i + 1}" for i in range(len(excluded)))
|
|
199
|
+
where_conditions.append(f"table_schema NOT IN ({placeholders})")
|
|
200
|
+
params.extend(excluded)
|
|
201
|
+
else:
|
|
202
|
+
# Fallback safety
|
|
203
|
+
where_conditions.append(
|
|
204
|
+
"table_schema NOT IN ('pg_catalog', 'information_schema')"
|
|
205
|
+
)
|
|
168
206
|
|
|
169
207
|
if table_pattern:
|
|
170
208
|
# Support patterns like 'schema.table' or just 'table'
|
|
171
209
|
if "." in table_pattern:
|
|
172
210
|
schema_pattern, table_name_pattern = table_pattern.split(".", 1)
|
|
211
|
+
s_idx = len(params) + 1
|
|
212
|
+
t_idx = len(params) + 2
|
|
173
213
|
where_conditions.append(
|
|
174
|
-
"(table_schema LIKE $
|
|
214
|
+
f"(table_schema LIKE ${s_idx} AND table_name LIKE ${t_idx})"
|
|
175
215
|
)
|
|
176
216
|
params.extend([schema_pattern, table_name_pattern])
|
|
177
217
|
else:
|
|
218
|
+
p_idx = len(params) + 1
|
|
178
219
|
where_conditions.append(
|
|
179
|
-
"(table_name LIKE $
|
|
220
|
+
f"(table_name LIKE ${p_idx} OR table_schema || '.' || table_name LIKE ${p_idx})"
|
|
180
221
|
)
|
|
181
222
|
params.append(table_pattern)
|
|
182
223
|
|
|
@@ -310,17 +351,28 @@ class PostgreSQLSchemaIntrospector(BaseSchemaIntrospector):
|
|
|
310
351
|
"""Get list of tables with basic information for PostgreSQL."""
|
|
311
352
|
pool = await connection.get_pool()
|
|
312
353
|
async with pool.acquire() as conn:
|
|
313
|
-
#
|
|
314
|
-
|
|
354
|
+
# Exclude system schemas (and TimescaleDB internals) for performance
|
|
355
|
+
excluded = self._get_excluded_schemas()
|
|
356
|
+
params: list[Any] = []
|
|
357
|
+
if excluded:
|
|
358
|
+
placeholders = ", ".join(f"${i + 1}" for i in range(len(excluded)))
|
|
359
|
+
where_clause = f"table_schema NOT IN ({placeholders})"
|
|
360
|
+
params.extend(excluded)
|
|
361
|
+
else:
|
|
362
|
+
where_clause = (
|
|
363
|
+
"table_schema NOT IN ('pg_catalog', 'information_schema')"
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
tables_query = f"""
|
|
315
367
|
SELECT
|
|
316
368
|
table_schema,
|
|
317
369
|
table_name,
|
|
318
370
|
table_type
|
|
319
371
|
FROM information_schema.tables
|
|
320
|
-
WHERE
|
|
372
|
+
WHERE {where_clause}
|
|
321
373
|
ORDER BY table_schema, table_name;
|
|
322
374
|
"""
|
|
323
|
-
tables = await conn.fetch(tables_query)
|
|
375
|
+
tables = await conn.fetch(tables_query, *params)
|
|
324
376
|
|
|
325
377
|
# Convert to expected format
|
|
326
378
|
return [
|