nao-core 0.0.38__py3-none-manylinux2014_aarch64.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 +2 -0
- nao_core/__init__.py.bak +2 -0
- nao_core/bin/build-info.json +5 -0
- nao_core/bin/fastapi/main.py +268 -0
- nao_core/bin/fastapi/test_main.py +156 -0
- nao_core/bin/migrations-postgres/0000_user_auth_and_chat_tables.sql +98 -0
- nao_core/bin/migrations-postgres/0001_message_feedback.sql +9 -0
- nao_core/bin/migrations-postgres/0002_chat_message_stop_reason_and_error_message.sql +2 -0
- nao_core/bin/migrations-postgres/0003_handle_slack_with_thread.sql +2 -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/0006_llm_model_ids.sql +4 -0
- nao_core/bin/migrations-postgres/0007_chat_message_llm_info.sql +2 -0
- nao_core/bin/migrations-postgres/meta/0000_snapshot.json +707 -0
- nao_core/bin/migrations-postgres/meta/0001_snapshot.json +766 -0
- nao_core/bin/migrations-postgres/meta/0002_snapshot.json +778 -0
- nao_core/bin/migrations-postgres/meta/0003_snapshot.json +799 -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/0006_snapshot.json +1141 -0
- nao_core/bin/migrations-postgres/meta/_journal.json +62 -0
- nao_core/bin/migrations-sqlite/0000_user_auth_and_chat_tables.sql +98 -0
- nao_core/bin/migrations-sqlite/0001_message_feedback.sql +8 -0
- nao_core/bin/migrations-sqlite/0002_chat_message_stop_reason_and_error_message.sql +2 -0
- nao_core/bin/migrations-sqlite/0003_handle_slack_with_thread.sql +2 -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/0006_llm_model_ids.sql +4 -0
- nao_core/bin/migrations-sqlite/0007_chat_message_llm_info.sql +2 -0
- nao_core/bin/migrations-sqlite/meta/0000_snapshot.json +674 -0
- nao_core/bin/migrations-sqlite/meta/0001_snapshot.json +735 -0
- nao_core/bin/migrations-sqlite/meta/0002_snapshot.json +749 -0
- nao_core/bin/migrations-sqlite/meta/0003_snapshot.json +763 -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/0006_snapshot.json +1100 -0
- nao_core/bin/migrations-sqlite/meta/_journal.json +62 -0
- nao_core/bin/nao-chat-server +0 -0
- nao_core/bin/public/assets/code-block-F6WJLWQG-CV0uOmNJ.js +153 -0
- nao_core/bin/public/assets/index-DcbndLHo.css +1 -0
- nao_core/bin/public/assets/index-t1hZI3nl.js +560 -0
- nao_core/bin/public/favicon.ico +0 -0
- nao_core/bin/public/index.html +18 -0
- nao_core/bin/rg +0 -0
- nao_core/commands/__init__.py +6 -0
- nao_core/commands/chat.py +225 -0
- nao_core/commands/debug.py +158 -0
- nao_core/commands/init.py +358 -0
- nao_core/commands/sync/__init__.py +124 -0
- nao_core/commands/sync/accessors.py +290 -0
- nao_core/commands/sync/cleanup.py +156 -0
- nao_core/commands/sync/providers/__init__.py +32 -0
- nao_core/commands/sync/providers/base.py +113 -0
- nao_core/commands/sync/providers/databases/__init__.py +17 -0
- nao_core/commands/sync/providers/databases/bigquery.py +79 -0
- nao_core/commands/sync/providers/databases/databricks.py +79 -0
- nao_core/commands/sync/providers/databases/duckdb.py +78 -0
- nao_core/commands/sync/providers/databases/postgres.py +79 -0
- nao_core/commands/sync/providers/databases/provider.py +129 -0
- nao_core/commands/sync/providers/databases/snowflake.py +79 -0
- nao_core/commands/sync/providers/notion/__init__.py +5 -0
- nao_core/commands/sync/providers/notion/provider.py +205 -0
- nao_core/commands/sync/providers/repositories/__init__.py +5 -0
- nao_core/commands/sync/providers/repositories/provider.py +134 -0
- nao_core/commands/sync/registry.py +23 -0
- nao_core/config/__init__.py +30 -0
- nao_core/config/base.py +100 -0
- nao_core/config/databases/__init__.py +55 -0
- nao_core/config/databases/base.py +85 -0
- nao_core/config/databases/bigquery.py +99 -0
- nao_core/config/databases/databricks.py +79 -0
- nao_core/config/databases/duckdb.py +41 -0
- nao_core/config/databases/postgres.py +83 -0
- nao_core/config/databases/snowflake.py +125 -0
- nao_core/config/exceptions.py +7 -0
- nao_core/config/llm/__init__.py +19 -0
- nao_core/config/notion/__init__.py +8 -0
- nao_core/config/repos/__init__.py +3 -0
- nao_core/config/repos/base.py +11 -0
- nao_core/config/slack/__init__.py +12 -0
- nao_core/context/__init__.py +54 -0
- nao_core/context/base.py +57 -0
- nao_core/context/git.py +177 -0
- nao_core/context/local.py +59 -0
- nao_core/main.py +13 -0
- nao_core/templates/__init__.py +41 -0
- nao_core/templates/context.py +193 -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/templates/render.py +196 -0
- nao_core-0.0.38.dist-info/METADATA +150 -0
- nao_core-0.0.38.dist-info/RECORD +98 -0
- nao_core-0.0.38.dist-info/WHEEL +4 -0
- nao_core-0.0.38.dist-info/entry_points.txt +2 -0
- nao_core-0.0.38.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
from cyclopts import Parameter
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.prompt import Confirm, Prompt
|
|
10
|
+
|
|
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
|
|
25
|
+
from nao_core.config.repos import RepoConfig
|
|
26
|
+
|
|
27
|
+
console = Console()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class EmptyProjectNameError(InitError):
|
|
31
|
+
"""Raised when project name is empty."""
|
|
32
|
+
|
|
33
|
+
def __init__(self):
|
|
34
|
+
super().__init__("Project name cannot be empty.")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ProjectExistsError(InitError):
|
|
38
|
+
"""Raised when project folder already exists."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, project_name: str):
|
|
41
|
+
self.project_name = project_name
|
|
42
|
+
super().__init__(f"Folder '{project_name}' already exists.")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class EmptyApiKeyError(InitError):
|
|
46
|
+
"""Raised when API key is empty."""
|
|
47
|
+
|
|
48
|
+
def __init__(self):
|
|
49
|
+
super().__init__("API key cannot be empty.")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class CreatedFile:
|
|
54
|
+
path: Path
|
|
55
|
+
content: str | None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def setup_project_name(force: bool = False) -> tuple[str, Path]:
|
|
59
|
+
"""Setup the project name."""
|
|
60
|
+
# Check if we're in a directory with an existing nao_config.yaml
|
|
61
|
+
current_dir = Path.cwd()
|
|
62
|
+
config_file = current_dir / "nao_config.yaml"
|
|
63
|
+
|
|
64
|
+
if config_file.exists():
|
|
65
|
+
# Load existing config to get project name
|
|
66
|
+
existing_config = NaoConfig.try_load(current_dir)
|
|
67
|
+
if existing_config:
|
|
68
|
+
console.print("\n[bold yellow]Found existing nao_config.yaml[/bold yellow]")
|
|
69
|
+
console.print(f"[dim]Project: {existing_config.project_name}[/dim]\n")
|
|
70
|
+
|
|
71
|
+
if force or Confirm.ask("[bold]Re-initialize this project?[/bold]", default=True):
|
|
72
|
+
return existing_config.project_name, current_dir
|
|
73
|
+
else:
|
|
74
|
+
raise InitError("Initialization cancelled.")
|
|
75
|
+
|
|
76
|
+
# Normal flow: prompt for project name
|
|
77
|
+
project_name = Prompt.ask("[bold]Enter your project name[/bold]")
|
|
78
|
+
|
|
79
|
+
if not project_name:
|
|
80
|
+
raise EmptyProjectNameError()
|
|
81
|
+
|
|
82
|
+
project_path = Path(project_name)
|
|
83
|
+
|
|
84
|
+
if project_path.exists() and not force:
|
|
85
|
+
raise ProjectExistsError(project_name)
|
|
86
|
+
|
|
87
|
+
project_path.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
|
|
89
|
+
return project_name, project_path
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def setup_bigquery() -> BigQueryConfig:
|
|
93
|
+
"""Setup a BigQuery database configuration."""
|
|
94
|
+
return BigQueryConfig.promptConfig()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def setup_duckdb() -> DuckDBConfig:
|
|
98
|
+
"""Setup a DuckDB database configuration."""
|
|
99
|
+
return DuckDBConfig.promptConfig()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def setup_databricks() -> DatabricksConfig:
|
|
103
|
+
"""Setup a Databricks database configuration."""
|
|
104
|
+
return DatabricksConfig.promptConfig()
|
|
105
|
+
|
|
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()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def setup_databases() -> list[AnyDatabaseConfig]:
|
|
118
|
+
"""Setup database configurations."""
|
|
119
|
+
databases: list[AnyDatabaseConfig] = []
|
|
120
|
+
|
|
121
|
+
should_setup = Confirm.ask("\n[bold]Set up database connections?[/bold]", default=True)
|
|
122
|
+
|
|
123
|
+
if not should_setup:
|
|
124
|
+
return databases
|
|
125
|
+
|
|
126
|
+
while True:
|
|
127
|
+
console.print("\n[bold cyan]Database Configuration[/bold cyan]\n")
|
|
128
|
+
|
|
129
|
+
db_type_choices = [t.value for t in DatabaseType]
|
|
130
|
+
db_type = Prompt.ask(
|
|
131
|
+
"[bold]Select database type[/bold]",
|
|
132
|
+
choices=db_type_choices,
|
|
133
|
+
default=db_type_choices[0],
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if db_type == DatabaseType.BIGQUERY.value:
|
|
137
|
+
db_config = setup_bigquery()
|
|
138
|
+
databases.append(db_config)
|
|
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]")
|
|
159
|
+
|
|
160
|
+
add_another = Confirm.ask("\n[bold]Add another database?[/bold]", default=False)
|
|
161
|
+
if not add_another:
|
|
162
|
+
break
|
|
163
|
+
|
|
164
|
+
return databases
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def setup_repos() -> list[RepoConfig]:
|
|
168
|
+
"""Setup repository configurations."""
|
|
169
|
+
repos: list[RepoConfig] = []
|
|
170
|
+
should_setup = Confirm.ask("\n[bold]Set up git repositories?[/bold]", default=True)
|
|
171
|
+
|
|
172
|
+
if not should_setup:
|
|
173
|
+
return repos
|
|
174
|
+
|
|
175
|
+
while True:
|
|
176
|
+
console.print("\n[bold cyan]Git Repository Configuration[/bold cyan]\n")
|
|
177
|
+
name = Prompt.ask("[bold]Repository name[/bold]")
|
|
178
|
+
url = Prompt.ask("[bold]Repository URL[/bold]")
|
|
179
|
+
|
|
180
|
+
repos.append(RepoConfig(name=name, url=url))
|
|
181
|
+
console.print(f"\n[bold green]✓[/bold green] Added repository [cyan]{name}[/cyan]")
|
|
182
|
+
|
|
183
|
+
add_another = Confirm.ask("\n[bold]Add another repository?[/bold]", default=False)
|
|
184
|
+
if not add_another:
|
|
185
|
+
break
|
|
186
|
+
|
|
187
|
+
return repos
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def setup_llm() -> LLMConfig | None:
|
|
191
|
+
"""Setup the LLM configuration."""
|
|
192
|
+
llm_config = None
|
|
193
|
+
should_setup = Confirm.ask("\n[bold]Set up LLM configuration?[/bold]", default=True)
|
|
194
|
+
|
|
195
|
+
if should_setup:
|
|
196
|
+
console.print("\n[bold cyan]LLM Configuration[/bold cyan]\n")
|
|
197
|
+
|
|
198
|
+
provider_choices = [p.value for p in LLMProvider]
|
|
199
|
+
llm_provider = Prompt.ask(
|
|
200
|
+
"[bold]Select LLM provider[/bold]",
|
|
201
|
+
choices=provider_choices,
|
|
202
|
+
default=provider_choices[0],
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
api_key = Prompt.ask(
|
|
206
|
+
f"[bold]Enter your {llm_provider.upper()} API key[/bold]",
|
|
207
|
+
password=True,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if not api_key:
|
|
211
|
+
raise EmptyApiKeyError()
|
|
212
|
+
|
|
213
|
+
llm_config = LLMConfig(
|
|
214
|
+
provider=LLMProvider(llm_provider),
|
|
215
|
+
api_key=api_key,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return llm_config
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def setup_slack() -> SlackConfig | None:
|
|
222
|
+
"""Setup the Slack configuration."""
|
|
223
|
+
slack_config = None
|
|
224
|
+
should_setup = Confirm.ask("\n[bold]Set up Slack integration?[/bold]", default=False)
|
|
225
|
+
|
|
226
|
+
if should_setup:
|
|
227
|
+
console.print("\n[bold cyan]Slack Configuration[/bold cyan]\n")
|
|
228
|
+
|
|
229
|
+
bot_token = Prompt.ask(
|
|
230
|
+
"[bold]Enter your Slack bot token[/bold]",
|
|
231
|
+
password=True,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if not bot_token:
|
|
235
|
+
raise InitError("Slack bot token cannot be empty.")
|
|
236
|
+
|
|
237
|
+
signing_secret = Prompt.ask(
|
|
238
|
+
"[bold]Enter your Slack signing secret[/bold]",
|
|
239
|
+
password=True,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if not signing_secret:
|
|
243
|
+
raise InitError("Slack signing secret cannot be empty.")
|
|
244
|
+
|
|
245
|
+
slack_config = SlackConfig(
|
|
246
|
+
bot_token=bot_token,
|
|
247
|
+
signing_secret=signing_secret,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
return slack_config
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def create_empty_structure(project_path: Path) -> tuple[list[str], list[CreatedFile]]:
|
|
254
|
+
"""Create project folder structure to guide users.
|
|
255
|
+
|
|
256
|
+
To add new folders, simply append them to the FOLDERS list below.
|
|
257
|
+
Each folder will be created automatically (can be empty).
|
|
258
|
+
"""
|
|
259
|
+
FOLDERS = [
|
|
260
|
+
"databases",
|
|
261
|
+
"queries",
|
|
262
|
+
"docs",
|
|
263
|
+
"semantics",
|
|
264
|
+
"repos",
|
|
265
|
+
"agent/tools",
|
|
266
|
+
"agent/mcps",
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
FILES = [
|
|
270
|
+
CreatedFile(path=Path("RULES.md"), content=None),
|
|
271
|
+
CreatedFile(path=Path(".naoignore"), content="templates/\n*.j2\n"),
|
|
272
|
+
]
|
|
273
|
+
|
|
274
|
+
created_folders = []
|
|
275
|
+
for folder in FOLDERS:
|
|
276
|
+
folder_path = project_path / folder
|
|
277
|
+
folder_path.mkdir(parents=True, exist_ok=True)
|
|
278
|
+
created_folders.append(folder)
|
|
279
|
+
|
|
280
|
+
created_files = []
|
|
281
|
+
for file in FILES:
|
|
282
|
+
file_path = project_path / file.path
|
|
283
|
+
if file.content:
|
|
284
|
+
file_path.write_text(file.content)
|
|
285
|
+
else:
|
|
286
|
+
file_path.touch()
|
|
287
|
+
created_files.append(file)
|
|
288
|
+
|
|
289
|
+
return created_folders, created_files
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def init(
|
|
293
|
+
*,
|
|
294
|
+
force: Annotated[bool, Parameter(name=["-f", "--force"])] = False,
|
|
295
|
+
):
|
|
296
|
+
"""Initialize a new nao project.
|
|
297
|
+
|
|
298
|
+
Creates a project folder with a nao_config.yaml configuration file.
|
|
299
|
+
|
|
300
|
+
Parameters
|
|
301
|
+
----------
|
|
302
|
+
force : bool
|
|
303
|
+
Force re-initialization even if the folder already exists.
|
|
304
|
+
"""
|
|
305
|
+
console.print("\n[bold cyan]🚀 nao project initialization[/bold cyan]\n")
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
project_name, project_path = setup_project_name(force=force)
|
|
309
|
+
config = NaoConfig(
|
|
310
|
+
project_name=project_name,
|
|
311
|
+
databases=setup_databases(),
|
|
312
|
+
repos=setup_repos(),
|
|
313
|
+
llm=setup_llm(),
|
|
314
|
+
slack=setup_slack(),
|
|
315
|
+
)
|
|
316
|
+
config.save(project_path)
|
|
317
|
+
|
|
318
|
+
# Create project folder structure
|
|
319
|
+
created_folders, created_files = create_empty_structure(project_path)
|
|
320
|
+
|
|
321
|
+
console.print()
|
|
322
|
+
console.print(f"[bold green]✓[/bold green] Created project [cyan]{project_name}[/cyan]")
|
|
323
|
+
console.print(f"[bold green]✓[/bold green] Created [dim]{project_path / 'nao_config.yaml'}[/dim]")
|
|
324
|
+
console.print()
|
|
325
|
+
console.print("[bold green]Done![/bold green] Your nao project is ready. 🎉")
|
|
326
|
+
|
|
327
|
+
is_subfolder = project_path.resolve() != Path.cwd().resolve()
|
|
328
|
+
|
|
329
|
+
has_connections = config.databases or config.llm
|
|
330
|
+
if has_connections:
|
|
331
|
+
# Change directory for the debug command to run in the right context
|
|
332
|
+
os.chdir(project_path)
|
|
333
|
+
from nao_core.commands.debug import debug
|
|
334
|
+
|
|
335
|
+
debug()
|
|
336
|
+
|
|
337
|
+
console.print()
|
|
338
|
+
|
|
339
|
+
cd_instruction = ""
|
|
340
|
+
if is_subfolder:
|
|
341
|
+
cd_instruction = f"\n[bold]First, navigate to your project:[/bold]\n[cyan]cd {project_path}[/cyan]\n\n"
|
|
342
|
+
|
|
343
|
+
help_content = f"""{cd_instruction}[bold]Available Commands:[/bold]
|
|
344
|
+
|
|
345
|
+
[cyan]nao debug[/cyan] - Test connectivity to your configured databases and LLM
|
|
346
|
+
Verifies that all connections are working properly
|
|
347
|
+
|
|
348
|
+
[cyan]nao sync[/cyan] - Sync database schemas to local markdown files
|
|
349
|
+
Creates documentation for your tables and columns
|
|
350
|
+
|
|
351
|
+
[cyan]nao chat[/cyan] - Start the nao chat interface
|
|
352
|
+
Launch the web UI to chat with your data
|
|
353
|
+
"""
|
|
354
|
+
console.print(Panel(help_content, border_style="cyan", title="🚀 Get Started", title_align="left"))
|
|
355
|
+
console.print()
|
|
356
|
+
|
|
357
|
+
except InitError as e:
|
|
358
|
+
console.print(f"[bold red]✗[/bold red] {e}")
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Sync command for synchronizing repositories and database schemas."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from nao_core.config import NaoConfig
|
|
9
|
+
from nao_core.templates.render import render_all_templates
|
|
10
|
+
|
|
11
|
+
from .providers import SyncProvider, SyncResult, get_all_providers
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def sync(
|
|
17
|
+
output_dirs: dict[str, str] | None = None,
|
|
18
|
+
providers: list[SyncProvider] | None = None,
|
|
19
|
+
render_templates: bool = True,
|
|
20
|
+
):
|
|
21
|
+
"""Sync resources using configured providers.
|
|
22
|
+
|
|
23
|
+
Creates folder structures based on each provider's default output directory:
|
|
24
|
+
- repos/<repo_name>/ (git repositories)
|
|
25
|
+
- databases/<type>/<connection>/<dataset>/<table>/*.md (database schemas)
|
|
26
|
+
|
|
27
|
+
After syncing providers, renders any Jinja templates (*.j2 files) found in
|
|
28
|
+
the project directory, making the `nao` context object available for
|
|
29
|
+
accessing provider data.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
output_dirs: Optional dict mapping provider names to custom output directories.
|
|
33
|
+
If not specified, uses each provider's default_output_dir.
|
|
34
|
+
providers: Optional list of providers to use. If not specified, uses all
|
|
35
|
+
registered providers.
|
|
36
|
+
render_templates: Whether to render Jinja templates after syncing providers.
|
|
37
|
+
Defaults to True.
|
|
38
|
+
"""
|
|
39
|
+
console.print("\n[bold cyan]🔄 nao sync[/bold cyan]\n")
|
|
40
|
+
|
|
41
|
+
config = NaoConfig.try_load()
|
|
42
|
+
if config is None:
|
|
43
|
+
console.print("[bold red]✗[/bold red] No nao_config.yaml found in current directory")
|
|
44
|
+
console.print("[dim]Run 'nao init' to create a configuration file[/dim]")
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
assert config is not None # Help type checker after sys.exit
|
|
47
|
+
|
|
48
|
+
# Get project path (current working directory after NaoConfig.try_load)
|
|
49
|
+
project_path = Path.cwd()
|
|
50
|
+
|
|
51
|
+
console.print(f"[dim]Project:[/dim] {config.project_name}")
|
|
52
|
+
|
|
53
|
+
# Use provided providers or default to all registered providers
|
|
54
|
+
active_providers = providers if providers is not None else get_all_providers()
|
|
55
|
+
output_dirs = output_dirs or {}
|
|
56
|
+
|
|
57
|
+
# Run each provider
|
|
58
|
+
results: list[SyncResult] = []
|
|
59
|
+
for provider in active_providers:
|
|
60
|
+
# Get output directory (custom or default)
|
|
61
|
+
output_dir = output_dirs.get(provider.name, provider.default_output_dir)
|
|
62
|
+
output_path = Path(output_dir)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
provider.pre_sync(config, output_path)
|
|
66
|
+
|
|
67
|
+
if not provider.should_sync(config):
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
# Get items and sync
|
|
71
|
+
items = provider.get_items(config)
|
|
72
|
+
result = provider.sync(items, output_path, project_path=project_path)
|
|
73
|
+
results.append(result)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
# Capture error but continue with other providers
|
|
76
|
+
results.append(SyncResult.from_error(provider.name, e))
|
|
77
|
+
console.print(f" [yellow]⚠[/yellow] {provider.emoji} {provider.name}: [red]{e}[/red]")
|
|
78
|
+
|
|
79
|
+
# Render user Jinja templates
|
|
80
|
+
template_result = None
|
|
81
|
+
if render_templates:
|
|
82
|
+
console.print("\n[bold cyan]📝 Rendering templates[/bold cyan]\n")
|
|
83
|
+
template_result = render_all_templates(project_path, config, console)
|
|
84
|
+
|
|
85
|
+
# Separate successful and failed results
|
|
86
|
+
successful_results = [r for r in results if r.success]
|
|
87
|
+
failed_results = [r for r in results if not r.success]
|
|
88
|
+
|
|
89
|
+
# Print summary with appropriate status
|
|
90
|
+
if failed_results:
|
|
91
|
+
if successful_results:
|
|
92
|
+
console.print("\n[bold yellow]⚠ Sync Completed with Errors[/bold yellow]\n")
|
|
93
|
+
else:
|
|
94
|
+
console.print("\n[bold red]✗ Sync Failed[/bold red]\n")
|
|
95
|
+
else:
|
|
96
|
+
console.print("\n[bold green]✓ Sync Complete[/bold green]\n")
|
|
97
|
+
|
|
98
|
+
has_results = False
|
|
99
|
+
|
|
100
|
+
# Show successful syncs
|
|
101
|
+
for result in successful_results:
|
|
102
|
+
if result.items_synced > 0:
|
|
103
|
+
has_results = True
|
|
104
|
+
console.print(f" [dim]{result.provider_name}:[/dim] {result.get_summary()}")
|
|
105
|
+
|
|
106
|
+
# Show template results
|
|
107
|
+
if template_result and (template_result.templates_rendered > 0 or template_result.templates_failed > 0):
|
|
108
|
+
has_results = True
|
|
109
|
+
console.print(f" [dim]Templates:[/dim] {template_result.get_summary()}")
|
|
110
|
+
|
|
111
|
+
# Show errors section if any
|
|
112
|
+
if failed_results:
|
|
113
|
+
has_results = True
|
|
114
|
+
console.print("\n [bold red]Errors:[/bold red]")
|
|
115
|
+
for result in failed_results:
|
|
116
|
+
console.print(f" [red]•[/red] {result.provider_name}: {result.error}")
|
|
117
|
+
|
|
118
|
+
if not has_results:
|
|
119
|
+
console.print(" [dim]Nothing to sync[/dim]")
|
|
120
|
+
|
|
121
|
+
console.print()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
__all__ = ["sync"]
|