@utopia-ai/cli 0.1.0

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 (49) hide show
  1. package/.claude/settings.json +1 -0
  2. package/.claude/settings.local.json +38 -0
  3. package/bin/utopia.js +20 -0
  4. package/package.json +46 -0
  5. package/python/README.md +34 -0
  6. package/python/instrumenter/instrument.py +1148 -0
  7. package/python/pyproject.toml +32 -0
  8. package/python/setup.py +27 -0
  9. package/python/utopia_runtime/__init__.py +30 -0
  10. package/python/utopia_runtime/__pycache__/__init__.cpython-313.pyc +0 -0
  11. package/python/utopia_runtime/__pycache__/client.cpython-313.pyc +0 -0
  12. package/python/utopia_runtime/__pycache__/probe.cpython-313.pyc +0 -0
  13. package/python/utopia_runtime/client.py +31 -0
  14. package/python/utopia_runtime/probe.py +446 -0
  15. package/python/utopia_runtime.egg-info/PKG-INFO +59 -0
  16. package/python/utopia_runtime.egg-info/SOURCES.txt +10 -0
  17. package/python/utopia_runtime.egg-info/dependency_links.txt +1 -0
  18. package/python/utopia_runtime.egg-info/top_level.txt +1 -0
  19. package/scripts/publish-npm.sh +14 -0
  20. package/scripts/publish-pypi.sh +17 -0
  21. package/src/cli/commands/codex.ts +193 -0
  22. package/src/cli/commands/context.ts +188 -0
  23. package/src/cli/commands/destruct.ts +237 -0
  24. package/src/cli/commands/easter-eggs.ts +203 -0
  25. package/src/cli/commands/init.ts +505 -0
  26. package/src/cli/commands/instrument.ts +962 -0
  27. package/src/cli/commands/mcp.ts +16 -0
  28. package/src/cli/commands/serve.ts +194 -0
  29. package/src/cli/commands/status.ts +304 -0
  30. package/src/cli/commands/validate.ts +328 -0
  31. package/src/cli/index.ts +37 -0
  32. package/src/cli/utils/config.ts +54 -0
  33. package/src/graph/index.ts +687 -0
  34. package/src/instrumenter/javascript.ts +1798 -0
  35. package/src/mcp/index.ts +886 -0
  36. package/src/runtime/js/index.ts +518 -0
  37. package/src/runtime/js/package-lock.json +30 -0
  38. package/src/runtime/js/package.json +30 -0
  39. package/src/runtime/js/tsconfig.json +16 -0
  40. package/src/server/db/index.ts +26 -0
  41. package/src/server/db/schema.ts +45 -0
  42. package/src/server/index.ts +79 -0
  43. package/src/server/middleware/auth.ts +74 -0
  44. package/src/server/routes/admin.ts +36 -0
  45. package/src/server/routes/graph.ts +358 -0
  46. package/src/server/routes/probes.ts +286 -0
  47. package/src/types.ts +147 -0
  48. package/src/utopia-mode/index.ts +206 -0
  49. package/tsconfig.json +19 -0
@@ -0,0 +1,16 @@
1
+ import { Command } from 'commander';
2
+
3
+ export const mcpCommand = new Command('mcp')
4
+ .description('Start the Utopia MCP server (used by Claude Code)')
5
+ .option('--endpoint <url>', 'Utopia data service endpoint', process.env.UTOPIA_ENDPOINT || 'http://localhost:7890')
6
+ .option('--project-id <id>', 'Project ID', process.env.UTOPIA_PROJECT_ID || '')
7
+ .action(async (options) => {
8
+ // Set env vars for the MCP server
9
+ process.env.UTOPIA_ENDPOINT = options.endpoint;
10
+ if (options.projectId) {
11
+ process.env.UTOPIA_PROJECT_ID = options.projectId;
12
+ }
13
+
14
+ // Import and start the MCP server
15
+ await import('../../mcp/index.js');
16
+ });
@@ -0,0 +1,194 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { resolve, dirname } from 'node:path';
4
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, openSync } from 'node:fs';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { spawn, execSync } from 'node:child_process';
7
+ import { loadConfig, configExists } from '../utils/config.js';
8
+
9
+ const PID_FILE = '.utopia/serve.pid';
10
+ const LOG_FILE = '.utopia/serve.log';
11
+
12
+ function isPortInUse(port: number): boolean {
13
+ try {
14
+ execSync(`lsof -ti:${port}`, { stdio: 'pipe' });
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ function killPort(port: number): void {
22
+ try {
23
+ execSync(`lsof -ti:${port} | xargs kill -9`, { stdio: 'pipe' });
24
+ } catch { /* nothing on that port */ }
25
+ }
26
+
27
+ function readRunningPid(cwd: string): number | null {
28
+ const pidPath = resolve(cwd, PID_FILE);
29
+ if (!existsSync(pidPath)) return null;
30
+ try {
31
+ const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
32
+ if (isNaN(pid)) return null;
33
+ process.kill(pid, 0); // throws if not running
34
+ return pid;
35
+ } catch {
36
+ try { unlinkSync(pidPath); } catch { /* ignore */ }
37
+ return null;
38
+ }
39
+ }
40
+
41
+ export const serveCommand = new Command('serve')
42
+ .description('Start the Utopia data service')
43
+ .option('--port <port>', 'Port number for the data service')
44
+ .option('--db <path>', 'Path to the SQLite database file')
45
+ .option('-b, --background', 'Run the server as a background process')
46
+ .option('--stop', 'Stop a running background server')
47
+ .action(async (options) => {
48
+ const cwd = process.cwd();
49
+ const isBackgroundChild = process.env.__UTOPIA_BG_CHILD === '1';
50
+
51
+ // Handle --stop
52
+ if (options.stop) {
53
+ const pid = readRunningPid(cwd);
54
+ if (pid) {
55
+ try { process.kill(pid, 'SIGTERM'); } catch { /* already dead */ }
56
+ try { unlinkSync(resolve(cwd, PID_FILE)); } catch { /* ignore */ }
57
+ console.log(chalk.green(`\n Utopia data service stopped (PID ${pid}).\n`));
58
+ } else {
59
+ console.log(chalk.yellow('\n No running Utopia data service found.\n'));
60
+ }
61
+ return;
62
+ }
63
+
64
+ // Read config for port default
65
+ let defaultPort = 7890;
66
+ const defaultDb = resolve(cwd, '.utopia', 'data.db');
67
+ if (configExists(cwd)) {
68
+ try {
69
+ const config = await loadConfig(cwd);
70
+ if (config.dataEndpoint) {
71
+ try { const url = new URL(config.dataEndpoint); if (url.port) defaultPort = parseInt(url.port, 10); } catch { /* ignore */ }
72
+ }
73
+ } catch { /* ignore */ }
74
+ }
75
+
76
+ const port = options.port ? parseInt(options.port as string, 10) : defaultPort;
77
+ const dbPath = (options.db as string) || defaultDb;
78
+
79
+ if (isNaN(port) || port < 1 || port > 65535) {
80
+ console.log(chalk.red('\n Error: Invalid port number.\n'));
81
+ process.exit(1);
82
+ }
83
+
84
+ // Background mode
85
+ if (options.background) {
86
+ console.log(chalk.bold.cyan('\n Starting Utopia Data Service (background)...\n'));
87
+
88
+ // Check if port is already in use
89
+ if (isPortInUse(port)) {
90
+ // Check if it's our own process
91
+ const existingPid = readRunningPid(cwd);
92
+ if (existingPid) {
93
+ console.log(chalk.yellow(` Already running (PID ${existingPid}) on port ${port}.`));
94
+ console.log(chalk.dim(' Run "utopia serve --stop" first.\n'));
95
+ } else {
96
+ console.log(chalk.red(` Error: Port ${port} is already in use by another process.`));
97
+ console.log(chalk.dim(` Run: lsof -ti:${port} | xargs kill to free it.\n`));
98
+ }
99
+ return;
100
+ }
101
+
102
+ const __filename = fileURLToPath(import.meta.url);
103
+ const binPath = resolve(dirname(__filename), '..', '..', '..', 'bin', 'utopia.js');
104
+ const logPath = resolve(cwd, LOG_FILE);
105
+ const pidPath = resolve(cwd, PID_FILE);
106
+ const logFd = openSync(logPath, 'w');
107
+
108
+ const child = spawn(
109
+ process.execPath,
110
+ [binPath, 'serve', '--port', String(port), '--db', dbPath],
111
+ {
112
+ cwd,
113
+ detached: true,
114
+ stdio: ['ignore', logFd, logFd],
115
+ env: { ...process.env, __UTOPIA_BG_CHILD: '1' },
116
+ }
117
+ );
118
+
119
+ if (!child.pid) {
120
+ console.log(chalk.red(' Error: Failed to spawn background process.\n'));
121
+ process.exit(1);
122
+ }
123
+
124
+ writeFileSync(pidPath, String(child.pid));
125
+ child.unref();
126
+
127
+ // Wait and verify it actually started
128
+ let healthy = false;
129
+ for (let i = 0; i < 10; i++) {
130
+ await new Promise(r => setTimeout(r, 500));
131
+ try {
132
+ const resp = await fetch(`http://localhost:${port}/api/v1/health`, { signal: AbortSignal.timeout(2000) });
133
+ if (resp.ok) { healthy = true; break; }
134
+ } catch { /* not ready yet */ }
135
+ }
136
+
137
+ if (healthy) {
138
+ console.log(chalk.dim(` Port: ${port}`));
139
+ console.log(chalk.dim(` Database: ${dbPath}`));
140
+ console.log(chalk.dim(` PID: ${child.pid}`));
141
+ console.log(chalk.dim(` Log: ${logPath}`));
142
+ console.log('');
143
+ console.log(chalk.bold.green(' Utopia data service is running.'));
144
+ console.log(chalk.dim(' Run "utopia serve --stop" to stop it.\n'));
145
+ } else {
146
+ // Child likely crashed — read the log
147
+ let logContent = '';
148
+ try { logContent = readFileSync(logPath, 'utf-8').trim(); } catch { /* ignore */ }
149
+ try { unlinkSync(pidPath); } catch { /* ignore */ }
150
+
151
+ console.log(chalk.red(' Error: Server failed to start.\n'));
152
+ if (logContent) {
153
+ // Pull out the useful error
154
+ const addrInUse = logContent.includes('EADDRINUSE');
155
+ if (addrInUse) {
156
+ console.log(chalk.red(` Port ${port} is already in use.`));
157
+ console.log(chalk.dim(` Run: lsof -ti:${port} | xargs kill to free it.\n`));
158
+ } else {
159
+ console.log(chalk.dim(' Log output:'));
160
+ console.log(chalk.dim(' ' + logContent.split('\n').slice(0, 5).join('\n ')));
161
+ console.log('');
162
+ }
163
+ }
164
+ process.exit(1);
165
+ }
166
+ return;
167
+ }
168
+
169
+ // Foreground mode
170
+ if (!isBackgroundChild) {
171
+ console.log(chalk.bold.cyan('\n Starting Utopia Data Service...\n'));
172
+ console.log(chalk.dim(` Port: ${port}`));
173
+ console.log(chalk.dim(` Database: ${dbPath}`));
174
+ }
175
+
176
+ try {
177
+ const { startServer } = await import('../../server/index.js');
178
+ startServer(port, dbPath);
179
+
180
+ if (!isBackgroundChild) {
181
+ console.log('');
182
+ console.log(chalk.bold.green(' Utopia data service is running!'));
183
+ console.log('');
184
+ console.log(` Endpoint: ${chalk.cyan(`http://localhost:${port}`)}`);
185
+ console.log(` Health: ${chalk.cyan(`http://localhost:${port}/api/v1/health`)}`);
186
+ console.log(` Probes: ${chalk.cyan(`http://localhost:${port}/api/v1/probes`)}`);
187
+ console.log('');
188
+ console.log(chalk.dim(' Press Ctrl+C to stop the server.\n'));
189
+ }
190
+ } catch (err) {
191
+ console.error(`[utopia-server] Failed to start: ${(err as Error).message}`);
192
+ process.exit(1);
193
+ }
194
+ });
@@ -0,0 +1,304 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { resolve, extname } from 'node:path';
4
+ import { existsSync, readFileSync } from 'node:fs';
5
+ import { readdir, readFile } from 'node:fs/promises';
6
+ import { loadConfig, configExists } from '../utils/config.js';
7
+
8
+ const IGNORED_DIRS = new Set([
9
+ 'node_modules', '.git', '.utopia', 'dist', 'build', '__pycache__',
10
+ '.next', '.vercel', 'coverage', '.nyc_output', 'venv', '.venv', 'env',
11
+ ]);
12
+
13
+ async function countInstrumentedFiles(dir: string): Promise<{ fileCount: number; probeCount: number }> {
14
+ let fileCount = 0;
15
+ let probeCount = 0;
16
+
17
+ async function walk(currentDir: string): Promise<void> {
18
+ let entries;
19
+ try {
20
+ entries = await readdir(currentDir, { withFileTypes: true });
21
+ } catch {
22
+ return;
23
+ }
24
+
25
+ for (const entry of entries) {
26
+ if (entry.name.startsWith('.') && entry.name !== '.') continue;
27
+ if (IGNORED_DIRS.has(entry.name)) continue;
28
+
29
+ const fullPath = resolve(currentDir, entry.name);
30
+
31
+ if (entry.isDirectory()) {
32
+ await walk(fullPath);
33
+ } else if (entry.isFile()) {
34
+ const ext = extname(entry.name);
35
+ if (['.js', '.jsx', '.ts', '.tsx', '.py'].includes(ext)) {
36
+ try {
37
+ const content = await readFile(fullPath, 'utf-8');
38
+ const jsMatches = content.match(/\/\/ utopia:probe/g);
39
+ const pyMatches = content.match(/# utopia:probe/g);
40
+ const total = (jsMatches?.length ?? 0) + (pyMatches?.length ?? 0);
41
+ if (total > 0) {
42
+ fileCount++;
43
+ probeCount += total;
44
+ }
45
+ } catch {
46
+ // Skip unreadable files
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ await walk(dir);
54
+ return { fileCount, probeCount };
55
+ }
56
+
57
+ interface HealthResponse {
58
+ status: string;
59
+ timestamp: string;
60
+ }
61
+
62
+ interface StatsResponse {
63
+ probes: {
64
+ total: number;
65
+ byType: Record<string, number>;
66
+ };
67
+ graph: {
68
+ nodes: number;
69
+ edges: number;
70
+ };
71
+ }
72
+
73
+ async function checkServiceHealth(endpoint: string): Promise<{ healthy: boolean; latencyMs: number }> {
74
+ try {
75
+ const start = Date.now();
76
+ const url = new URL('/api/v1/health', endpoint);
77
+ const response = await fetch(url.toString(), {
78
+ signal: AbortSignal.timeout(5000),
79
+ });
80
+ const latencyMs = Date.now() - start;
81
+
82
+ if (response.ok) {
83
+ const data = (await response.json()) as HealthResponse;
84
+ return { healthy: data.status === 'ok', latencyMs };
85
+ }
86
+ return { healthy: false, latencyMs };
87
+ } catch {
88
+ return { healthy: false, latencyMs: 0 };
89
+ }
90
+ }
91
+
92
+ async function fetchStats(endpoint: string): Promise<StatsResponse | null> {
93
+ try {
94
+ const url = new URL('/api/v1/admin/stats', endpoint);
95
+ const response = await fetch(url.toString(), {
96
+ signal: AbortSignal.timeout(5000),
97
+ });
98
+
99
+ if (response.ok) {
100
+ return (await response.json()) as StatsResponse;
101
+ }
102
+ return null;
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Check if the utopia-runtime is properly installed as a dependency.
110
+ */
111
+ function checkRuntimeInstalled(cwd: string): { installed: boolean; method: string } {
112
+ // Check for file: dependency approach (.utopia/runtime/)
113
+ const runtimeDir = resolve(cwd, '.utopia', 'runtime');
114
+ if (existsSync(resolve(runtimeDir, 'index.js')) && existsSync(resolve(runtimeDir, 'package.json'))) {
115
+ // Also verify it's in package.json
116
+ try {
117
+ const pkg = JSON.parse(readFileSync(resolve(cwd, 'package.json'), 'utf-8'));
118
+ if (pkg.dependencies?.['utopia-runtime']?.includes('file:')) {
119
+ return { installed: true, method: 'file: dependency (.utopia/runtime)' };
120
+ }
121
+ } catch { /* ignore */ }
122
+ return { installed: true, method: '.utopia/runtime (not yet in package.json)' };
123
+ }
124
+
125
+ // Check for legacy direct node_modules approach
126
+ if (existsSync(resolve(cwd, 'node_modules', 'utopia-runtime', 'index.js'))) {
127
+ return { installed: true, method: 'node_modules (legacy)' };
128
+ }
129
+
130
+ return { installed: false, method: 'not installed' };
131
+ }
132
+
133
+ /**
134
+ * Check if a background serve process is running.
135
+ */
136
+ function checkBackgroundProcess(cwd: string): { running: boolean; pid: number | null } {
137
+ const pidPath = resolve(cwd, '.utopia', 'serve.pid');
138
+ if (!existsSync(pidPath)) return { running: false, pid: null };
139
+
140
+ try {
141
+ const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
142
+ if (isNaN(pid)) return { running: false, pid: null };
143
+
144
+ // Check if process is alive
145
+ process.kill(pid, 0);
146
+ return { running: true, pid };
147
+ } catch {
148
+ return { running: false, pid: null };
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Check if environment variables are configured.
154
+ */
155
+ function checkEnvVars(cwd: string, framework: string): { found: boolean; fileName: string; missing: string[] } {
156
+ const envFileName = framework === 'nextjs' ? '.env.local' : '.env';
157
+ const envFilePath = resolve(cwd, envFileName);
158
+
159
+ const requiredVars = ['UTOPIA_ENDPOINT', 'UTOPIA_PROJECT_ID'];
160
+ const missing: string[] = [];
161
+
162
+ if (!existsSync(envFilePath)) {
163
+ return { found: false, fileName: envFileName, missing: requiredVars };
164
+ }
165
+
166
+ try {
167
+ const content = readFileSync(envFilePath, 'utf-8');
168
+ for (const varName of requiredVars) {
169
+ if (!content.includes(varName + '=')) {
170
+ missing.push(varName);
171
+ }
172
+ }
173
+ return { found: true, fileName: envFileName, missing };
174
+ } catch {
175
+ return { found: false, fileName: envFileName, missing: requiredVars };
176
+ }
177
+ }
178
+
179
+ export const statusCommand = new Command('status')
180
+ .description('Check the status of your Utopia setup')
181
+ .action(async () => {
182
+ const cwd = process.cwd();
183
+
184
+ console.log(chalk.bold.cyan('\n Utopia Status\n'));
185
+
186
+ // 1. Check config
187
+ const hasConfig = configExists(cwd);
188
+ if (hasConfig) {
189
+ console.log(chalk.green(' [x] Configuration found (.utopia/config.json)'));
190
+ } else {
191
+ console.log(chalk.red(' [ ] Configuration not found'));
192
+ console.log(chalk.dim(' Run "utopia init" to set up your project.\n'));
193
+ return;
194
+ }
195
+
196
+ let config;
197
+ try {
198
+ config = await loadConfig(cwd);
199
+ console.log(chalk.dim(` Project: ${config.projectId}`));
200
+ console.log(chalk.dim(` Provider: ${config.cloudProvider} / ${config.service}`));
201
+ console.log(chalk.dim(` Languages: ${config.language.join(', ')}`));
202
+ console.log(chalk.dim(` Framework: ${config.framework}`));
203
+ } catch (err) {
204
+ console.log(chalk.red(` [!] Configuration exists but could not be loaded: ${(err as Error).message}`));
205
+ return;
206
+ }
207
+
208
+ console.log('');
209
+
210
+ // 2. Check environment variables
211
+ const envCheck = checkEnvVars(cwd, config.framework);
212
+ if (envCheck.found && envCheck.missing.length === 0) {
213
+ console.log(chalk.green(` [x] Environment variables configured (${envCheck.fileName})`));
214
+ } else if (envCheck.found && envCheck.missing.length > 0) {
215
+ console.log(chalk.yellow(` [~] Environment variables partially configured (${envCheck.fileName})`));
216
+ console.log(chalk.dim(` Missing: ${envCheck.missing.join(', ')}`));
217
+ } else {
218
+ console.log(chalk.yellow(` [ ] Environment file not found (${envCheck.fileName})`));
219
+ console.log(chalk.dim(' Run "utopia init" to set up environment variables.'));
220
+ }
221
+
222
+ console.log('');
223
+
224
+ // 3. Check runtime installation
225
+ const runtime = checkRuntimeInstalled(cwd);
226
+ if (runtime.installed) {
227
+ console.log(chalk.green(` [x] Runtime installed (${runtime.method})`));
228
+ } else {
229
+ console.log(chalk.yellow(' [ ] Runtime not installed'));
230
+ console.log(chalk.dim(' Run "utopia instrument" to install the runtime and add probes.'));
231
+ }
232
+
233
+ console.log('');
234
+
235
+ // 4. Check instrumented files
236
+ console.log(chalk.dim(' Scanning for instrumented files...'));
237
+ const { fileCount, probeCount } = await countInstrumentedFiles(cwd);
238
+
239
+ if (fileCount > 0) {
240
+ console.log(chalk.green(` [x] Instrumented: ${fileCount} file(s), ${probeCount} probe(s)`));
241
+ } else {
242
+ console.log(chalk.yellow(' [ ] No instrumented files found'));
243
+ console.log(chalk.dim(' Run "utopia instrument" to add probes to your codebase.'));
244
+ }
245
+
246
+ console.log('');
247
+
248
+ // 5. Check data service
249
+ const endpoint = config.dataEndpoint;
250
+ console.log(chalk.dim(` Checking data service at ${endpoint}...`));
251
+
252
+ // Also check for background process
253
+ const bgProcess = checkBackgroundProcess(cwd);
254
+
255
+ const health = await checkServiceHealth(endpoint);
256
+
257
+ if (health.healthy) {
258
+ const bgInfo = bgProcess.running ? ` (background, PID ${bgProcess.pid})` : '';
259
+ console.log(chalk.green(` [x] Data service is running${bgInfo} (${health.latencyMs}ms latency)`));
260
+
261
+ // Fetch stats if service is healthy
262
+ const stats = await fetchStats(endpoint);
263
+
264
+ if (stats) {
265
+ console.log(chalk.dim(` Stored probes: ${stats.probes.total}`));
266
+
267
+ if (Object.keys(stats.probes.byType).length > 0) {
268
+ for (const [type, count] of Object.entries(stats.probes.byType)) {
269
+ console.log(chalk.dim(` ${type}: ${count}`));
270
+ }
271
+ }
272
+
273
+ console.log(chalk.dim(` Graph: ${stats.graph.nodes} nodes, ${stats.graph.edges} edges`));
274
+ } else {
275
+ console.log(chalk.yellow(' [~] Could not fetch stats'));
276
+ }
277
+ } else {
278
+ if (bgProcess.running) {
279
+ console.log(chalk.yellow(` [~] Background process found (PID ${bgProcess.pid}) but health check failed`));
280
+ } else {
281
+ console.log(chalk.yellow(' [ ] Data service is not reachable'));
282
+ }
283
+
284
+ if (endpoint.includes('localhost') || endpoint.includes('127.0.0.1')) {
285
+ console.log(chalk.dim(' Run "utopia serve" to start the local data service.'));
286
+ } else {
287
+ console.log(chalk.dim(` Check that your service is running at ${endpoint}`));
288
+ }
289
+ }
290
+
291
+ // Summary
292
+ console.log('');
293
+ const allGood = hasConfig
294
+ && envCheck.missing.length === 0
295
+ && runtime.installed
296
+ && fileCount > 0
297
+ && health.healthy;
298
+
299
+ if (allGood) {
300
+ console.log(chalk.bold.green(' Everything looks good! Utopia is fully operational.\n'));
301
+ } else {
302
+ console.log(chalk.bold.yellow(' Some components need attention. See details above.\n'));
303
+ }
304
+ });