nao-core 0.0.9__tar.gz → 0.0.11__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.
- nao_core-0.0.11/PKG-INFO +130 -0
- nao_core-0.0.11/README.md +101 -0
- {nao_core-0.0.9 → nao_core-0.0.11}/nao_core/__init__.py +1 -2
- nao_core-0.0.11/nao_core/commands/__init__.py +6 -0
- nao_core-0.0.11/nao_core/commands/chat.py +132 -0
- nao_core-0.0.11/nao_core/commands/debug.py +144 -0
- nao_core-0.0.11/nao_core/commands/init.py +175 -0
- nao_core-0.0.11/nao_core/commands/sync.py +150 -0
- nao_core-0.0.11/nao_core/config.py +136 -0
- {nao_core-0.0.9 → nao_core-0.0.11}/nao_core/main.py +4 -2
- {nao_core-0.0.9 → nao_core-0.0.11}/pyproject.toml +10 -2
- nao_core-0.0.9/PKG-INFO +0 -93
- nao_core-0.0.9/README.md +0 -66
- nao_core-0.0.9/nao_core/commands/__init__.py +0 -5
- nao_core-0.0.9/nao_core/commands/chat.py +0 -140
- nao_core-0.0.9/nao_core/commands/init.py +0 -72
- nao_core-0.0.9/nao_core/config.py +0 -64
- {nao_core-0.0.9 → nao_core-0.0.11}/.gitignore +0 -0
- {nao_core-0.0.9 → nao_core-0.0.11}/LICENSE +0 -0
- {nao_core-0.0.9 → nao_core-0.0.11}/nao_core/bin/db.sqlite +0 -0
- {nao_core-0.0.9 → nao_core-0.0.11}/nao_core/bin/nao-chat-server +0 -0
- {nao_core-0.0.9 → nao_core-0.0.11}/nao_core/bin/public/assets/index-BSxC58nD.js +0 -0
- {nao_core-0.0.9 → nao_core-0.0.11}/nao_core/bin/public/assets/index-Dh3br3Ia.js +0 -0
- {nao_core-0.0.9 → nao_core-0.0.11}/nao_core/bin/public/assets/index-heKLHGGE.css +0 -0
- {nao_core-0.0.9 → nao_core-0.0.11}/nao_core/bin/public/favicon.ico +0 -0
- {nao_core-0.0.9 → nao_core-0.0.11}/nao_core/bin/public/index.html +0 -0
nao_core-0.0.11/PKG-INFO
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nao-core
|
|
3
|
+
Version: 0.0.11
|
|
4
|
+
Summary: nao Core is your analytics context builder with the best chat interface.
|
|
5
|
+
Project-URL: Homepage, https://getnao.io
|
|
6
|
+
Project-URL: Repository, https://github.com/naolabs/chat
|
|
7
|
+
Author: nao Labs
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ai,analytics,chat
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: cyclopts>=4.4.4
|
|
23
|
+
Requires-Dist: ibis-framework[bigquery]>=9.0.0
|
|
24
|
+
Requires-Dist: openai>=1.0.0
|
|
25
|
+
Requires-Dist: pydantic>=2.10.0
|
|
26
|
+
Requires-Dist: pyyaml>=6.0.0
|
|
27
|
+
Requires-Dist: rich>=14.0.0
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# nao CLI
|
|
31
|
+
|
|
32
|
+
Command-line interface for nao chat.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install nao-core
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
nao --help
|
|
44
|
+
Usage: nao COMMAND
|
|
45
|
+
|
|
46
|
+
╭─ Commands ────────────────────────────────────────────────────────────────╮
|
|
47
|
+
│ chat Start the nao chat UI. │
|
|
48
|
+
│ init Initialize a new nao project. │
|
|
49
|
+
│ --help (-h) Display this message and exit. │
|
|
50
|
+
│ --version Display application version. │
|
|
51
|
+
╰───────────────────────────────────────────────────────────────────────────╯
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Initialize a new nao project
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
nao init
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This will create a new nao project in the current directory. It will prompt you for a project name and ask you if you want to set up an LLM configuration.
|
|
61
|
+
|
|
62
|
+
### Start the nao chat UI
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
nao chat
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This will start the nao chat UI. It will open the chat interface in your browser at `http://localhost:5005`.
|
|
69
|
+
|
|
70
|
+
## Development
|
|
71
|
+
|
|
72
|
+
### Building the package
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
cd cli
|
|
76
|
+
python build.py --help
|
|
77
|
+
Usage: build.py [OPTIONS]
|
|
78
|
+
|
|
79
|
+
Build and package nao-core CLI.
|
|
80
|
+
|
|
81
|
+
╭─ Commands ────────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
82
|
+
│ --help (-h) Display this message and exit. │
|
|
83
|
+
│ --version Display application version. │
|
|
84
|
+
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
85
|
+
╭─ Parameters ──────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
86
|
+
│ --force -f --no-force Force rebuild the server binary [default: False] │
|
|
87
|
+
│ --skip-server -s --no-skip-server Skip server build, only build Python package [default: False] │
|
|
88
|
+
│ --bump Bump version before building (patch, minor, major) [choices: patch, │
|
|
89
|
+
│ minor, major] │
|
|
90
|
+
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This will:
|
|
94
|
+
1. Build the frontend with Vite
|
|
95
|
+
2. Compile the backend with Bun into a standalone binary
|
|
96
|
+
3. Bundle everything into a Python wheel in `dist/`
|
|
97
|
+
|
|
98
|
+
Options:
|
|
99
|
+
- `--force` / `-f`: Force rebuild the server binary
|
|
100
|
+
- `--skip-server`: Skip server build, only build Python package
|
|
101
|
+
- `--bump`: Bump version before building (patch, minor, major)
|
|
102
|
+
|
|
103
|
+
### Installing for development
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
cd cli
|
|
107
|
+
pip install -e .
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Publishing to PyPI
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Build first
|
|
114
|
+
python build.py
|
|
115
|
+
|
|
116
|
+
# Publish
|
|
117
|
+
uv publish dist/*
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Architecture
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
nao chat (CLI command)
|
|
124
|
+
↓ spawns
|
|
125
|
+
nao-chat-server (Bun-compiled binary)
|
|
126
|
+
↓ serves
|
|
127
|
+
Backend API + Frontend Static Files
|
|
128
|
+
↓
|
|
129
|
+
Browser at http://localhost:5005
|
|
130
|
+
```
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# nao CLI
|
|
2
|
+
|
|
3
|
+
Command-line interface for nao chat.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install nao-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
nao --help
|
|
15
|
+
Usage: nao COMMAND
|
|
16
|
+
|
|
17
|
+
╭─ Commands ────────────────────────────────────────────────────────────────╮
|
|
18
|
+
│ chat Start the nao chat UI. │
|
|
19
|
+
│ init Initialize a new nao project. │
|
|
20
|
+
│ --help (-h) Display this message and exit. │
|
|
21
|
+
│ --version Display application version. │
|
|
22
|
+
╰───────────────────────────────────────────────────────────────────────────╯
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Initialize a new nao project
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
nao init
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This will create a new nao project in the current directory. It will prompt you for a project name and ask you if you want to set up an LLM configuration.
|
|
32
|
+
|
|
33
|
+
### Start the nao chat UI
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
nao chat
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This will start the nao chat UI. It will open the chat interface in your browser at `http://localhost:5005`.
|
|
40
|
+
|
|
41
|
+
## Development
|
|
42
|
+
|
|
43
|
+
### Building the package
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cd cli
|
|
47
|
+
python build.py --help
|
|
48
|
+
Usage: build.py [OPTIONS]
|
|
49
|
+
|
|
50
|
+
Build and package nao-core CLI.
|
|
51
|
+
|
|
52
|
+
╭─ Commands ────────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
53
|
+
│ --help (-h) Display this message and exit. │
|
|
54
|
+
│ --version Display application version. │
|
|
55
|
+
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
56
|
+
╭─ Parameters ──────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
57
|
+
│ --force -f --no-force Force rebuild the server binary [default: False] │
|
|
58
|
+
│ --skip-server -s --no-skip-server Skip server build, only build Python package [default: False] │
|
|
59
|
+
│ --bump Bump version before building (patch, minor, major) [choices: patch, │
|
|
60
|
+
│ minor, major] │
|
|
61
|
+
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This will:
|
|
65
|
+
1. Build the frontend with Vite
|
|
66
|
+
2. Compile the backend with Bun into a standalone binary
|
|
67
|
+
3. Bundle everything into a Python wheel in `dist/`
|
|
68
|
+
|
|
69
|
+
Options:
|
|
70
|
+
- `--force` / `-f`: Force rebuild the server binary
|
|
71
|
+
- `--skip-server`: Skip server build, only build Python package
|
|
72
|
+
- `--bump`: Bump version before building (patch, minor, major)
|
|
73
|
+
|
|
74
|
+
### Installing for development
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
cd cli
|
|
78
|
+
pip install -e .
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Publishing to PyPI
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Build first
|
|
85
|
+
python build.py
|
|
86
|
+
|
|
87
|
+
# Publish
|
|
88
|
+
uv publish dist/*
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Architecture
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
nao chat (CLI command)
|
|
95
|
+
↓ spawns
|
|
96
|
+
nao-chat-server (Bun-compiled binary)
|
|
97
|
+
↓ serves
|
|
98
|
+
Backend API + Frontend Static Files
|
|
99
|
+
↓
|
|
100
|
+
Browser at http://localhost:5005
|
|
101
|
+
```
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
import webbrowser
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from time import sleep
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from nao_core.config import NaoConfig
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
# Default port for the nao chat server
|
|
15
|
+
SERVER_PORT = 5005
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_server_binary_path() -> Path:
|
|
19
|
+
"""Get the path to the bundled nao-chat-server binary."""
|
|
20
|
+
# The binary is in the bin folder relative to this file
|
|
21
|
+
cli_dir = Path(__file__).parent.parent
|
|
22
|
+
bin_dir = cli_dir / "bin"
|
|
23
|
+
binary_path = bin_dir / "nao-chat-server"
|
|
24
|
+
|
|
25
|
+
if not binary_path.exists():
|
|
26
|
+
console.print(f"[bold red]✗[/bold red] Server binary not found at {binary_path}")
|
|
27
|
+
console.print("[dim]Make sure you've built the server with ./scripts/build-server.sh[/dim]")
|
|
28
|
+
sys.exit(1)
|
|
29
|
+
|
|
30
|
+
return binary_path
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def wait_for_server(port: int, timeout: int = 30) -> bool:
|
|
34
|
+
"""Wait for the server to be ready."""
|
|
35
|
+
import socket
|
|
36
|
+
|
|
37
|
+
for _ in range(timeout * 10): # Check every 100ms
|
|
38
|
+
try:
|
|
39
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
40
|
+
sock.settimeout(0.1)
|
|
41
|
+
result = sock.connect_ex(("localhost", port))
|
|
42
|
+
if result == 0:
|
|
43
|
+
return True
|
|
44
|
+
except OSError:
|
|
45
|
+
pass
|
|
46
|
+
sleep(0.1)
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def chat():
|
|
51
|
+
"""Start the nao chat UI.
|
|
52
|
+
|
|
53
|
+
Launches the nao chat server and opens the web interface in your browser.
|
|
54
|
+
"""
|
|
55
|
+
console.print("\n[bold cyan]💬 Starting nao chat...[/bold cyan]\n")
|
|
56
|
+
|
|
57
|
+
binary_path = get_server_binary_path()
|
|
58
|
+
bin_dir = binary_path.parent
|
|
59
|
+
|
|
60
|
+
console.print(f"[dim]Server binary: {binary_path}[/dim]")
|
|
61
|
+
console.print(f"[dim]Working directory: {bin_dir}[/dim]")
|
|
62
|
+
|
|
63
|
+
# Try to load nao config from current directory
|
|
64
|
+
config = NaoConfig.try_load()
|
|
65
|
+
if config:
|
|
66
|
+
console.print(f"[bold green]✓[/bold green] Loaded config from {Path.cwd() / 'nao_config.yaml'}")
|
|
67
|
+
else:
|
|
68
|
+
console.print("[dim]No nao_config.yaml found in current directory[/dim]")
|
|
69
|
+
|
|
70
|
+
# Start the server process
|
|
71
|
+
process = None
|
|
72
|
+
try:
|
|
73
|
+
# Set up environment - inherit from parent but ensure we're in the bin dir
|
|
74
|
+
# so the server can find the public folder
|
|
75
|
+
env = os.environ.copy()
|
|
76
|
+
|
|
77
|
+
# Set LLM API key from config if available
|
|
78
|
+
if config and config.llm:
|
|
79
|
+
env_var_name = f"{config.llm.provider.upper()}_API_KEY"
|
|
80
|
+
env[env_var_name] = config.llm.api_key
|
|
81
|
+
console.print(f"[bold green]✓[/bold green] Set {env_var_name} from config")
|
|
82
|
+
|
|
83
|
+
process = subprocess.Popen(
|
|
84
|
+
[str(binary_path)],
|
|
85
|
+
cwd=str(bin_dir),
|
|
86
|
+
env=env,
|
|
87
|
+
stdout=subprocess.PIPE,
|
|
88
|
+
stderr=subprocess.STDOUT,
|
|
89
|
+
text=True,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
console.print("[bold green]✓[/bold green] Server starting...")
|
|
93
|
+
|
|
94
|
+
# Wait for the server to be ready
|
|
95
|
+
if wait_for_server(SERVER_PORT):
|
|
96
|
+
url = f"http://localhost:{SERVER_PORT}"
|
|
97
|
+
console.print(f"[bold green]✓[/bold green] Server ready at {url}")
|
|
98
|
+
console.print("\n[bold]Opening browser...[/bold]")
|
|
99
|
+
webbrowser.open(url)
|
|
100
|
+
console.print("\n[dim]Press Ctrl+C to stop the server[/dim]\n")
|
|
101
|
+
else:
|
|
102
|
+
console.print("[bold yellow]⚠[/bold yellow] Server is taking longer than expected to start...")
|
|
103
|
+
console.print(f"[dim]Check http://localhost:{SERVER_PORT} manually[/dim]")
|
|
104
|
+
|
|
105
|
+
# Stream server output to console
|
|
106
|
+
if process.stdout:
|
|
107
|
+
for line in process.stdout:
|
|
108
|
+
# Filter out some of the verbose logging if needed
|
|
109
|
+
console.print(f"[dim]{line.rstrip()}[/dim]")
|
|
110
|
+
|
|
111
|
+
# Wait for process to complete
|
|
112
|
+
process.wait()
|
|
113
|
+
|
|
114
|
+
except KeyboardInterrupt:
|
|
115
|
+
console.print("\n[bold yellow]Shutting down...[/bold yellow]")
|
|
116
|
+
if process:
|
|
117
|
+
# Send SIGTERM for graceful shutdown
|
|
118
|
+
process.terminate()
|
|
119
|
+
try:
|
|
120
|
+
process.wait(timeout=5)
|
|
121
|
+
except subprocess.TimeoutExpired:
|
|
122
|
+
# Force kill if it doesn't respond
|
|
123
|
+
process.kill()
|
|
124
|
+
process.wait()
|
|
125
|
+
console.print("[bold green]✓[/bold green] Server stopped")
|
|
126
|
+
sys.exit(0)
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
console.print(f"[bold red]✗[/bold red] Failed to start server: {e}")
|
|
130
|
+
if process:
|
|
131
|
+
process.kill()
|
|
132
|
+
sys.exit(1)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
|
|
6
|
+
from nao_core.config import NaoConfig
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_database_connection(db_config) -> tuple[bool, str]:
|
|
12
|
+
"""Test connectivity to a database.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Tuple of (success, message)
|
|
16
|
+
"""
|
|
17
|
+
try:
|
|
18
|
+
conn = db_config.connect()
|
|
19
|
+
# Run a simple query to verify the connection works
|
|
20
|
+
if db_config.dataset_id:
|
|
21
|
+
# If dataset is specified, list tables in that dataset
|
|
22
|
+
tables = conn.list_tables()
|
|
23
|
+
table_count = len(tables)
|
|
24
|
+
return True, f"Connected successfully ({table_count} tables found)"
|
|
25
|
+
else:
|
|
26
|
+
# If no dataset, list datasets in the project instead
|
|
27
|
+
datasets = conn.list_databases()
|
|
28
|
+
dataset_count = len(datasets)
|
|
29
|
+
return True, f"Connected successfully ({dataset_count} datasets found)"
|
|
30
|
+
except Exception as e:
|
|
31
|
+
return False, str(e)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_llm_connection(llm_config) -> tuple[bool, str]:
|
|
35
|
+
"""Test connectivity to an LLM provider.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Tuple of (success, message)
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
if llm_config.provider.value == "openai":
|
|
42
|
+
import openai
|
|
43
|
+
|
|
44
|
+
client = openai.OpenAI(api_key=llm_config.api_key)
|
|
45
|
+
# Make a minimal API call to verify the key works
|
|
46
|
+
models = client.models.list()
|
|
47
|
+
# Just check we can iterate (don't need to consume all)
|
|
48
|
+
model_count = sum(1 for _ in models)
|
|
49
|
+
return True, f"Connected successfully ({model_count} models available)"
|
|
50
|
+
else:
|
|
51
|
+
return False, f"Unknown provider: {llm_config.provider}"
|
|
52
|
+
except Exception as e:
|
|
53
|
+
return False, str(e)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def debug():
|
|
57
|
+
"""Test connectivity to configured databases and LLMs.
|
|
58
|
+
|
|
59
|
+
Loads the nao configuration from the current directory and tests
|
|
60
|
+
connections to all configured databases and LLM providers.
|
|
61
|
+
"""
|
|
62
|
+
console.print("\n[bold cyan]🔍 nao debug - Testing connections...[/bold cyan]\n")
|
|
63
|
+
|
|
64
|
+
# Load config
|
|
65
|
+
config = NaoConfig.try_load()
|
|
66
|
+
if not config:
|
|
67
|
+
console.print("[bold red]✗[/bold red] No nao_config.yaml found in current directory")
|
|
68
|
+
console.print("[dim]Run 'nao init' to create a configuration file[/dim]")
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
|
|
71
|
+
console.print(f"[bold green]✓[/bold green] Loaded config: [cyan]{config.project_name}[/cyan]\n")
|
|
72
|
+
|
|
73
|
+
# Test databases
|
|
74
|
+
if config.databases:
|
|
75
|
+
console.print("[bold]Databases:[/bold]")
|
|
76
|
+
db_table = Table(show_header=True, header_style="bold")
|
|
77
|
+
db_table.add_column("Name")
|
|
78
|
+
db_table.add_column("Type")
|
|
79
|
+
db_table.add_column("Status")
|
|
80
|
+
db_table.add_column("Details")
|
|
81
|
+
|
|
82
|
+
for db in config.databases:
|
|
83
|
+
console.print(f" Testing [cyan]{db.name}[/cyan]...", end=" ")
|
|
84
|
+
success, message = test_database_connection(db)
|
|
85
|
+
|
|
86
|
+
if success:
|
|
87
|
+
console.print("[bold green]✓[/bold green]")
|
|
88
|
+
db_table.add_row(
|
|
89
|
+
db.name,
|
|
90
|
+
db.type,
|
|
91
|
+
"[green]Connected[/green]",
|
|
92
|
+
message,
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
console.print("[bold red]✗[/bold red]")
|
|
96
|
+
# Truncate long error messages
|
|
97
|
+
short_msg = message[:80] + "..." if len(message) > 80 else message
|
|
98
|
+
db_table.add_row(
|
|
99
|
+
db.name,
|
|
100
|
+
db.type,
|
|
101
|
+
"[red]Failed[/red]",
|
|
102
|
+
short_msg,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
console.print()
|
|
106
|
+
console.print(db_table)
|
|
107
|
+
else:
|
|
108
|
+
console.print("[dim]No databases configured[/dim]")
|
|
109
|
+
|
|
110
|
+
console.print()
|
|
111
|
+
|
|
112
|
+
# Test LLM
|
|
113
|
+
if config.llm:
|
|
114
|
+
console.print("[bold]LLM Provider:[/bold]")
|
|
115
|
+
llm_table = Table(show_header=True, header_style="bold")
|
|
116
|
+
llm_table.add_column("Provider")
|
|
117
|
+
llm_table.add_column("Status")
|
|
118
|
+
llm_table.add_column("Details")
|
|
119
|
+
|
|
120
|
+
console.print(f" Testing [cyan]{config.llm.provider.value}[/cyan]...", end=" ")
|
|
121
|
+
success, message = test_llm_connection(config.llm)
|
|
122
|
+
|
|
123
|
+
if success:
|
|
124
|
+
console.print("[bold green]✓[/bold green]")
|
|
125
|
+
llm_table.add_row(
|
|
126
|
+
config.llm.provider.value,
|
|
127
|
+
"[green]Connected[/green]",
|
|
128
|
+
message,
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
console.print("[bold red]✗[/bold red]")
|
|
132
|
+
short_msg = message[:80] + "..." if len(message) > 80 else message
|
|
133
|
+
llm_table.add_row(
|
|
134
|
+
config.llm.provider.value,
|
|
135
|
+
"[red]Failed[/red]",
|
|
136
|
+
short_msg,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
console.print()
|
|
140
|
+
console.print(llm_table)
|
|
141
|
+
else:
|
|
142
|
+
console.print("[dim]No LLM configured[/dim]")
|
|
143
|
+
|
|
144
|
+
console.print()
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
|
|
4
|
+
from cyclopts import Parameter
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.prompt import Confirm, Prompt
|
|
7
|
+
|
|
8
|
+
from nao_core.config import BigQueryConfig, DatabaseConfig, DatabaseType, LLMConfig, LLMProvider, NaoConfig
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InitError(Exception):
|
|
14
|
+
"""Base exception for init command errors."""
|
|
15
|
+
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EmptyProjectNameError(InitError):
|
|
20
|
+
"""Raised when project name is empty."""
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
super().__init__("Project name cannot be empty.")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ProjectExistsError(InitError):
|
|
27
|
+
"""Raised when project folder already exists."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, project_name: str):
|
|
30
|
+
self.project_name = project_name
|
|
31
|
+
super().__init__(f"Folder '{project_name}' already exists.")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class EmptyApiKeyError(InitError):
|
|
35
|
+
"""Raised when API key is empty."""
|
|
36
|
+
|
|
37
|
+
def __init__(self):
|
|
38
|
+
super().__init__("API key cannot be empty.")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def setup_project_name(force: bool = False) -> tuple[str, Path]:
|
|
42
|
+
"""Setup the project name."""
|
|
43
|
+
project_name = Prompt.ask("[bold]Enter your project name[/bold]")
|
|
44
|
+
|
|
45
|
+
if not project_name:
|
|
46
|
+
raise EmptyProjectNameError()
|
|
47
|
+
|
|
48
|
+
project_path = Path(project_name)
|
|
49
|
+
|
|
50
|
+
if project_path.exists() and not force:
|
|
51
|
+
raise ProjectExistsError(project_name)
|
|
52
|
+
|
|
53
|
+
project_path.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
|
|
55
|
+
return project_name, project_path
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def setup_bigquery() -> BigQueryConfig:
|
|
59
|
+
"""Setup a BigQuery database configuration."""
|
|
60
|
+
console.print("\n[bold cyan]BigQuery Configuration[/bold cyan]\n")
|
|
61
|
+
|
|
62
|
+
name = Prompt.ask("[bold]Connection name[/bold]", default="bigquery-prod")
|
|
63
|
+
|
|
64
|
+
project_id = Prompt.ask("[bold]GCP Project ID[/bold]")
|
|
65
|
+
if not project_id:
|
|
66
|
+
raise InitError("GCP Project ID cannot be empty.")
|
|
67
|
+
|
|
68
|
+
dataset_id = Prompt.ask("[bold]Default dataset[/bold] [dim](optional, press Enter to skip)[/dim]", default="")
|
|
69
|
+
|
|
70
|
+
credentials_path = Prompt.ask(
|
|
71
|
+
"[bold]Service account JSON path[/bold] [dim](optional, uses ADC if empty)[/dim]",
|
|
72
|
+
default="",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return BigQueryConfig(
|
|
76
|
+
name=name,
|
|
77
|
+
project_id=project_id,
|
|
78
|
+
dataset_id=dataset_id or None,
|
|
79
|
+
credentials_path=credentials_path or None,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def setup_databases() -> list[DatabaseConfig]:
|
|
84
|
+
"""Setup database configurations."""
|
|
85
|
+
databases: list[DatabaseConfig] = []
|
|
86
|
+
|
|
87
|
+
should_setup = Confirm.ask("\n[bold]Set up database connections?[/bold]", default=True)
|
|
88
|
+
|
|
89
|
+
if not should_setup:
|
|
90
|
+
return databases
|
|
91
|
+
|
|
92
|
+
while True:
|
|
93
|
+
console.print("\n[bold cyan]Database Configuration[/bold cyan]\n")
|
|
94
|
+
|
|
95
|
+
db_type_choices = [t.value for t in DatabaseType]
|
|
96
|
+
db_type = Prompt.ask(
|
|
97
|
+
"[bold]Select database type[/bold]",
|
|
98
|
+
choices=db_type_choices,
|
|
99
|
+
default=db_type_choices[0],
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if db_type == DatabaseType.BIGQUERY.value:
|
|
103
|
+
db_config = setup_bigquery()
|
|
104
|
+
databases.append(db_config)
|
|
105
|
+
console.print(f"\n[bold green]✓[/bold green] Added database [cyan]{db_config.name}[/cyan]")
|
|
106
|
+
|
|
107
|
+
add_another = Confirm.ask("\n[bold]Add another database?[/bold]", default=False)
|
|
108
|
+
if not add_another:
|
|
109
|
+
break
|
|
110
|
+
|
|
111
|
+
return databases
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def setup_llm() -> LLMConfig | None:
|
|
115
|
+
"""Setup the LLM configuration."""
|
|
116
|
+
llm_config = None
|
|
117
|
+
should_setup = Confirm.ask("\n[bold]Set up LLM configuration?[/bold]", default=True)
|
|
118
|
+
|
|
119
|
+
if should_setup:
|
|
120
|
+
console.print("\n[bold cyan]LLM Configuration[/bold cyan]\n")
|
|
121
|
+
|
|
122
|
+
provider_choices = [p.value for p in LLMProvider]
|
|
123
|
+
llm_provider = Prompt.ask(
|
|
124
|
+
"[bold]Select LLM provider[/bold]",
|
|
125
|
+
choices=provider_choices,
|
|
126
|
+
default=provider_choices[0],
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
api_key = Prompt.ask(
|
|
130
|
+
f"[bold]Enter your {llm_provider.upper()} API key[/bold]",
|
|
131
|
+
password=True,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if not api_key:
|
|
135
|
+
raise EmptyApiKeyError()
|
|
136
|
+
|
|
137
|
+
llm_config = LLMConfig(
|
|
138
|
+
provider=LLMProvider(llm_provider),
|
|
139
|
+
api_key=api_key,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return llm_config
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def init(
|
|
146
|
+
*,
|
|
147
|
+
force: Annotated[bool, Parameter(name=["-f", "--force"])] = False,
|
|
148
|
+
):
|
|
149
|
+
"""Initialize a new nao project.
|
|
150
|
+
|
|
151
|
+
Creates a project folder with a nao_config.yaml configuration file.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
force : bool
|
|
156
|
+
Force re-initialization even if the folder already exists.
|
|
157
|
+
"""
|
|
158
|
+
console.print("\n[bold cyan]🚀 nao project initialization[/bold cyan]\n")
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
project_name, project_path = setup_project_name(force=force)
|
|
162
|
+
config = NaoConfig(
|
|
163
|
+
project_name=project_name,
|
|
164
|
+
databases=setup_databases(),
|
|
165
|
+
llm=setup_llm(),
|
|
166
|
+
)
|
|
167
|
+
config.save(project_path)
|
|
168
|
+
|
|
169
|
+
console.print()
|
|
170
|
+
console.print(f"[bold green]✓[/bold green] Created project [cyan]{project_name}[/cyan]")
|
|
171
|
+
console.print(f"[bold green]✓[/bold green] Created [dim]{project_path / 'nao_config.yaml'}[/dim]")
|
|
172
|
+
console.print()
|
|
173
|
+
console.print("[bold green]Done![/bold green] Your nao project is ready. 🎉")
|
|
174
|
+
except InitError as e:
|
|
175
|
+
console.print(f"[bold red]✗[/bold red] {e}")
|