nao-core 0.0.29__py3-none-any.whl → 0.0.31__py3-none-any.whl
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/__init__.py +1 -1
- nao_core/bin/fastapi/main.py +21 -2
- nao_core/bin/fastapi/test_main.py +156 -0
- nao_core/bin/migrations-postgres/0004_input_and_output_tokens.sql +8 -0
- nao_core/bin/migrations-postgres/0005_add_project_tables.sql +39 -0
- nao_core/bin/migrations-postgres/meta/0004_snapshot.json +847 -0
- nao_core/bin/migrations-postgres/meta/0005_snapshot.json +1129 -0
- nao_core/bin/migrations-postgres/meta/_journal.json +14 -0
- nao_core/bin/migrations-sqlite/0004_input_and_output_tokens.sql +8 -0
- nao_core/bin/migrations-sqlite/0005_add_project_tables.sql +38 -0
- nao_core/bin/migrations-sqlite/meta/0004_snapshot.json +819 -0
- nao_core/bin/migrations-sqlite/meta/0005_snapshot.json +1086 -0
- nao_core/bin/migrations-sqlite/meta/_journal.json +14 -0
- nao_core/bin/nao-chat-server +0 -0
- nao_core/bin/public/assets/{code-block-F6WJLWQG-EQr6mTlQ.js → code-block-F6WJLWQG-TAi8koem.js} +3 -3
- nao_core/bin/public/assets/index-BfHcd9Xz.css +1 -0
- nao_core/bin/public/assets/index-Mzo9bkag.js +557 -0
- nao_core/bin/public/index.html +2 -2
- nao_core/commands/chat.py +11 -10
- nao_core/commands/debug.py +10 -6
- nao_core/commands/init.py +66 -27
- nao_core/commands/sync/__init__.py +40 -21
- nao_core/commands/sync/accessors.py +219 -141
- nao_core/commands/sync/cleanup.py +133 -0
- nao_core/commands/sync/providers/__init__.py +30 -0
- nao_core/commands/sync/providers/base.py +87 -0
- nao_core/commands/sync/providers/databases/__init__.py +17 -0
- nao_core/commands/sync/providers/databases/bigquery.py +78 -0
- nao_core/commands/sync/providers/databases/databricks.py +79 -0
- nao_core/commands/sync/providers/databases/duckdb.py +83 -0
- nao_core/commands/sync/providers/databases/postgres.py +78 -0
- nao_core/commands/sync/providers/databases/provider.py +123 -0
- nao_core/commands/sync/providers/databases/snowflake.py +78 -0
- nao_core/commands/sync/providers/repositories/__init__.py +5 -0
- nao_core/commands/sync/{repositories.py → providers/repositories/provider.py} +43 -20
- nao_core/config/__init__.py +16 -1
- nao_core/config/base.py +23 -4
- nao_core/config/databases/__init__.py +37 -11
- nao_core/config/databases/base.py +7 -0
- nao_core/config/databases/bigquery.py +29 -1
- nao_core/config/databases/databricks.py +69 -0
- nao_core/config/databases/duckdb.py +33 -0
- nao_core/config/databases/postgres.py +78 -0
- nao_core/config/databases/snowflake.py +115 -0
- nao_core/config/exceptions.py +7 -0
- nao_core/templates/__init__.py +12 -0
- nao_core/templates/defaults/databases/columns.md.j2 +23 -0
- nao_core/templates/defaults/databases/description.md.j2 +32 -0
- nao_core/templates/defaults/databases/preview.md.j2 +22 -0
- nao_core/templates/defaults/databases/profiling.md.j2 +34 -0
- nao_core/templates/engine.py +133 -0
- {nao_core-0.0.29.dist-info → nao_core-0.0.31.dist-info}/METADATA +9 -4
- nao_core-0.0.31.dist-info/RECORD +86 -0
- nao_core/bin/public/assets/_chat-layout-BTlqRUE5.js +0 -1
- nao_core/bin/public/assets/_chat-layout.index-DOARokp1.js +0 -1
- nao_core/bin/public/assets/agentProvider-C6dGIy-H.js +0 -1
- nao_core/bin/public/assets/button-By_1dzVx.js +0 -1
- nao_core/bin/public/assets/folder-DnRS5rg3.js +0 -1
- nao_core/bin/public/assets/index-CElAN2SH.css +0 -1
- nao_core/bin/public/assets/index-ZTHASguQ.js +0 -59
- nao_core/bin/public/assets/input-CUQA5tsi.js +0 -1
- nao_core/bin/public/assets/login-BUQDum3t.js +0 -1
- nao_core/bin/public/assets/mermaid-FSSLJTFX-Dc6ZvCPw.js +0 -427
- nao_core/bin/public/assets/sidebar-bgEk7Xg8.js +0 -1
- nao_core/bin/public/assets/signinForm-CGAhnAkv.js +0 -1
- nao_core/bin/public/assets/signup-D2n11La3.js +0 -1
- nao_core/bin/public/assets/user-CYl8Tly2.js +0 -1
- nao_core/bin/public/assets/utils-DzJYey0s.js +0 -1
- nao_core/commands/sync/databases.py +0 -132
- nao_core-0.0.29.dist-info/RECORD +0 -69
- {nao_core-0.0.29.dist-info → nao_core-0.0.31.dist-info}/WHEEL +0 -0
- {nao_core-0.0.29.dist-info → nao_core-0.0.31.dist-info}/entry_points.txt +0 -0
- {nao_core-0.0.29.dist-info → nao_core-0.0.31.dist-info}/licenses/LICENSE +0 -0
nao_core/bin/public/index.html
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
<link rel="apple-touch-icon" href="/logo192.png" />
|
|
10
10
|
<link rel="manifest" href="/manifest.json" />
|
|
11
11
|
<title>nao — Chat with your data</title>
|
|
12
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
12
|
+
<script type="module" crossorigin src="/assets/index-Mzo9bkag.js"></script>
|
|
13
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BfHcd9Xz.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
16
16
|
<div id="app"></div>
|
nao_core/commands/chat.py
CHANGED
|
@@ -106,18 +106,21 @@ def chat():
|
|
|
106
106
|
"""
|
|
107
107
|
console.print("\n[bold cyan]💬 Starting nao chat...[/bold cyan]\n")
|
|
108
108
|
|
|
109
|
-
binary_path = get_server_binary_path()
|
|
110
|
-
bin_dir = binary_path.parent
|
|
111
|
-
|
|
112
|
-
console.print(f"[dim]Server binary: {binary_path}[/dim]")
|
|
113
|
-
console.print(f"[dim]Working directory: {bin_dir}[/dim]")
|
|
114
|
-
|
|
115
109
|
# Try to load nao config from current directory
|
|
116
110
|
config = NaoConfig.try_load()
|
|
117
111
|
if config:
|
|
118
112
|
console.print(f"[bold green]✓[/bold green] Loaded config from {Path.cwd() / 'nao_config.yaml'}")
|
|
119
113
|
else:
|
|
120
|
-
console.print(
|
|
114
|
+
console.print(
|
|
115
|
+
"[bold red]✗No nao_config.yaml found in current directory. Please move to a nao project directory.[/bold red]"
|
|
116
|
+
)
|
|
117
|
+
sys.exit(1)
|
|
118
|
+
|
|
119
|
+
binary_path = get_server_binary_path()
|
|
120
|
+
bin_dir = binary_path.parent
|
|
121
|
+
|
|
122
|
+
console.print(f"[dim]Server binary: {binary_path}[/dim]")
|
|
123
|
+
console.print(f"[dim]Working directory: {bin_dir}[/dim]")
|
|
121
124
|
|
|
122
125
|
# Start the server processes
|
|
123
126
|
chat_process = None
|
|
@@ -154,10 +157,9 @@ def chat():
|
|
|
154
157
|
if config and config.slack:
|
|
155
158
|
env["SLACK_BOT_TOKEN"] = config.slack.bot_token
|
|
156
159
|
env["SLACK_SIGNING_SECRET"] = config.slack.signing_secret
|
|
157
|
-
env["SLACK_POST_MESSAGE_URL"] = config.slack.post_message_url
|
|
158
160
|
console.print("[bold green]✓[/bold green] Set Slack environment variables from config")
|
|
159
161
|
|
|
160
|
-
env["
|
|
162
|
+
env["NAO_DEFAULT_PROJECT_PATH"] = str(Path.cwd())
|
|
161
163
|
env["FASTAPI_URL"] = f"http://localhost:{FASTAPI_PORT}"
|
|
162
164
|
|
|
163
165
|
# Start the FastAPI server first
|
|
@@ -166,7 +168,6 @@ def chat():
|
|
|
166
168
|
|
|
167
169
|
fastapi_process = subprocess.Popen(
|
|
168
170
|
[sys.executable, str(fastapi_path)],
|
|
169
|
-
cwd=str(fastapi_path.parent),
|
|
170
171
|
env=env,
|
|
171
172
|
stdout=subprocess.DEVNULL,
|
|
172
173
|
stderr=subprocess.DEVNULL,
|
nao_core/commands/debug.py
CHANGED
|
@@ -4,11 +4,12 @@ from rich.console import Console
|
|
|
4
4
|
from rich.table import Table
|
|
5
5
|
|
|
6
6
|
from nao_core.config import NaoConfig
|
|
7
|
+
from nao_core.config.databases import AnyDatabaseConfig
|
|
7
8
|
|
|
8
9
|
console = Console()
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
def test_database_connection(db_config) -> tuple[bool, str]:
|
|
12
|
+
def test_database_connection(db_config: AnyDatabaseConfig) -> tuple[bool, str]:
|
|
12
13
|
"""Test connectivity to a database.
|
|
13
14
|
|
|
14
15
|
Returns:
|
|
@@ -17,16 +18,19 @@ def test_database_connection(db_config) -> tuple[bool, str]:
|
|
|
17
18
|
try:
|
|
18
19
|
conn = db_config.connect()
|
|
19
20
|
# Run a simple query to verify the connection works
|
|
20
|
-
if db_config.dataset_id:
|
|
21
|
+
if hasattr(db_config, "dataset_id") and db_config.dataset_id:
|
|
21
22
|
# If dataset is specified, list tables in that dataset
|
|
22
23
|
tables = conn.list_tables()
|
|
23
24
|
table_count = len(tables)
|
|
24
25
|
return True, f"Connected successfully ({table_count} tables found)"
|
|
26
|
+
elif list_databases := getattr(conn, "list_databases", None):
|
|
27
|
+
# If no dataset, list schemas in the database instead
|
|
28
|
+
schemas = list_databases()
|
|
29
|
+
schema_count = len(schemas)
|
|
30
|
+
return True, f"Connected successfully ({schema_count} schemas found)"
|
|
25
31
|
else:
|
|
26
|
-
#
|
|
27
|
-
datasets
|
|
28
|
-
dataset_count = len(datasets)
|
|
29
|
-
return True, f"Connected successfully ({dataset_count} datasets found)"
|
|
32
|
+
# Fallback for backends that don't support list_tables and list_databases
|
|
33
|
+
return True, "Connected but unable to list neither datasets nor schemas"
|
|
30
34
|
except Exception as e:
|
|
31
35
|
return False, str(e)
|
|
32
36
|
|
nao_core/commands/init.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from dataclasses import dataclass
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from typing import Annotated
|
|
4
5
|
|
|
@@ -7,18 +8,25 @@ from rich.console import Console
|
|
|
7
8
|
from rich.panel import Panel
|
|
8
9
|
from rich.prompt import Confirm, Prompt
|
|
9
10
|
|
|
10
|
-
from nao_core.config import
|
|
11
|
+
from nao_core.config import (
|
|
12
|
+
AnyDatabaseConfig,
|
|
13
|
+
BigQueryConfig,
|
|
14
|
+
DatabaseType,
|
|
15
|
+
DatabricksConfig,
|
|
16
|
+
DuckDBConfig,
|
|
17
|
+
LLMConfig,
|
|
18
|
+
LLMProvider,
|
|
19
|
+
NaoConfig,
|
|
20
|
+
PostgresConfig,
|
|
21
|
+
SlackConfig,
|
|
22
|
+
SnowflakeConfig,
|
|
23
|
+
)
|
|
24
|
+
from nao_core.config.exceptions import InitError
|
|
11
25
|
from nao_core.config.repos import RepoConfig
|
|
12
26
|
|
|
13
27
|
console = Console()
|
|
14
28
|
|
|
15
29
|
|
|
16
|
-
class InitError(Exception):
|
|
17
|
-
"""Base exception for init command errors."""
|
|
18
|
-
|
|
19
|
-
pass
|
|
20
|
-
|
|
21
|
-
|
|
22
30
|
class EmptyProjectNameError(InitError):
|
|
23
31
|
"""Raised when project name is empty."""
|
|
24
32
|
|
|
@@ -41,6 +49,12 @@ class EmptyApiKeyError(InitError):
|
|
|
41
49
|
super().__init__("API key cannot be empty.")
|
|
42
50
|
|
|
43
51
|
|
|
52
|
+
@dataclass
|
|
53
|
+
class CreatedFile:
|
|
54
|
+
path: Path
|
|
55
|
+
content: str | None
|
|
56
|
+
|
|
57
|
+
|
|
44
58
|
def setup_project_name(force: bool = False) -> tuple[str, Path]:
|
|
45
59
|
"""Setup the project name."""
|
|
46
60
|
# Check if we're in a directory with an existing nao_config.yaml
|
|
@@ -77,27 +91,27 @@ def setup_project_name(force: bool = False) -> tuple[str, Path]:
|
|
|
77
91
|
|
|
78
92
|
def setup_bigquery() -> BigQueryConfig:
|
|
79
93
|
"""Setup a BigQuery database configuration."""
|
|
80
|
-
|
|
94
|
+
return BigQueryConfig.promptConfig()
|
|
81
95
|
|
|
82
|
-
name = Prompt.ask("[bold]Connection name[/bold]", default="bigquery-prod")
|
|
83
96
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
97
|
+
def setup_duckdb() -> DuckDBConfig:
|
|
98
|
+
"""Setup a DuckDB database configuration."""
|
|
99
|
+
return DuckDBConfig.promptConfig()
|
|
87
100
|
|
|
88
|
-
dataset_id = Prompt.ask("[bold]Default dataset[/bold] [dim](optional, press Enter to skip)[/dim]", default="")
|
|
89
101
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)
|
|
102
|
+
def setup_databricks() -> DatabricksConfig:
|
|
103
|
+
"""Setup a Databricks database configuration."""
|
|
104
|
+
return DatabricksConfig.promptConfig()
|
|
94
105
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
|
|
107
|
+
def setup_snowflake() -> SnowflakeConfig:
|
|
108
|
+
"""Setup a Snowflake database configuration."""
|
|
109
|
+
return SnowflakeConfig.promptConfig()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def setup_postgres() -> PostgresConfig:
|
|
113
|
+
"""Setup a PostgreSQL database configuration."""
|
|
114
|
+
return PostgresConfig.promptConfig()
|
|
101
115
|
|
|
102
116
|
|
|
103
117
|
def setup_databases() -> list[AnyDatabaseConfig]:
|
|
@@ -123,6 +137,25 @@ def setup_databases() -> list[AnyDatabaseConfig]:
|
|
|
123
137
|
db_config = setup_bigquery()
|
|
124
138
|
databases.append(db_config)
|
|
125
139
|
console.print(f"\n[bold green]✓[/bold green] Added database [cyan]{db_config.name}[/cyan]")
|
|
140
|
+
elif db_type == DatabaseType.POSTGRES.value:
|
|
141
|
+
db_config = setup_postgres()
|
|
142
|
+
databases.append(db_config)
|
|
143
|
+
console.print(f"\n[bold green]✓[/bold green] Added database [cyan]{db_config.name}[/cyan]")
|
|
144
|
+
|
|
145
|
+
elif db_type == DatabaseType.DUCKDB.value:
|
|
146
|
+
db_config = setup_duckdb()
|
|
147
|
+
databases.append(db_config)
|
|
148
|
+
console.print(f"\n[bold green]✓[/bold green] Added database [cyan]{db_config.name}[/cyan]")
|
|
149
|
+
|
|
150
|
+
elif db_type == DatabaseType.DATABRICKS.value:
|
|
151
|
+
db_config = setup_databricks()
|
|
152
|
+
databases.append(db_config)
|
|
153
|
+
console.print(f"\n[bold green]✓[/bold green] Added database [cyan]{db_config.name}[/cyan]")
|
|
154
|
+
|
|
155
|
+
elif db_type == DatabaseType.SNOWFLAKE.value:
|
|
156
|
+
db_config = setup_snowflake()
|
|
157
|
+
databases.append(db_config)
|
|
158
|
+
console.print(f"\n[bold green]✓[/bold green] Added database [cyan]{db_config.name}[/cyan]")
|
|
126
159
|
|
|
127
160
|
add_another = Confirm.ask("\n[bold]Add another database?[/bold]", default=False)
|
|
128
161
|
if not add_another:
|
|
@@ -217,7 +250,7 @@ def setup_slack() -> SlackConfig | None:
|
|
|
217
250
|
return slack_config
|
|
218
251
|
|
|
219
252
|
|
|
220
|
-
def create_empty_structure(project_path: Path) -> tuple[list[str], list[
|
|
253
|
+
def create_empty_structure(project_path: Path) -> tuple[list[str], list[CreatedFile]]:
|
|
221
254
|
"""Create project folder structure to guide users.
|
|
222
255
|
|
|
223
256
|
To add new folders, simply append them to the FOLDERS list below.
|
|
@@ -233,7 +266,10 @@ def create_empty_structure(project_path: Path) -> tuple[list[str], list[str]]:
|
|
|
233
266
|
"agent/mcps",
|
|
234
267
|
]
|
|
235
268
|
|
|
236
|
-
FILES = [
|
|
269
|
+
FILES = [
|
|
270
|
+
CreatedFile(path=Path("RULES.md"), content=None),
|
|
271
|
+
CreatedFile(path=Path(".naoignore"), content="templates/\n"),
|
|
272
|
+
]
|
|
237
273
|
|
|
238
274
|
created_folders = []
|
|
239
275
|
for folder in FOLDERS:
|
|
@@ -243,8 +279,11 @@ def create_empty_structure(project_path: Path) -> tuple[list[str], list[str]]:
|
|
|
243
279
|
|
|
244
280
|
created_files = []
|
|
245
281
|
for file in FILES:
|
|
246
|
-
file_path = project_path / file
|
|
247
|
-
|
|
282
|
+
file_path = project_path / file.path
|
|
283
|
+
if file.content:
|
|
284
|
+
file_path.write_text(file.content)
|
|
285
|
+
else:
|
|
286
|
+
file_path.touch()
|
|
248
287
|
created_files.append(file)
|
|
249
288
|
|
|
250
289
|
return created_folders, created_files
|
|
@@ -7,50 +7,69 @@ from rich.console import Console
|
|
|
7
7
|
|
|
8
8
|
from nao_core.config import NaoConfig
|
|
9
9
|
|
|
10
|
-
from .
|
|
11
|
-
from .repositories import sync_repositories
|
|
10
|
+
from .providers import SyncProvider, SyncResult, get_all_providers
|
|
12
11
|
|
|
13
12
|
console = Console()
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
def sync(
|
|
17
|
-
|
|
15
|
+
def sync(
|
|
16
|
+
output_dirs: dict[str, str] | None = None,
|
|
17
|
+
providers: list[SyncProvider] | None = None,
|
|
18
|
+
):
|
|
19
|
+
"""Sync resources using configured providers.
|
|
18
20
|
|
|
19
|
-
Creates folder structures:
|
|
21
|
+
Creates folder structures based on each provider's default output directory:
|
|
20
22
|
- repos/<repo_name>/ (git repositories)
|
|
21
|
-
- databases
|
|
23
|
+
- databases/<type>/<connection>/<dataset>/<table>/*.md (database schemas)
|
|
22
24
|
|
|
23
25
|
Args:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
output_dirs: Optional dict mapping provider names to custom output directories.
|
|
27
|
+
If not specified, uses each provider's default_output_dir.
|
|
28
|
+
providers: Optional list of providers to use. If not specified, uses all
|
|
29
|
+
registered providers.
|
|
26
30
|
"""
|
|
27
31
|
console.print("\n[bold cyan]🔄 nao sync[/bold cyan]\n")
|
|
28
32
|
|
|
29
33
|
config = NaoConfig.try_load()
|
|
30
|
-
if
|
|
34
|
+
if config is None:
|
|
31
35
|
console.print("[bold red]✗[/bold red] No nao_config.yaml found in current directory")
|
|
32
36
|
console.print("[dim]Run 'nao init' to create a configuration file[/dim]")
|
|
33
37
|
sys.exit(1)
|
|
34
38
|
|
|
39
|
+
# Get project path (current working directory after NaoConfig.try_load)
|
|
40
|
+
project_path = Path.cwd()
|
|
41
|
+
|
|
35
42
|
console.print(f"[dim]Project:[/dim] {config.project_name}")
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
if
|
|
39
|
-
|
|
40
|
-
repos_synced = sync_repositories(config.repos, repos_path)
|
|
44
|
+
# Use provided providers or default to all registered providers
|
|
45
|
+
active_providers = providers if providers is not None else get_all_providers()
|
|
46
|
+
output_dirs = output_dirs or {}
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
# Run each provider
|
|
49
|
+
results: list[SyncResult] = []
|
|
50
|
+
for provider in active_providers:
|
|
51
|
+
if config is None or not provider.should_sync(config):
|
|
52
|
+
continue
|
|
44
53
|
|
|
45
|
-
|
|
54
|
+
# Get output directory (custom or default)
|
|
55
|
+
output_dir = output_dirs.get(provider.name, provider.default_output_dir)
|
|
56
|
+
output_path = Path(output_dir)
|
|
46
57
|
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
# Get items and sync
|
|
59
|
+
items = provider.get_items(config)
|
|
60
|
+
result = provider.sync(items, output_path, project_path=project_path)
|
|
61
|
+
results.append(result)
|
|
62
|
+
|
|
63
|
+
# Print summary
|
|
64
|
+
console.print("\n[bold green]✓ Sync Complete[/bold green]\n")
|
|
49
65
|
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
has_results = False
|
|
67
|
+
for result in results:
|
|
68
|
+
if result.items_synced > 0:
|
|
69
|
+
has_results = True
|
|
70
|
+
console.print(f" [dim]{result.provider_name}:[/dim] {result.get_summary()}")
|
|
52
71
|
|
|
53
|
-
if
|
|
72
|
+
if not has_results:
|
|
54
73
|
console.print(" [dim]Nothing to sync[/dim]")
|
|
55
74
|
|
|
56
75
|
console.print()
|