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.
Files changed (73) hide show
  1. nao_core/__init__.py +1 -1
  2. nao_core/bin/fastapi/main.py +21 -2
  3. nao_core/bin/fastapi/test_main.py +156 -0
  4. nao_core/bin/migrations-postgres/0004_input_and_output_tokens.sql +8 -0
  5. nao_core/bin/migrations-postgres/0005_add_project_tables.sql +39 -0
  6. nao_core/bin/migrations-postgres/meta/0004_snapshot.json +847 -0
  7. nao_core/bin/migrations-postgres/meta/0005_snapshot.json +1129 -0
  8. nao_core/bin/migrations-postgres/meta/_journal.json +14 -0
  9. nao_core/bin/migrations-sqlite/0004_input_and_output_tokens.sql +8 -0
  10. nao_core/bin/migrations-sqlite/0005_add_project_tables.sql +38 -0
  11. nao_core/bin/migrations-sqlite/meta/0004_snapshot.json +819 -0
  12. nao_core/bin/migrations-sqlite/meta/0005_snapshot.json +1086 -0
  13. nao_core/bin/migrations-sqlite/meta/_journal.json +14 -0
  14. nao_core/bin/nao-chat-server +0 -0
  15. nao_core/bin/public/assets/{code-block-F6WJLWQG-EQr6mTlQ.js → code-block-F6WJLWQG-TAi8koem.js} +3 -3
  16. nao_core/bin/public/assets/index-BfHcd9Xz.css +1 -0
  17. nao_core/bin/public/assets/index-Mzo9bkag.js +557 -0
  18. nao_core/bin/public/index.html +2 -2
  19. nao_core/commands/chat.py +11 -10
  20. nao_core/commands/debug.py +10 -6
  21. nao_core/commands/init.py +66 -27
  22. nao_core/commands/sync/__init__.py +40 -21
  23. nao_core/commands/sync/accessors.py +219 -141
  24. nao_core/commands/sync/cleanup.py +133 -0
  25. nao_core/commands/sync/providers/__init__.py +30 -0
  26. nao_core/commands/sync/providers/base.py +87 -0
  27. nao_core/commands/sync/providers/databases/__init__.py +17 -0
  28. nao_core/commands/sync/providers/databases/bigquery.py +78 -0
  29. nao_core/commands/sync/providers/databases/databricks.py +79 -0
  30. nao_core/commands/sync/providers/databases/duckdb.py +83 -0
  31. nao_core/commands/sync/providers/databases/postgres.py +78 -0
  32. nao_core/commands/sync/providers/databases/provider.py +123 -0
  33. nao_core/commands/sync/providers/databases/snowflake.py +78 -0
  34. nao_core/commands/sync/providers/repositories/__init__.py +5 -0
  35. nao_core/commands/sync/{repositories.py → providers/repositories/provider.py} +43 -20
  36. nao_core/config/__init__.py +16 -1
  37. nao_core/config/base.py +23 -4
  38. nao_core/config/databases/__init__.py +37 -11
  39. nao_core/config/databases/base.py +7 -0
  40. nao_core/config/databases/bigquery.py +29 -1
  41. nao_core/config/databases/databricks.py +69 -0
  42. nao_core/config/databases/duckdb.py +33 -0
  43. nao_core/config/databases/postgres.py +78 -0
  44. nao_core/config/databases/snowflake.py +115 -0
  45. nao_core/config/exceptions.py +7 -0
  46. nao_core/templates/__init__.py +12 -0
  47. nao_core/templates/defaults/databases/columns.md.j2 +23 -0
  48. nao_core/templates/defaults/databases/description.md.j2 +32 -0
  49. nao_core/templates/defaults/databases/preview.md.j2 +22 -0
  50. nao_core/templates/defaults/databases/profiling.md.j2 +34 -0
  51. nao_core/templates/engine.py +133 -0
  52. {nao_core-0.0.29.dist-info → nao_core-0.0.31.dist-info}/METADATA +9 -4
  53. nao_core-0.0.31.dist-info/RECORD +86 -0
  54. nao_core/bin/public/assets/_chat-layout-BTlqRUE5.js +0 -1
  55. nao_core/bin/public/assets/_chat-layout.index-DOARokp1.js +0 -1
  56. nao_core/bin/public/assets/agentProvider-C6dGIy-H.js +0 -1
  57. nao_core/bin/public/assets/button-By_1dzVx.js +0 -1
  58. nao_core/bin/public/assets/folder-DnRS5rg3.js +0 -1
  59. nao_core/bin/public/assets/index-CElAN2SH.css +0 -1
  60. nao_core/bin/public/assets/index-ZTHASguQ.js +0 -59
  61. nao_core/bin/public/assets/input-CUQA5tsi.js +0 -1
  62. nao_core/bin/public/assets/login-BUQDum3t.js +0 -1
  63. nao_core/bin/public/assets/mermaid-FSSLJTFX-Dc6ZvCPw.js +0 -427
  64. nao_core/bin/public/assets/sidebar-bgEk7Xg8.js +0 -1
  65. nao_core/bin/public/assets/signinForm-CGAhnAkv.js +0 -1
  66. nao_core/bin/public/assets/signup-D2n11La3.js +0 -1
  67. nao_core/bin/public/assets/user-CYl8Tly2.js +0 -1
  68. nao_core/bin/public/assets/utils-DzJYey0s.js +0 -1
  69. nao_core/commands/sync/databases.py +0 -132
  70. nao_core-0.0.29.dist-info/RECORD +0 -69
  71. {nao_core-0.0.29.dist-info → nao_core-0.0.31.dist-info}/WHEEL +0 -0
  72. {nao_core-0.0.29.dist-info → nao_core-0.0.31.dist-info}/entry_points.txt +0 -0
  73. {nao_core-0.0.29.dist-info → nao_core-0.0.31.dist-info}/licenses/LICENSE +0 -0
@@ -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-ZTHASguQ.js"></script>
13
- <link rel="stylesheet" crossorigin href="/assets/index-CElAN2SH.css">
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("[dim]No nao_config.yaml found in current directory[/dim]")
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["NAO_PROJECT_FOLDER"] = str(Path.cwd())
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,
@@ -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
- # 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)"
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 AnyDatabaseConfig, BigQueryConfig, DatabaseType, LLMConfig, LLMProvider, NaoConfig, SlackConfig
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
- console.print("\n[bold cyan]BigQuery Configuration[/bold cyan]\n")
94
+ return BigQueryConfig.promptConfig()
81
95
 
82
- name = Prompt.ask("[bold]Connection name[/bold]", default="bigquery-prod")
83
96
 
84
- project_id = Prompt.ask("[bold]GCP Project ID[/bold]")
85
- if not project_id:
86
- raise InitError("GCP Project ID cannot be empty.")
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
- credentials_path = Prompt.ask(
91
- "[bold]Service account JSON path[/bold] [dim](optional, uses ADC if empty)[/dim]",
92
- default="",
93
- )
102
+ def setup_databricks() -> DatabricksConfig:
103
+ """Setup a Databricks database configuration."""
104
+ return DatabricksConfig.promptConfig()
94
105
 
95
- return BigQueryConfig(
96
- name=name,
97
- project_id=project_id,
98
- dataset_id=dataset_id or None,
99
- credentials_path=credentials_path or None,
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[str]]:
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 = ["RULES.md"]
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
- file_path.touch()
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 .databases import sync_databases
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(output_dir: str = "databases", repos_dir: str = "repos"):
17
- """Sync repositories and database schemas to local files.
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/bigquery/<connection>/<dataset>/<table>/*.md (database schemas)
23
+ - databases/<type>/<connection>/<dataset>/<table>/*.md (database schemas)
22
24
 
23
25
  Args:
24
- output_dir: Output directory for database schemas (default: "databases")
25
- repos_dir: Output directory for repositories (default: "repos")
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 not config:
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
- repos_synced = 0
38
- if config.repos:
39
- repos_path = Path(repos_dir)
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
- db_path = Path(output_dir)
43
- datasets_synced, tables_synced = sync_databases(config.databases, db_path)
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
- console.print("\n[bold green]✓ Sync Complete[/bold green]\n")
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
- if repos_synced > 0:
48
- console.print(f" [dim]Repositories:[/dim] {repos_synced} synced")
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
- if tables_synced > 0:
51
- console.print(f" [dim]Databases:[/dim] {tables_synced} tables across {datasets_synced} datasets")
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 repos_synced == 0 and tables_synced == 0:
72
+ if not has_results:
54
73
  console.print(" [dim]Nothing to sync[/dim]")
55
74
 
56
75
  console.print()