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.
Files changed (98) hide show
  1. nao_core/__init__.py +2 -0
  2. nao_core/__init__.py.bak +2 -0
  3. nao_core/bin/build-info.json +5 -0
  4. nao_core/bin/fastapi/main.py +268 -0
  5. nao_core/bin/fastapi/test_main.py +156 -0
  6. nao_core/bin/migrations-postgres/0000_user_auth_and_chat_tables.sql +98 -0
  7. nao_core/bin/migrations-postgres/0001_message_feedback.sql +9 -0
  8. nao_core/bin/migrations-postgres/0002_chat_message_stop_reason_and_error_message.sql +2 -0
  9. nao_core/bin/migrations-postgres/0003_handle_slack_with_thread.sql +2 -0
  10. nao_core/bin/migrations-postgres/0004_input_and_output_tokens.sql +8 -0
  11. nao_core/bin/migrations-postgres/0005_add_project_tables.sql +39 -0
  12. nao_core/bin/migrations-postgres/0006_llm_model_ids.sql +4 -0
  13. nao_core/bin/migrations-postgres/0007_chat_message_llm_info.sql +2 -0
  14. nao_core/bin/migrations-postgres/meta/0000_snapshot.json +707 -0
  15. nao_core/bin/migrations-postgres/meta/0001_snapshot.json +766 -0
  16. nao_core/bin/migrations-postgres/meta/0002_snapshot.json +778 -0
  17. nao_core/bin/migrations-postgres/meta/0003_snapshot.json +799 -0
  18. nao_core/bin/migrations-postgres/meta/0004_snapshot.json +847 -0
  19. nao_core/bin/migrations-postgres/meta/0005_snapshot.json +1129 -0
  20. nao_core/bin/migrations-postgres/meta/0006_snapshot.json +1141 -0
  21. nao_core/bin/migrations-postgres/meta/_journal.json +62 -0
  22. nao_core/bin/migrations-sqlite/0000_user_auth_and_chat_tables.sql +98 -0
  23. nao_core/bin/migrations-sqlite/0001_message_feedback.sql +8 -0
  24. nao_core/bin/migrations-sqlite/0002_chat_message_stop_reason_and_error_message.sql +2 -0
  25. nao_core/bin/migrations-sqlite/0003_handle_slack_with_thread.sql +2 -0
  26. nao_core/bin/migrations-sqlite/0004_input_and_output_tokens.sql +8 -0
  27. nao_core/bin/migrations-sqlite/0005_add_project_tables.sql +38 -0
  28. nao_core/bin/migrations-sqlite/0006_llm_model_ids.sql +4 -0
  29. nao_core/bin/migrations-sqlite/0007_chat_message_llm_info.sql +2 -0
  30. nao_core/bin/migrations-sqlite/meta/0000_snapshot.json +674 -0
  31. nao_core/bin/migrations-sqlite/meta/0001_snapshot.json +735 -0
  32. nao_core/bin/migrations-sqlite/meta/0002_snapshot.json +749 -0
  33. nao_core/bin/migrations-sqlite/meta/0003_snapshot.json +763 -0
  34. nao_core/bin/migrations-sqlite/meta/0004_snapshot.json +819 -0
  35. nao_core/bin/migrations-sqlite/meta/0005_snapshot.json +1086 -0
  36. nao_core/bin/migrations-sqlite/meta/0006_snapshot.json +1100 -0
  37. nao_core/bin/migrations-sqlite/meta/_journal.json +62 -0
  38. nao_core/bin/nao-chat-server +0 -0
  39. nao_core/bin/public/assets/code-block-F6WJLWQG-CV0uOmNJ.js +153 -0
  40. nao_core/bin/public/assets/index-DcbndLHo.css +1 -0
  41. nao_core/bin/public/assets/index-t1hZI3nl.js +560 -0
  42. nao_core/bin/public/favicon.ico +0 -0
  43. nao_core/bin/public/index.html +18 -0
  44. nao_core/bin/rg +0 -0
  45. nao_core/commands/__init__.py +6 -0
  46. nao_core/commands/chat.py +225 -0
  47. nao_core/commands/debug.py +158 -0
  48. nao_core/commands/init.py +358 -0
  49. nao_core/commands/sync/__init__.py +124 -0
  50. nao_core/commands/sync/accessors.py +290 -0
  51. nao_core/commands/sync/cleanup.py +156 -0
  52. nao_core/commands/sync/providers/__init__.py +32 -0
  53. nao_core/commands/sync/providers/base.py +113 -0
  54. nao_core/commands/sync/providers/databases/__init__.py +17 -0
  55. nao_core/commands/sync/providers/databases/bigquery.py +79 -0
  56. nao_core/commands/sync/providers/databases/databricks.py +79 -0
  57. nao_core/commands/sync/providers/databases/duckdb.py +78 -0
  58. nao_core/commands/sync/providers/databases/postgres.py +79 -0
  59. nao_core/commands/sync/providers/databases/provider.py +129 -0
  60. nao_core/commands/sync/providers/databases/snowflake.py +79 -0
  61. nao_core/commands/sync/providers/notion/__init__.py +5 -0
  62. nao_core/commands/sync/providers/notion/provider.py +205 -0
  63. nao_core/commands/sync/providers/repositories/__init__.py +5 -0
  64. nao_core/commands/sync/providers/repositories/provider.py +134 -0
  65. nao_core/commands/sync/registry.py +23 -0
  66. nao_core/config/__init__.py +30 -0
  67. nao_core/config/base.py +100 -0
  68. nao_core/config/databases/__init__.py +55 -0
  69. nao_core/config/databases/base.py +85 -0
  70. nao_core/config/databases/bigquery.py +99 -0
  71. nao_core/config/databases/databricks.py +79 -0
  72. nao_core/config/databases/duckdb.py +41 -0
  73. nao_core/config/databases/postgres.py +83 -0
  74. nao_core/config/databases/snowflake.py +125 -0
  75. nao_core/config/exceptions.py +7 -0
  76. nao_core/config/llm/__init__.py +19 -0
  77. nao_core/config/notion/__init__.py +8 -0
  78. nao_core/config/repos/__init__.py +3 -0
  79. nao_core/config/repos/base.py +11 -0
  80. nao_core/config/slack/__init__.py +12 -0
  81. nao_core/context/__init__.py +54 -0
  82. nao_core/context/base.py +57 -0
  83. nao_core/context/git.py +177 -0
  84. nao_core/context/local.py +59 -0
  85. nao_core/main.py +13 -0
  86. nao_core/templates/__init__.py +41 -0
  87. nao_core/templates/context.py +193 -0
  88. nao_core/templates/defaults/databases/columns.md.j2 +23 -0
  89. nao_core/templates/defaults/databases/description.md.j2 +32 -0
  90. nao_core/templates/defaults/databases/preview.md.j2 +22 -0
  91. nao_core/templates/defaults/databases/profiling.md.j2 +34 -0
  92. nao_core/templates/engine.py +133 -0
  93. nao_core/templates/render.py +196 -0
  94. nao_core-0.0.38.dist-info/METADATA +150 -0
  95. nao_core-0.0.38.dist-info/RECORD +98 -0
  96. nao_core-0.0.38.dist-info/WHEEL +4 -0
  97. nao_core-0.0.38.dist-info/entry_points.txt +2 -0
  98. nao_core-0.0.38.dist-info/licenses/LICENSE +22 -0
Binary file
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="icon" href="/favicon.ico" />
7
+ <meta name="theme-color" content="#000000" />
8
+ <meta name="description" content="Web site created using create-tsrouter-app" />
9
+ <link rel="apple-touch-icon" href="/logo192.png" />
10
+ <link rel="manifest" href="/manifest.json" />
11
+ <title>nao — Chat with your data</title>
12
+ <script type="module" crossorigin src="/assets/index-t1hZI3nl.js"></script>
13
+ <link rel="stylesheet" crossorigin href="/assets/index-DcbndLHo.css">
14
+ </head>
15
+ <body>
16
+ <div id="app"></div>
17
+ </body>
18
+ </html>
nao_core/bin/rg ADDED
Binary file
@@ -0,0 +1,6 @@
1
+ from nao_core.commands.chat import chat
2
+ from nao_core.commands.debug import debug
3
+ from nao_core.commands.init import init
4
+ from nao_core.commands.sync import sync
5
+
6
+ __all__ = ["chat", "debug", "init", "sync"]
@@ -0,0 +1,225 @@
1
+ import os
2
+ import secrets
3
+ import subprocess
4
+ import sys
5
+ import webbrowser
6
+ from pathlib import Path
7
+ from time import sleep
8
+
9
+ from rich.console import Console
10
+
11
+ from nao_core.config import NaoConfig
12
+
13
+ console = Console()
14
+
15
+ # Default port for the nao chat server
16
+ SERVER_PORT = 5005
17
+ FASTAPI_PORT = 8005
18
+ SECRET_FILE_NAME = ".nao-secret"
19
+
20
+
21
+ def get_server_binary_path() -> Path:
22
+ """Get the path to the bundled nao-chat-server binary."""
23
+ # The binary is in the bin folder relative to this file
24
+ cli_dir = Path(__file__).parent.parent
25
+ bin_dir = cli_dir / "bin"
26
+ binary_path = bin_dir / "nao-chat-server"
27
+
28
+ if not binary_path.exists():
29
+ console.print(f"[bold red]✗[/bold red] Server binary not found at {binary_path}")
30
+ console.print("[dim]Make sure you've built the server with ./scripts/build-server.sh[/dim]")
31
+ sys.exit(1)
32
+
33
+ return binary_path
34
+
35
+
36
+ def get_fastapi_main_path() -> Path:
37
+ """Get the path to the FastAPI main.py file."""
38
+ cli_dir = Path(__file__).parent.parent
39
+ bin_dir = cli_dir / "bin"
40
+ fastapi_path = bin_dir / "fastapi" / "main.py"
41
+
42
+ if not fastapi_path.exists():
43
+ console.print(f"[bold red]✗[/bold red] FastAPI main.py not found at {fastapi_path}")
44
+ sys.exit(1)
45
+
46
+ return fastapi_path
47
+
48
+
49
+ def wait_for_server(port: int, timeout: int = 30) -> bool:
50
+ """Wait for the server to be ready."""
51
+ import socket
52
+
53
+ for _ in range(timeout * 10): # Check every 100ms
54
+ try:
55
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
56
+ sock.settimeout(0.1)
57
+ result = sock.connect_ex(("localhost", port))
58
+ if result == 0:
59
+ return True
60
+ except OSError:
61
+ pass
62
+ sleep(0.1)
63
+ return False
64
+
65
+
66
+ def ensure_auth_secret(bin_dir: Path) -> str | None:
67
+ """Ensure auth secret exists, generating one if needed.
68
+
69
+ Returns the secret value if one was loaded/generated, or None if
70
+ BETTER_AUTH_SECRET is already set in the environment.
71
+ """
72
+ # If already set via environment, nothing to do
73
+ if os.environ.get("BETTER_AUTH_SECRET"):
74
+ return None
75
+
76
+ secret_path = bin_dir / SECRET_FILE_NAME
77
+
78
+ # Try to load existing secret from file
79
+ if secret_path.exists():
80
+ try:
81
+ secret = secret_path.read_text().strip()
82
+ if secret:
83
+ console.print(f"[bold green]✓[/bold green] Loaded auth secret from {secret_path}")
84
+ return secret
85
+ except Exception:
86
+ pass # Fall through to generate new secret
87
+
88
+ # Generate and save new secret
89
+ new_secret = secrets.token_urlsafe(32)
90
+ try:
91
+ secret_path.write_text(new_secret)
92
+ # Set restrictive permissions (owner read/write only)
93
+ secret_path.chmod(0o600)
94
+ console.print(f"[bold green]✓[/bold green] Generated new auth secret and saved to {secret_path}")
95
+ return new_secret
96
+ except Exception as e:
97
+ console.print(f"[bold yellow]⚠[/bold yellow] Could not save auth secret to {secret_path}: {e}")
98
+ console.print("[dim]Sessions will not persist across restarts[/dim]")
99
+ return new_secret
100
+
101
+
102
+ def chat():
103
+ """Start the nao chat UI.
104
+
105
+ Launches the nao chat server and opens the web interface in your browser.
106
+ """
107
+ console.print("\n[bold cyan]💬 Starting nao chat...[/bold cyan]\n")
108
+
109
+ # Try to load nao config from current directory
110
+ config = NaoConfig.try_load()
111
+ if config:
112
+ console.print(f"[bold green]✓[/bold green] Loaded config from {Path.cwd() / 'nao_config.yaml'}")
113
+ else:
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]")
124
+
125
+ # Start the server processes
126
+ chat_process = None
127
+ fastapi_process = None
128
+
129
+ def shutdown_servers():
130
+ """Gracefully shut down both server processes."""
131
+ for name, proc in [("Chat server", chat_process), ("FastAPI server", fastapi_process)]:
132
+ if proc:
133
+ proc.terminate()
134
+ try:
135
+ proc.wait(timeout=5)
136
+ except subprocess.TimeoutExpired:
137
+ proc.kill()
138
+ proc.wait()
139
+
140
+ try:
141
+ # Set up environment - inherit from parent but ensure we're in the bin dir
142
+ # so the server can find the public folder
143
+ env = os.environ.copy()
144
+
145
+ # Ensure auth secret is available
146
+ auth_secret = ensure_auth_secret(bin_dir)
147
+ if auth_secret:
148
+ env["BETTER_AUTH_SECRET"] = auth_secret
149
+
150
+ # Set LLM API key from config if available
151
+ if config and config.llm:
152
+ env_var_name = f"{config.llm.provider.upper()}_API_KEY"
153
+ env[env_var_name] = config.llm.api_key
154
+ console.print(f"[bold green]✓[/bold green] Set {env_var_name} from config")
155
+
156
+ # Set Slack config if available
157
+ if config and config.slack:
158
+ env["SLACK_BOT_TOKEN"] = config.slack.bot_token
159
+ env["SLACK_SIGNING_SECRET"] = config.slack.signing_secret
160
+ console.print("[bold green]✓[/bold green] Set Slack environment variables from config")
161
+
162
+ env["NAO_DEFAULT_PROJECT_PATH"] = str(Path.cwd())
163
+ env["FASTAPI_URL"] = f"http://localhost:{FASTAPI_PORT}"
164
+
165
+ # Start the FastAPI server first
166
+ fastapi_path = get_fastapi_main_path()
167
+ console.print(f"[dim]FastAPI server: {fastapi_path}[/dim]")
168
+
169
+ fastapi_process = subprocess.Popen(
170
+ [sys.executable, str(fastapi_path)],
171
+ env=env,
172
+ stdout=subprocess.DEVNULL,
173
+ stderr=subprocess.DEVNULL,
174
+ )
175
+
176
+ console.print("[bold green]✓[/bold green] FastAPI server starting...")
177
+
178
+ # Wait for FastAPI server to be ready
179
+ if wait_for_server(FASTAPI_PORT):
180
+ console.print(f"[bold green]✓[/bold green] FastAPI server ready at http://localhost:{FASTAPI_PORT}")
181
+ else:
182
+ console.print("[bold yellow]⚠[/bold yellow] FastAPI server is taking longer than expected to start...")
183
+
184
+ # Start the chat server
185
+ chat_process = subprocess.Popen(
186
+ [str(binary_path)],
187
+ cwd=str(bin_dir),
188
+ env=env,
189
+ stdout=subprocess.PIPE,
190
+ stderr=subprocess.STDOUT,
191
+ text=True,
192
+ )
193
+
194
+ console.print("[bold green]✓[/bold green] Chat server starting...")
195
+
196
+ # Wait for the chat server to be ready
197
+ if wait_for_server(SERVER_PORT):
198
+ url = f"http://localhost:{SERVER_PORT}"
199
+ console.print(f"[bold green]✓[/bold green] Chat server ready at {url}")
200
+ console.print("\n[bold]Opening browser...[/bold]")
201
+ webbrowser.open(url)
202
+ console.print("\n[dim]Press Ctrl+C to stop the servers[/dim]\n")
203
+ else:
204
+ console.print("[bold yellow]⚠[/bold yellow] Chat server is taking longer than expected to start...")
205
+ console.print(f"[dim]Check http://localhost:{SERVER_PORT} manually[/dim]")
206
+
207
+ # Stream chat server output to console
208
+ if chat_process.stdout:
209
+ for line in chat_process.stdout:
210
+ # Filter out some of the verbose logging if needed
211
+ console.print(f"[dim]{line.rstrip()}[/dim]")
212
+
213
+ # Wait for process to complete
214
+ chat_process.wait()
215
+
216
+ except KeyboardInterrupt:
217
+ console.print("\n[bold yellow]Shutting down...[/bold yellow]")
218
+ shutdown_servers()
219
+ console.print("[bold green]✓[/bold green] Servers stopped")
220
+ sys.exit(0)
221
+
222
+ except Exception as e:
223
+ console.print(f"[bold red]✗[/bold red] Failed to start servers: {e}")
224
+ shutdown_servers()
225
+ sys.exit(1)
@@ -0,0 +1,158 @@
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
+ from nao_core.config.databases import AnyDatabaseConfig
8
+
9
+ console = Console()
10
+
11
+
12
+ def test_database_connection(db_config: AnyDatabaseConfig) -> tuple[bool, str]:
13
+ """Test connectivity to a database.
14
+
15
+ Returns:
16
+ Tuple of (success, message)
17
+ """
18
+ try:
19
+ conn = db_config.connect()
20
+ # Run a simple query to verify the connection works
21
+ if hasattr(db_config, "dataset_id") and db_config.dataset_id:
22
+ # If dataset is specified, list tables in that dataset
23
+ tables = conn.list_tables()
24
+ table_count = len(tables)
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)"
31
+ else:
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"
34
+ except Exception as e:
35
+ return False, str(e)
36
+
37
+
38
+ def test_llm_connection(llm_config) -> tuple[bool, str]:
39
+ """Test connectivity to an LLM provider.
40
+
41
+ Returns:
42
+ Tuple of (success, message)
43
+ """
44
+ try:
45
+ if llm_config.provider.value == "openai":
46
+ import openai
47
+
48
+ client = openai.OpenAI(api_key=llm_config.api_key)
49
+ # Make a minimal API call to verify the key works
50
+ models = client.models.list()
51
+ # Just check we can iterate (don't need to consume all)
52
+ model_count = sum(1 for _ in models)
53
+ return True, f"Connected successfully ({model_count} models available)"
54
+ elif llm_config.provider.value == "anthropic":
55
+ from anthropic import Anthropic
56
+
57
+ client = Anthropic(api_key=llm_config.api_key)
58
+
59
+ models = client.models.list()
60
+
61
+ model_count = sum(1 for _ in models)
62
+ return True, f"Connected successfully ({model_count} models available)"
63
+ else:
64
+ return False, f"Unknown provider: {llm_config.provider}"
65
+ except Exception as e:
66
+ return False, str(e)
67
+
68
+
69
+ def debug():
70
+ """Test connectivity to configured databases and LLMs.
71
+
72
+ Loads the nao configuration from the current directory and tests
73
+ connections to all configured databases and LLM providers.
74
+ """
75
+ console.print("\n[bold cyan]🔍 nao debug - Testing connections...[/bold cyan]\n")
76
+
77
+ # Load config
78
+ config = NaoConfig.try_load()
79
+ if not config:
80
+ console.print("[bold red]✗[/bold red] No nao_config.yaml found in current directory")
81
+ console.print("[dim]Run 'nao init' to create a configuration file[/dim]")
82
+ sys.exit(1)
83
+ assert config is not None # Help type checker after sys.exit
84
+
85
+ console.print(f"[bold green]✓[/bold green] Loaded config: [cyan]{config.project_name}[/cyan]\n")
86
+
87
+ # Test databases
88
+ if config.databases:
89
+ console.print("[bold]Databases:[/bold]")
90
+ db_table = Table(show_header=True, header_style="bold")
91
+ db_table.add_column("Name")
92
+ db_table.add_column("Type")
93
+ db_table.add_column("Status")
94
+ db_table.add_column("Details")
95
+
96
+ for db in config.databases:
97
+ console.print(f" Testing [cyan]{db.name}[/cyan]...", end=" ")
98
+ success, message = test_database_connection(db)
99
+
100
+ if success:
101
+ console.print("[bold green]✓[/bold green]")
102
+ db_table.add_row(
103
+ db.name,
104
+ db.type,
105
+ "[green]Connected[/green]",
106
+ message,
107
+ )
108
+ else:
109
+ console.print("[bold red]✗[/bold red]")
110
+ # Truncate long error messages
111
+ short_msg = message[:80] + "..." if len(message) > 80 else message
112
+ db_table.add_row(
113
+ db.name,
114
+ db.type,
115
+ "[red]Failed[/red]",
116
+ short_msg,
117
+ )
118
+
119
+ console.print()
120
+ console.print(db_table)
121
+ else:
122
+ console.print("[dim]No databases configured[/dim]")
123
+
124
+ console.print()
125
+
126
+ # Test LLM
127
+ if config.llm:
128
+ console.print("[bold]LLM Provider:[/bold]")
129
+ llm_table = Table(show_header=True, header_style="bold")
130
+ llm_table.add_column("Provider")
131
+ llm_table.add_column("Status")
132
+ llm_table.add_column("Details")
133
+
134
+ console.print(f" Testing [cyan]{config.llm.provider.value}[/cyan]...", end=" ")
135
+ success, message = test_llm_connection(config.llm)
136
+
137
+ if success:
138
+ console.print("[bold green]✓[/bold green]")
139
+ llm_table.add_row(
140
+ config.llm.provider.value,
141
+ "[green]Connected[/green]",
142
+ message,
143
+ )
144
+ else:
145
+ console.print("[bold red]✗[/bold red]")
146
+ short_msg = message[:80] + "..." if len(message) > 80 else message
147
+ llm_table.add_row(
148
+ config.llm.provider.value,
149
+ "[red]Failed[/red]",
150
+ short_msg,
151
+ )
152
+
153
+ console.print()
154
+ console.print(llm_table)
155
+ else:
156
+ console.print("[dim]No LLM configured[/dim]")
157
+
158
+ console.print()