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.
@@ -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
+ ```
@@ -1,3 +1,2 @@
1
1
  # nao Core CLI package
2
- __version__ = "0.0.9"
3
-
2
+ __version__ = "0.0.11"
@@ -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,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}")