terno-agent 0.1.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 (48) hide show
  1. terno_agent-0.1.0/.env.example +22 -0
  2. terno_agent-0.1.0/.gitignore +30 -0
  3. terno_agent-0.1.0/.python-version +1 -0
  4. terno_agent-0.1.0/Chinook_Sqlite.sqlite +0 -0
  5. terno_agent-0.1.0/PKG-INFO +317 -0
  6. terno_agent-0.1.0/README.md +272 -0
  7. terno_agent-0.1.0/pyproject.toml +76 -0
  8. terno_agent-0.1.0/src/terno_agent/__init__.py +7 -0
  9. terno_agent-0.1.0/src/terno_agent/__main__.py +4 -0
  10. terno_agent-0.1.0/src/terno_agent/agents/__init__.py +6 -0
  11. terno_agent-0.1.0/src/terno_agent/agents/base.py +123 -0
  12. terno_agent-0.1.0/src/terno_agent/agents/coder.py +22 -0
  13. terno_agent-0.1.0/src/terno_agent/agents/database.py +28 -0
  14. terno_agent-0.1.0/src/terno_agent/agents/orchestrator.py +149 -0
  15. terno_agent-0.1.0/src/terno_agent/cli.py +323 -0
  16. terno_agent-0.1.0/src/terno_agent/config.py +110 -0
  17. terno_agent-0.1.0/src/terno_agent/core/__init__.py +52 -0
  18. terno_agent-0.1.0/src/terno_agent/core/events.py +69 -0
  19. terno_agent-0.1.0/src/terno_agent/core/exceptions.py +22 -0
  20. terno_agent-0.1.0/src/terno_agent/core/messages.py +60 -0
  21. terno_agent-0.1.0/src/terno_agent/core/tool.py +34 -0
  22. terno_agent-0.1.0/src/terno_agent/db/__init__.py +3 -0
  23. terno_agent-0.1.0/src/terno_agent/db/connection.py +117 -0
  24. terno_agent-0.1.0/src/terno_agent/llm/__init__.py +4 -0
  25. terno_agent-0.1.0/src/terno_agent/llm/anthropic_client.py +138 -0
  26. terno_agent-0.1.0/src/terno_agent/llm/base.py +46 -0
  27. terno_agent-0.1.0/src/terno_agent/llm/factory.py +23 -0
  28. terno_agent-0.1.0/src/terno_agent/llm/openai_client.py +169 -0
  29. terno_agent-0.1.0/src/terno_agent/prompts/__init__.py +5 -0
  30. terno_agent-0.1.0/src/terno_agent/prompts/coder.py +20 -0
  31. terno_agent-0.1.0/src/terno_agent/prompts/database.py +21 -0
  32. terno_agent-0.1.0/src/terno_agent/prompts/orchestrator.py +29 -0
  33. terno_agent-0.1.0/src/terno_agent/sandbox/__init__.py +4 -0
  34. terno_agent-0.1.0/src/terno_agent/sandbox/base.py +52 -0
  35. terno_agent-0.1.0/src/terno_agent/sandbox/docker.py +111 -0
  36. terno_agent-0.1.0/src/terno_agent/sandbox/factory.py +22 -0
  37. terno_agent-0.1.0/src/terno_agent/sandbox/local.py +54 -0
  38. terno_agent-0.1.0/src/terno_agent/tools/__init__.py +9 -0
  39. terno_agent-0.1.0/src/terno_agent/tools/code_exec.py +48 -0
  40. terno_agent-0.1.0/src/terno_agent/tools/sql.py +123 -0
  41. terno_agent-0.1.0/tests/__init__.py +0 -0
  42. terno_agent-0.1.0/tests/test_agent_events.py +111 -0
  43. terno_agent-0.1.0/tests/test_chat_gated.py +45 -0
  44. terno_agent-0.1.0/tests/test_config_dotenv.py +80 -0
  45. terno_agent-0.1.0/tests/test_imports.py +14 -0
  46. terno_agent-0.1.0/tests/test_local_sandbox.py +22 -0
  47. terno_agent-0.1.0/tests/test_sql_tool.py +47 -0
  48. terno_agent-0.1.0/uv.lock +998 -0
@@ -0,0 +1,22 @@
1
+ # Copy to .env and fill in values. The agent loads this automatically from the
2
+ # current working directory (or any parent dir) via python-dotenv. Variables
3
+ # already set in the shell win over values in this file.
4
+
5
+ # --- LLM ---
6
+ TERNO_LLM_PROVIDER=anthropic # anthropic | openai
7
+ TERNO_LLM_MODEL=claude-opus-4-7 # optional; sensible default per provider
8
+ ANTHROPIC_API_KEY=
9
+ # OPENAI_API_KEY=
10
+
11
+ # --- Database (any SQLAlchemy URL) ---
12
+ TERNO_DATABASE_URL=sqlite:///./demo.db
13
+ # TERNO_DATABASE_URL=postgresql+psycopg://user:pass@host:5432/dbname
14
+ # TERNO_DATABASE_URL=mysql+pymysql://user:pass@host:3306/dbname
15
+
16
+ # --- Sandbox ---
17
+ TERNO_SANDBOX=local # docker | local | none
18
+ TERNO_SANDBOX_IMAGE=python:3.12-slim # only used for the docker sandbox
19
+
20
+ # --- Behavior ---
21
+ TERNO_MAX_ROWS=200
22
+ TERNO_READ_ONLY_SQL=true # set to false to allow writes
@@ -0,0 +1,30 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info/
8
+
9
+ # Virtual environments
10
+ .venv/
11
+ venv/
12
+
13
+ # Tooling
14
+ .pytest_cache/
15
+ .mypy_cache/
16
+ .ruff_cache/
17
+ .coverage
18
+ htmlcov/
19
+
20
+ # Local config / secrets — never commit real .env files
21
+ .env
22
+ .env.*
23
+ !.env.example
24
+ config.local.toml
25
+
26
+ # Editors
27
+ .idea/
28
+ .vscode/
29
+ *.swp
30
+ .DS_Store
@@ -0,0 +1 @@
1
+ 3.12
Binary file
@@ -0,0 +1,317 @@
1
+ Metadata-Version: 2.4
2
+ Name: terno-agent
3
+ Version: 0.1.0
4
+ Summary: Multi-agent CLI that answers questions about your database and runs code in a sandbox.
5
+ Project-URL: Homepage, https://github.com/terno-ai/terno-agent
6
+ Project-URL: Issues, https://github.com/terno-ai/terno-agent/issues
7
+ Author: Sandeep Akode
8
+ License: MIT
9
+ Keywords: agent,ai,cli,database,llm,sandbox,sql
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Topic :: Database
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Requires-Python: >=3.12
19
+ Requires-Dist: pydantic>=2.6
20
+ Requires-Dist: python-dotenv>=1.0
21
+ Requires-Dist: rich>=13.7
22
+ Requires-Dist: sqlalchemy>=2.0
23
+ Provides-Extra: all
24
+ Requires-Dist: anthropic>=0.39; extra == 'all'
25
+ Requires-Dist: docker>=7.0; extra == 'all'
26
+ Requires-Dist: openai>=1.40; extra == 'all'
27
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'all'
28
+ Requires-Dist: pymysql>=1.1; extra == 'all'
29
+ Provides-Extra: anthropic
30
+ Requires-Dist: anthropic>=0.39; extra == 'anthropic'
31
+ Provides-Extra: dev
32
+ Requires-Dist: mypy>=1.10; extra == 'dev'
33
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
34
+ Requires-Dist: pytest>=8.0; extra == 'dev'
35
+ Requires-Dist: ruff>=0.5; extra == 'dev'
36
+ Provides-Extra: docker
37
+ Requires-Dist: docker>=7.0; extra == 'docker'
38
+ Provides-Extra: mysql
39
+ Requires-Dist: pymysql>=1.1; extra == 'mysql'
40
+ Provides-Extra: openai
41
+ Requires-Dist: openai>=1.40; extra == 'openai'
42
+ Provides-Extra: postgres
43
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
44
+ Description-Content-Type: text/markdown
45
+
46
+ # Terno Agent
47
+
48
+ A multi-agent CLI that answers questions about your database. It plans,
49
+ generates and executes SQL, and can write Python and run it in a sandbox
50
+ (Docker by default) to analyze results.
51
+
52
+ ## Features
53
+
54
+ - **Multi-agent**: an Orchestrator plans and delegates to a Database
55
+ specialist (SQL) and a Coder specialist (sandboxed Python).
56
+ - **Provider-agnostic LLM**: Anthropic Claude and OpenAI — pick at runtime.
57
+ - **Any database**: anything SQLAlchemy can talk to (Postgres, MySQL, SQLite,
58
+ …) via a single URL.
59
+ - **Sandboxed code execution**: Docker by default (`--network none`,
60
+ read-only rootfs, mem/CPU caps); local subprocess fallback for dev.
61
+ - **Read-only by default**: only `SELECT` / `WITH` / `EXPLAIN` allowed unless
62
+ you opt in.
63
+ - **Streaming + typed events**: assistant text streams live; tool calls and
64
+ results render with syntax-highlighted panels and result tables.
65
+ - **CLI + library**: `terno ask "..."` from the shell, or
66
+ `from terno_agent import Agent` in Python.
67
+
68
+ ## Architecture
69
+
70
+ ```
71
+ ┌────────────────────────┐
72
+ user query → │ Orchestrator │ ← planner: decomposes into steps
73
+ └────────────┬───────────┘ and routes to a specialist
74
+
75
+ ┌────────────┼────────────┐
76
+ ▼ ▼
77
+ ┌────────────────┐ ┌────────────────┐
78
+ │ DatabaseAgent │ │ CoderAgent │
79
+ │ • sql_query │ │ • run_python │
80
+ │ • list_tables │ │ (sandboxed) │
81
+ │ • describe │ │ │
82
+ └────────┬───────┘ └────────┬───────┘
83
+ │ │
84
+ ▼ ▼
85
+ SQLAlchemy engine Docker / local
86
+ (any DB URL) sandbox runner
87
+ ```
88
+
89
+ All cross-cutting boundaries are protocols, so each layer is swappable:
90
+
91
+ | Boundary | Protocol | Implementations |
92
+ | -------- | -------------- | ---------------------------- |
93
+ | LLM | `LLMClient` | Anthropic, OpenAI |
94
+ | Sandbox | `Sandbox` | Docker, local subprocess |
95
+ | Tool | `Tool` | sql_query, run_python, ... |
96
+ | Database | SQLAlchemy URL | Postgres, MySQL, SQLite, ... |
97
+
98
+ ## Install
99
+
100
+ You can install with either `uv` or plain `pip`. Both produce the same `terno`
101
+ CLI on your `PATH` and the same importable `terno_agent` package.
102
+
103
+ ### Optional extras
104
+
105
+ Pick only what you need (or use `all` to get everything):
106
+
107
+ | Extra | What it pulls in |
108
+ | ----------- | ------------------------------- |
109
+ | `anthropic` | the `anthropic` SDK |
110
+ | `openai` | the `openai` SDK |
111
+ | `docker` | the `docker` SDK for sandboxing |
112
+ | `postgres` | `psycopg[binary]` |
113
+ | `mysql` | `pymysql` |
114
+ | `all` | all of the above |
115
+ | `dev` | pytest, ruff, mypy |
116
+
117
+ ### With `uv` (recommended)
118
+
119
+ ```bash
120
+ # install globally as a uv tool — `terno` works from anywhere
121
+ uv tool install terno-agent
122
+ uv tool install "terno-agent[anthropic,docker,postgres]"
123
+
124
+ # editable install from a local checkout
125
+ git clone https://github.com/terno-ai/terno-agent.git
126
+ cd terno-agent
127
+ uv tool install --editable ".[all]"
128
+
129
+ # add it as a dependency of another uv project
130
+ uv add terno-agent
131
+ uv add "terno-agent[anthropic,docker]"
132
+
133
+ # from a local path or git
134
+ uv add /path/to/terno_agent
135
+ uv add "git+https://github.com/terno-ai/terno-agent.git"
136
+ ```
137
+
138
+ Refresh after changing `pyproject.toml`:
139
+
140
+ ```bash
141
+ uv tool install --editable ".[all]" --force
142
+ ```
143
+
144
+ ### With `pip`
145
+
146
+ ```bash
147
+ # from PyPI
148
+ pip install terno-agent
149
+ pip install "terno-agent[anthropic,docker,postgres]"
150
+
151
+ # from a local checkout (editable)
152
+ git clone https://github.com/terno-ai/terno-agent.git
153
+ cd terno-agent
154
+ python -m venv .venv && source .venv/bin/activate
155
+ pip install -e ".[all]"
156
+
157
+ # from git
158
+ pip install "git+https://github.com/terno-ai/terno-agent.git"
159
+
160
+ # from a built wheel
161
+ pip install ./dist/terno_agent-0.1.0-py3-none-any.whl
162
+ ```
163
+
164
+ > **Tip:** if you install into a project venv with plain `pip`, you have to
165
+ > activate the venv (or use its `bin/terno`) to run the CLI. `uv tool install`
166
+ > avoids this by giving the CLI its own isolated environment on `PATH`.
167
+
168
+ ## Configure
169
+
170
+ Configuration is read from environment variables, with `.env` auto-loaded from
171
+ your current working directory (or any parent). Process env wins over `.env`.
172
+
173
+ ```bash
174
+ cp .env.example .env
175
+ # then edit:
176
+ ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY=
177
+ TERNO_LLM_PROVIDER=anthropic # anthropic | openai
178
+ TERNO_LLM_MODEL=claude-opus-4-7
179
+ TERNO_DATABASE_URL=sqlite:///./demo.db
180
+ TERNO_SANDBOX=docker # docker | local | none
181
+ ```
182
+
183
+ Run `terno config` to print the effective settings (API keys masked).
184
+
185
+ ### SQLAlchemy URL examples
186
+
187
+ ```bash
188
+ sqlite:///./relative.db # relative to CWD
189
+ sqlite:////absolute/path/to.db # absolute (4 slashes)
190
+ postgresql+psycopg://user:pass@host:5432/db
191
+ mysql+pymysql://user:pass@host:3306/db
192
+ ```
193
+
194
+ ## Use the CLI
195
+
196
+ ```bash
197
+ # one-shot question
198
+ terno ask "what were the top 10 customers by revenue last quarter?"
199
+
200
+ # interactive REPL
201
+ terno chat
202
+
203
+ # suppress streaming/activity, print only the final answer
204
+ terno -q ask "how many tracks are in the database?"
205
+
206
+ # show effective config
207
+ terno config
208
+
209
+ # show version
210
+ terno --version
211
+ ```
212
+
213
+ If you installed with plain `pip` into a project venv and didn't activate it:
214
+
215
+ ```bash
216
+ .venv/bin/terno ask "..."
217
+ # or
218
+ python -m terno_agent ask "..."
219
+ ```
220
+
221
+ If you installed with `uv` into the current project rather than as a tool:
222
+
223
+ ```bash
224
+ uv run terno ask "..."
225
+ ```
226
+
227
+ ## Use as a library
228
+
229
+ ```python
230
+ from terno_agent import Agent
231
+
232
+ # Reads .env + env vars
233
+ agent = Agent.from_env()
234
+ result = agent.ask("how many active users signed up this week?")
235
+ print(result.answer)
236
+ ```
237
+
238
+ Programmatic config (no env vars required):
239
+
240
+ ```python
241
+ from terno_agent import Agent
242
+ from terno_agent.config import Config
243
+
244
+ cfg = Config(
245
+ llm_provider="anthropic",
246
+ llm_model="claude-opus-4-7",
247
+ llm_api_key="sk-ant-...",
248
+ database_url="postgresql+psycopg://u:p@host/db",
249
+ sandbox="local", # "docker" | "local" | "none"
250
+ )
251
+ agent = Agent.from_config(cfg)
252
+ print(agent.ask("top 5 tables by row count").answer)
253
+ ```
254
+
255
+ Stream events into your own UI:
256
+
257
+ ```python
258
+ from terno_agent import Agent
259
+ from terno_agent.core.events import TextDelta, ToolCallEvent, ToolResultEvent
260
+
261
+ def on_event(e):
262
+ if isinstance(e, TextDelta):
263
+ print(e.text, end="", flush=True)
264
+ elif isinstance(e, ToolCallEvent):
265
+ print(f"\n[tool] {e.call.name}({e.call.arguments})")
266
+ elif isinstance(e, ToolResultEvent):
267
+ print(f"[result] {e.result.content[:200]}")
268
+
269
+ agent = Agent.from_env(on_event=on_event)
270
+ agent.ask("describe the users table and count rows")
271
+ ```
272
+
273
+ ## Project layout
274
+
275
+ ```
276
+ src/terno_agent/
277
+ cli.py # argparse entry point + rich renderer
278
+ config.py # env + .env-driven Config
279
+ core/ # message/tool/event/exception types
280
+ llm/ # LLMClient protocol + Anthropic + OpenAI (streaming)
281
+ agents/ # orchestrator + specialist agents
282
+ tools/ # sql_query, run_python, list_tables, describe_table
283
+ sandbox/ # Docker + local runners
284
+ db/ # SQLAlchemy engine & inspector
285
+ prompts/ # system prompts per agent
286
+ tests/ # pytest suite
287
+ ```
288
+
289
+ ## Develop
290
+
291
+ ```bash
292
+ # clone + editable install with dev extras
293
+ git clone https://github.com/terno-ai/terno-agent.git
294
+ cd terno-agent
295
+ uv venv --python 3.12
296
+ uv pip install -e ".[dev,all]"
297
+
298
+ # tests
299
+ uv run pytest -q
300
+
301
+ # lint / format / typecheck
302
+ uv run ruff check .
303
+ uv run ruff format .
304
+ uv run mypy src
305
+ ```
306
+
307
+ Or with plain `pip`:
308
+
309
+ ```bash
310
+ python -m venv .venv && source .venv/bin/activate
311
+ pip install -e ".[dev,all]"
312
+ pytest -q
313
+ ```
314
+
315
+ ## License
316
+
317
+ MIT
@@ -0,0 +1,272 @@
1
+ # Terno Agent
2
+
3
+ A multi-agent CLI that answers questions about your database. It plans,
4
+ generates and executes SQL, and can write Python and run it in a sandbox
5
+ (Docker by default) to analyze results.
6
+
7
+ ## Features
8
+
9
+ - **Multi-agent**: an Orchestrator plans and delegates to a Database
10
+ specialist (SQL) and a Coder specialist (sandboxed Python).
11
+ - **Provider-agnostic LLM**: Anthropic Claude and OpenAI — pick at runtime.
12
+ - **Any database**: anything SQLAlchemy can talk to (Postgres, MySQL, SQLite,
13
+ …) via a single URL.
14
+ - **Sandboxed code execution**: Docker by default (`--network none`,
15
+ read-only rootfs, mem/CPU caps); local subprocess fallback for dev.
16
+ - **Read-only by default**: only `SELECT` / `WITH` / `EXPLAIN` allowed unless
17
+ you opt in.
18
+ - **Streaming + typed events**: assistant text streams live; tool calls and
19
+ results render with syntax-highlighted panels and result tables.
20
+ - **CLI + library**: `terno ask "..."` from the shell, or
21
+ `from terno_agent import Agent` in Python.
22
+
23
+ ## Architecture
24
+
25
+ ```
26
+ ┌────────────────────────┐
27
+ user query → │ Orchestrator │ ← planner: decomposes into steps
28
+ └────────────┬───────────┘ and routes to a specialist
29
+
30
+ ┌────────────┼────────────┐
31
+ ▼ ▼
32
+ ┌────────────────┐ ┌────────────────┐
33
+ │ DatabaseAgent │ │ CoderAgent │
34
+ │ • sql_query │ │ • run_python │
35
+ │ • list_tables │ │ (sandboxed) │
36
+ │ • describe │ │ │
37
+ └────────┬───────┘ └────────┬───────┘
38
+ │ │
39
+ ▼ ▼
40
+ SQLAlchemy engine Docker / local
41
+ (any DB URL) sandbox runner
42
+ ```
43
+
44
+ All cross-cutting boundaries are protocols, so each layer is swappable:
45
+
46
+ | Boundary | Protocol | Implementations |
47
+ | -------- | -------------- | ---------------------------- |
48
+ | LLM | `LLMClient` | Anthropic, OpenAI |
49
+ | Sandbox | `Sandbox` | Docker, local subprocess |
50
+ | Tool | `Tool` | sql_query, run_python, ... |
51
+ | Database | SQLAlchemy URL | Postgres, MySQL, SQLite, ... |
52
+
53
+ ## Install
54
+
55
+ You can install with either `uv` or plain `pip`. Both produce the same `terno`
56
+ CLI on your `PATH` and the same importable `terno_agent` package.
57
+
58
+ ### Optional extras
59
+
60
+ Pick only what you need (or use `all` to get everything):
61
+
62
+ | Extra | What it pulls in |
63
+ | ----------- | ------------------------------- |
64
+ | `anthropic` | the `anthropic` SDK |
65
+ | `openai` | the `openai` SDK |
66
+ | `docker` | the `docker` SDK for sandboxing |
67
+ | `postgres` | `psycopg[binary]` |
68
+ | `mysql` | `pymysql` |
69
+ | `all` | all of the above |
70
+ | `dev` | pytest, ruff, mypy |
71
+
72
+ ### With `uv` (recommended)
73
+
74
+ ```bash
75
+ # install globally as a uv tool — `terno` works from anywhere
76
+ uv tool install terno-agent
77
+ uv tool install "terno-agent[anthropic,docker,postgres]"
78
+
79
+ # editable install from a local checkout
80
+ git clone https://github.com/terno-ai/terno-agent.git
81
+ cd terno-agent
82
+ uv tool install --editable ".[all]"
83
+
84
+ # add it as a dependency of another uv project
85
+ uv add terno-agent
86
+ uv add "terno-agent[anthropic,docker]"
87
+
88
+ # from a local path or git
89
+ uv add /path/to/terno_agent
90
+ uv add "git+https://github.com/terno-ai/terno-agent.git"
91
+ ```
92
+
93
+ Refresh after changing `pyproject.toml`:
94
+
95
+ ```bash
96
+ uv tool install --editable ".[all]" --force
97
+ ```
98
+
99
+ ### With `pip`
100
+
101
+ ```bash
102
+ # from PyPI
103
+ pip install terno-agent
104
+ pip install "terno-agent[anthropic,docker,postgres]"
105
+
106
+ # from a local checkout (editable)
107
+ git clone https://github.com/terno-ai/terno-agent.git
108
+ cd terno-agent
109
+ python -m venv .venv && source .venv/bin/activate
110
+ pip install -e ".[all]"
111
+
112
+ # from git
113
+ pip install "git+https://github.com/terno-ai/terno-agent.git"
114
+
115
+ # from a built wheel
116
+ pip install ./dist/terno_agent-0.1.0-py3-none-any.whl
117
+ ```
118
+
119
+ > **Tip:** if you install into a project venv with plain `pip`, you have to
120
+ > activate the venv (or use its `bin/terno`) to run the CLI. `uv tool install`
121
+ > avoids this by giving the CLI its own isolated environment on `PATH`.
122
+
123
+ ## Configure
124
+
125
+ Configuration is read from environment variables, with `.env` auto-loaded from
126
+ your current working directory (or any parent). Process env wins over `.env`.
127
+
128
+ ```bash
129
+ cp .env.example .env
130
+ # then edit:
131
+ ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY=
132
+ TERNO_LLM_PROVIDER=anthropic # anthropic | openai
133
+ TERNO_LLM_MODEL=claude-opus-4-7
134
+ TERNO_DATABASE_URL=sqlite:///./demo.db
135
+ TERNO_SANDBOX=docker # docker | local | none
136
+ ```
137
+
138
+ Run `terno config` to print the effective settings (API keys masked).
139
+
140
+ ### SQLAlchemy URL examples
141
+
142
+ ```bash
143
+ sqlite:///./relative.db # relative to CWD
144
+ sqlite:////absolute/path/to.db # absolute (4 slashes)
145
+ postgresql+psycopg://user:pass@host:5432/db
146
+ mysql+pymysql://user:pass@host:3306/db
147
+ ```
148
+
149
+ ## Use the CLI
150
+
151
+ ```bash
152
+ # one-shot question
153
+ terno ask "what were the top 10 customers by revenue last quarter?"
154
+
155
+ # interactive REPL
156
+ terno chat
157
+
158
+ # suppress streaming/activity, print only the final answer
159
+ terno -q ask "how many tracks are in the database?"
160
+
161
+ # show effective config
162
+ terno config
163
+
164
+ # show version
165
+ terno --version
166
+ ```
167
+
168
+ If you installed with plain `pip` into a project venv and didn't activate it:
169
+
170
+ ```bash
171
+ .venv/bin/terno ask "..."
172
+ # or
173
+ python -m terno_agent ask "..."
174
+ ```
175
+
176
+ If you installed with `uv` into the current project rather than as a tool:
177
+
178
+ ```bash
179
+ uv run terno ask "..."
180
+ ```
181
+
182
+ ## Use as a library
183
+
184
+ ```python
185
+ from terno_agent import Agent
186
+
187
+ # Reads .env + env vars
188
+ agent = Agent.from_env()
189
+ result = agent.ask("how many active users signed up this week?")
190
+ print(result.answer)
191
+ ```
192
+
193
+ Programmatic config (no env vars required):
194
+
195
+ ```python
196
+ from terno_agent import Agent
197
+ from terno_agent.config import Config
198
+
199
+ cfg = Config(
200
+ llm_provider="anthropic",
201
+ llm_model="claude-opus-4-7",
202
+ llm_api_key="sk-ant-...",
203
+ database_url="postgresql+psycopg://u:p@host/db",
204
+ sandbox="local", # "docker" | "local" | "none"
205
+ )
206
+ agent = Agent.from_config(cfg)
207
+ print(agent.ask("top 5 tables by row count").answer)
208
+ ```
209
+
210
+ Stream events into your own UI:
211
+
212
+ ```python
213
+ from terno_agent import Agent
214
+ from terno_agent.core.events import TextDelta, ToolCallEvent, ToolResultEvent
215
+
216
+ def on_event(e):
217
+ if isinstance(e, TextDelta):
218
+ print(e.text, end="", flush=True)
219
+ elif isinstance(e, ToolCallEvent):
220
+ print(f"\n[tool] {e.call.name}({e.call.arguments})")
221
+ elif isinstance(e, ToolResultEvent):
222
+ print(f"[result] {e.result.content[:200]}")
223
+
224
+ agent = Agent.from_env(on_event=on_event)
225
+ agent.ask("describe the users table and count rows")
226
+ ```
227
+
228
+ ## Project layout
229
+
230
+ ```
231
+ src/terno_agent/
232
+ cli.py # argparse entry point + rich renderer
233
+ config.py # env + .env-driven Config
234
+ core/ # message/tool/event/exception types
235
+ llm/ # LLMClient protocol + Anthropic + OpenAI (streaming)
236
+ agents/ # orchestrator + specialist agents
237
+ tools/ # sql_query, run_python, list_tables, describe_table
238
+ sandbox/ # Docker + local runners
239
+ db/ # SQLAlchemy engine & inspector
240
+ prompts/ # system prompts per agent
241
+ tests/ # pytest suite
242
+ ```
243
+
244
+ ## Develop
245
+
246
+ ```bash
247
+ # clone + editable install with dev extras
248
+ git clone https://github.com/terno-ai/terno-agent.git
249
+ cd terno-agent
250
+ uv venv --python 3.12
251
+ uv pip install -e ".[dev,all]"
252
+
253
+ # tests
254
+ uv run pytest -q
255
+
256
+ # lint / format / typecheck
257
+ uv run ruff check .
258
+ uv run ruff format .
259
+ uv run mypy src
260
+ ```
261
+
262
+ Or with plain `pip`:
263
+
264
+ ```bash
265
+ python -m venv .venv && source .venv/bin/activate
266
+ pip install -e ".[dev,all]"
267
+ pytest -q
268
+ ```
269
+
270
+ ## License
271
+
272
+ MIT