@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,505 @@
1
+ import { Command } from 'commander';
2
+ import inquirer from 'inquirer';
3
+ import chalk from 'chalk';
4
+ import crypto from 'node:crypto';
5
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
6
+ import { execSync } from 'node:child_process';
7
+ import { readFile } from 'node:fs/promises';
8
+ import { resolve, dirname } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { saveConfig, configExists } from '../utils/config.js';
11
+ import type { UtopiaConfig, SupportedFramework, AgentType } from '../utils/config.js';
12
+
13
+ function detectLanguages(dir: string): string[] {
14
+ const languages: Set<string> = new Set();
15
+ const checks: Array<{ glob: string[]; lang: string }> = [
16
+ { glob: ['tsconfig.json'], lang: 'typescript' },
17
+ { glob: ['package.json'], lang: 'javascript' },
18
+ { glob: ['requirements.txt', 'pyproject.toml', 'setup.py', 'Pipfile'], lang: 'python' },
19
+ ];
20
+
21
+ for (const check of checks) {
22
+ for (const file of check.glob) {
23
+ if (existsSync(resolve(dir, file))) {
24
+ languages.add(check.lang);
25
+ }
26
+ }
27
+ }
28
+
29
+ // Scan for common file extensions in the top-level src directory
30
+ const srcDir = resolve(dir, 'src');
31
+ if (existsSync(srcDir)) {
32
+ // Quick existence check for common extension patterns
33
+ const extChecks: Array<{ ext: string; lang: string }> = [
34
+ { ext: '.ts', lang: 'typescript' },
35
+ { ext: '.tsx', lang: 'typescript' },
36
+ { ext: '.js', lang: 'javascript' },
37
+ { ext: '.jsx', lang: 'javascript' },
38
+ { ext: '.py', lang: 'python' },
39
+ ];
40
+ for (const check of extChecks) {
41
+ // If we already detected the language, skip filesystem scan
42
+ if (!languages.has(check.lang)) {
43
+ // We rely on config file detection above; extension scanning
44
+ // would require walking the tree, which we skip for speed.
45
+ }
46
+ }
47
+ }
48
+
49
+ if (languages.size === 0) {
50
+ languages.add('javascript');
51
+ }
52
+
53
+ return [...languages];
54
+ }
55
+
56
+ async function detectFramework(dir: string): Promise<string> {
57
+ // Check package.json for JS/TS frameworks
58
+ const packageJsonPath = resolve(dir, 'package.json');
59
+ if (existsSync(packageJsonPath)) {
60
+ try {
61
+ const raw = await readFile(packageJsonPath, 'utf-8');
62
+ const pkg = JSON.parse(raw);
63
+ const allDeps = {
64
+ ...(pkg.dependencies || {}),
65
+ ...(pkg.devDependencies || {}),
66
+ };
67
+
68
+ if (allDeps['next']) return 'nextjs';
69
+ if (allDeps['react'] && !allDeps['next']) return 'react';
70
+ } catch {
71
+ // Ignore parse errors
72
+ }
73
+ }
74
+
75
+ // Check Python config files
76
+ const pythonFiles = ['requirements.txt', 'pyproject.toml', 'Pipfile', 'setup.py'];
77
+ for (const file of pythonFiles) {
78
+ if (existsSync(resolve(dir, file))) {
79
+ return 'python';
80
+ }
81
+ }
82
+
83
+ return 'unsupported';
84
+ }
85
+
86
+ /**
87
+ * Write Utopia environment variables to the appropriate .env file.
88
+ * For Next.js projects, writes to .env.local with NEXT_PUBLIC_ prefixed variants.
89
+ * For other projects, writes to .env.
90
+ * Appends cleanly without duplicating existing variables.
91
+ */
92
+ function writeEnvVars(cwd: string, config: UtopiaConfig): { written: number; fileName: string } {
93
+ // Python projects read from .utopia/config.json directly — skip env vars
94
+ // Also clean up any leftover utopia env vars from previous runs
95
+ if (config.framework === 'python') {
96
+ for (const envFile of ['.env', '.env.local']) {
97
+ const envPath = resolve(cwd, envFile);
98
+ if (existsSync(envPath)) {
99
+ const content = readFileSync(envPath, 'utf-8');
100
+ if (content.includes('UTOPIA_ENDPOINT') || content.includes('UTOPIA_PROJECT_ID')) {
101
+ const cleaned = content
102
+ .split('\n')
103
+ .filter(line => !line.includes('UTOPIA_ENDPOINT') && !line.includes('UTOPIA_PROJECT_ID') && line.trim() !== '# Utopia probe configuration')
104
+ .join('\n')
105
+ .replace(/\n{3,}/g, '\n\n');
106
+ writeFileSync(envPath, cleaned);
107
+ }
108
+ }
109
+ }
110
+ return { written: 0, fileName: '(none — Python reads .utopia/config.json)' };
111
+ }
112
+
113
+ const isNextJs = config.framework === 'nextjs';
114
+ const envFileName = isNextJs ? '.env.local' : '.env';
115
+ const envFilePath = resolve(cwd, envFileName);
116
+
117
+ const envVars: string[] = [
118
+ `UTOPIA_ENDPOINT=${config.dataEndpoint}`,
119
+ `UTOPIA_PROJECT_ID=${config.projectId}`,
120
+ ];
121
+
122
+ // Next.js needs NEXT_PUBLIC_ variants for client components
123
+ if (isNextJs) {
124
+ envVars.push(
125
+ `NEXT_PUBLIC_UTOPIA_ENDPOINT=${config.dataEndpoint}`,
126
+ `NEXT_PUBLIC_UTOPIA_PROJECT_ID=${config.projectId}`,
127
+ );
128
+ }
129
+
130
+ let existingEnv = '';
131
+ try {
132
+ existingEnv = readFileSync(envFilePath, 'utf-8');
133
+ } catch {
134
+ // File doesn't exist yet
135
+ }
136
+
137
+ // Replace existing Utopia env vars with current values (handles re-init)
138
+ let updatedEnv = existingEnv;
139
+ let changed = 0;
140
+ for (const envVar of envVars) {
141
+ const key = envVar.split('=')[0];
142
+ const regex = new RegExp(`^${key}=.*$`, 'm');
143
+ if (regex.test(updatedEnv)) {
144
+ const before = updatedEnv;
145
+ updatedEnv = updatedEnv.replace(regex, envVar);
146
+ if (updatedEnv !== before) changed++;
147
+ }
148
+ }
149
+
150
+ // Add any vars that don't exist yet
151
+ const missing = envVars.filter(v => {
152
+ const key = v.split('=')[0];
153
+ return !updatedEnv.includes(key + '=');
154
+ });
155
+ if (missing.length > 0) {
156
+ const block = '\n# Utopia probe configuration\n' + missing.join('\n') + '\n';
157
+ updatedEnv = updatedEnv ? updatedEnv + block : block.trimStart();
158
+ }
159
+
160
+ if (updatedEnv !== existingEnv) {
161
+ writeFileSync(envFilePath, updatedEnv);
162
+ }
163
+
164
+ return { written: missing.length + changed, fileName: envFileName };
165
+ }
166
+
167
+ /**
168
+ * Set up the Utopia MCP server for the configured agent.
169
+ */
170
+ function setupMcpServer(cwd: string, config: UtopiaConfig): void {
171
+ let utopiaBin = 'utopia';
172
+ try {
173
+ utopiaBin = execSync('which utopia', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim() || 'utopia';
174
+ } catch { /* fall back to bare name */ }
175
+
176
+ if (config.agent === 'codex') {
177
+ try {
178
+ execSync('codex mcp remove utopia', { cwd, stdio: 'pipe' });
179
+ } catch { /* didn't exist */ }
180
+ execSync(
181
+ `codex mcp add utopia -- ${utopiaBin} mcp --endpoint ${config.dataEndpoint} --project-id ${config.projectId}`,
182
+ { cwd, stdio: 'pipe' }
183
+ );
184
+ } else {
185
+ try {
186
+ execSync('claude mcp remove utopia', { cwd, stdio: 'pipe' });
187
+ } catch { /* didn't exist */ }
188
+ execSync(
189
+ `claude mcp add utopia --scope project -- ${utopiaBin} mcp --endpoint ${config.dataEndpoint} --project-id ${config.projectId}`,
190
+ { cwd, stdio: 'pipe' }
191
+ );
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Create/update the agent instructions file (CLAUDE.md or AGENTS.md)
197
+ * with Utopia context so the agent knows about the probe system.
198
+ */
199
+ function setupAgentInstructions(cwd: string, config: UtopiaConfig): void {
200
+ const fileName = config.agent === 'codex' ? 'AGENTS.md' : 'CLAUDE.md';
201
+ const claudeMdPath = resolve(cwd, fileName);
202
+
203
+ const utopiaSection = `
204
+ ## Utopia — Production Probe System
205
+
206
+ This project has Utopia installed. It captures real-time production data via probes embedded in the code. You have MCP tools to query this data. **USE THEM.**
207
+
208
+ ### MANDATORY: Check Utopia FIRST
209
+
210
+ When the user asks you to investigate errors, fix bugs, build features, or modify code — your FIRST action must be to call the Utopia MCP tools. Do NOT run the app, read logs, or guess. The probe data already has what you need.
211
+
212
+ **When the user reports errors or bugs:**
213
+ 1. Call \`get_recent_errors\` — this has the exact errors, stack traces, and the input data that caused them
214
+ 2. Call \`get_production_context\` with a description of the issue
215
+ 3. THEN fix the code based on what the probes tell you
216
+
217
+ **When starting any task:**
218
+ 1. Call \`get_full_context\` — comprehensive overview of production state
219
+ 2. Call \`get_production_context\` with your task description
220
+ 3. THEN start working
221
+
222
+ **Before modifying any file:**
223
+ - Call \`get_production_context\` with the file/function name
224
+ - Call \`get_impact_analysis\` to see what depends on it
225
+
226
+ **Do NOT:**
227
+ - Run the dev server just to see errors (the probes already captured them)
228
+ - Guess at runtime behavior (the probes show you exactly what happens)
229
+ - Skip checking probes because it seems faster (it's not — you'll miss context)
230
+
231
+ ### MCP Tools
232
+
233
+ - \`get_recent_errors\` — Errors with stack traces and input data that caused them
234
+ - \`get_production_context\` — Context relevant to a specific task or file
235
+ - \`get_full_context\` — Complete production overview (use at task start)
236
+ - \`get_api_context\` — External API call patterns and latencies
237
+ - \`get_database_context\` — Database query patterns and data shapes
238
+ - \`get_infrastructure_context\` — Deployment environment details
239
+ - \`get_impact_analysis\` — What is affected by changing a file or function
240
+
241
+ ### Adding probes — ALWAYS use utopia reinstrument
242
+
243
+ **NEVER write probe code manually.** Always use the \`utopia reinstrument\` command to add new probes. This ensures probes are correctly formatted, tracked for clean removal, and follow all Utopia conventions.
244
+
245
+ \`\`\`bash
246
+ utopia reinstrument -p "description of what you need deeper insight into"
247
+ \`\`\`
248
+
249
+ Run this when:
250
+ - You're debugging an issue but the probes don't cover that code path
251
+ - You're about to refactor a module and want to understand its runtime behavior first
252
+ - The user asks about a feature that doesn't have probes yet
253
+ - You need to understand data shapes flowing through specific functions
254
+
255
+ After reinstrumenting, the user needs to restart their app and trigger the code paths. Then query the MCP tools again for the new data.
256
+
257
+ **Do NOT:**
258
+ - Write \`// utopia:probe\` blocks yourself
259
+ - Copy probe code from other files
260
+ - Import utopia-runtime and add report calls manually
261
+ These will not be tracked properly and may break \`utopia destruct\`.
262
+
263
+ ### Removing probes
264
+ \`\`\`bash
265
+ utopia destruct
266
+ \`\`\`
267
+ `.trim();
268
+
269
+ // Check if CLAUDE.md exists and already has utopia section
270
+ let existing = '';
271
+ try {
272
+ existing = readFileSync(claudeMdPath, 'utf-8');
273
+ } catch { /* doesn't exist */ }
274
+
275
+ if (existing.includes('## Utopia')) {
276
+ // Replace existing section
277
+ const regex = /## Utopia — Production Probe System[\s\S]*?(?=\n## |\n*$)/;
278
+ if (regex.test(existing)) {
279
+ const updated = existing.replace(regex, utopiaSection);
280
+ writeFileSync(claudeMdPath, updated);
281
+ }
282
+ } else if (existing) {
283
+ // Append to existing file
284
+ writeFileSync(claudeMdPath, existing.trimEnd() + '\n\n' + utopiaSection + '\n');
285
+ } else {
286
+ // Create new file
287
+ writeFileSync(claudeMdPath, utopiaSection + '\n');
288
+ }
289
+ }
290
+
291
+ export const initCommand = new Command('init')
292
+ .description('Initialize Utopia in your project')
293
+ .action(async () => {
294
+ console.log(chalk.bold.cyan(`
295
+ ██╗ ██╗████████╗ ██████╗ ██████╗ ██╗ █████╗
296
+ ██║ ██║╚══██╔══╝██╔═══██╗██╔══██╗██║██╔══██╗
297
+ ██║ ██║ ██║ ██║ ██║██████╔╝██║███████║
298
+ ██║ ██║ ██║ ██║ ██║██╔═══╝ ██║██╔══██║
299
+ ╚██████╔╝ ██║ ╚██████╔╝██║ ██║██║ ██║
300
+ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
301
+ `));
302
+ console.log(chalk.bold.white(' Debug AI Generated Code at Lightning Speed\n'));
303
+ console.log(chalk.dim(' Utopia gives your AI coding agent eyes into production.'));
304
+ console.log(chalk.dim(' It sees how your code actually runs — errors, data flow,'));
305
+ console.log(chalk.dim(' API calls, security gaps — so the agent writes better'));
306
+ console.log(chalk.dim(' code without you copying logs or explaining context.\n'));
307
+
308
+ const cwd = process.cwd();
309
+
310
+ if (configExists(cwd)) {
311
+ const { overwrite } = await inquirer.prompt<{ overwrite: boolean }>([
312
+ {
313
+ type: 'confirm',
314
+ name: 'overwrite',
315
+ message: 'A .utopia/config.json already exists. Overwrite?',
316
+ default: false,
317
+ },
318
+ ]);
319
+ if (!overwrite) {
320
+ console.log(chalk.yellow('\n Setup cancelled.\n'));
321
+ return;
322
+ }
323
+ }
324
+
325
+ // Auto-detect languages and framework
326
+ const detectedLanguages = detectLanguages(cwd);
327
+ const detectedFramework = await detectFramework(cwd) as SupportedFramework;
328
+
329
+ console.log(chalk.dim(` Detected languages: ${detectedLanguages.join(', ')}`));
330
+ console.log(chalk.dim(` Detected framework: ${detectedFramework}\n`));
331
+
332
+ if (detectedFramework === 'unsupported') {
333
+ console.log(chalk.red(' Unsupported framework. Utopia currently supports:'));
334
+ console.log(chalk.white(' - Next.js (TypeScript/JavaScript)'));
335
+ console.log(chalk.white(' - React (TypeScript/JavaScript)'));
336
+ console.log(chalk.white(' - Python (FastAPI, Flask, Django, etc.)\n'));
337
+ process.exit(1);
338
+ }
339
+
340
+ // Agent choice
341
+ const { agent } = await inquirer.prompt<{ agent: string }>([
342
+ {
343
+ type: 'list',
344
+ name: 'agent',
345
+ message: 'Which coding agent do you use?',
346
+ choices: [
347
+ { name: 'Claude Code', value: 'claude' },
348
+ { name: 'Codex (OpenAI)', value: 'codex' },
349
+ ],
350
+ },
351
+ ]);
352
+
353
+ // Cloud provider
354
+ const { cloudProvider } = await inquirer.prompt<{ cloudProvider: string }>([
355
+ {
356
+ type: 'list',
357
+ name: 'cloudProvider',
358
+ message: 'Where is your code deployed?',
359
+ choices: [
360
+ { name: 'AWS', value: 'aws' },
361
+ { name: 'GCP', value: 'gcp' },
362
+ { name: 'Vercel', value: 'vercel' },
363
+ { name: 'Azure', value: 'azure' },
364
+ { name: 'Other', value: 'other' },
365
+ ],
366
+ },
367
+ ]);
368
+
369
+ // Auto-derive service from provider
370
+ const serviceMap: Record<string, string> = {
371
+ aws: 'AWS',
372
+ gcp: 'GCP',
373
+ vercel: 'Vercel',
374
+ azure: 'Azure',
375
+ other: 'Other',
376
+ };
377
+ const service = serviceMap[cloudProvider] || cloudProvider;
378
+
379
+ // Step 2: Deployment method
380
+ const { deploymentMethod } = await inquirer.prompt<{ deploymentMethod: string }>([
381
+ {
382
+ type: 'list',
383
+ name: 'deploymentMethod',
384
+ message: 'How is your code deployed?',
385
+ choices: [
386
+ { name: 'Manual', value: 'manual' },
387
+ { name: 'GitHub Actions', value: 'github-actions' },
388
+ { name: 'Vercel Trigger', value: 'vercel-trigger' },
389
+ { name: 'Other', value: 'other' },
390
+ ],
391
+ },
392
+ ]);
393
+
394
+ // Step 4: Standalone repository
395
+ const { isStandalone } = await inquirer.prompt<{ isStandalone: boolean }>([
396
+ {
397
+ type: 'confirm',
398
+ name: 'isStandalone',
399
+ message: 'Is this repository standalone? (No = used by other repos)',
400
+ default: true,
401
+ },
402
+ ]);
403
+
404
+ // Data collection depth
405
+ const { dataMode } = await inquirer.prompt<{ dataMode: string }>([
406
+ {
407
+ type: 'list',
408
+ name: 'dataMode',
409
+ message: 'What level of data should probes capture?',
410
+ choices: [
411
+ { name: 'Schemas & shapes only (counts, types, field names — no actual user data)', value: 'schemas' },
412
+ { name: 'Full data context (real inputs, outputs, DB results — maximum visibility)', value: 'full' },
413
+ ],
414
+ },
415
+ ]);
416
+
417
+ // Probe goal
418
+ const { probeGoal } = await inquirer.prompt<{ probeGoal: string }>([
419
+ {
420
+ type: 'list',
421
+ name: 'probeGoal',
422
+ message: 'What are you looking to solve?',
423
+ choices: [
424
+ { name: 'Debugging — runtime behavior, errors, data flow, performance', value: 'debugging' },
425
+ { name: 'Security — SQL injection, auth flaws, insecure patterns, bad domains', value: 'security' },
426
+ { name: 'Both — full debugging + security analysis', value: 'both' },
427
+ ],
428
+ },
429
+ ]);
430
+
431
+ const dataEndpoint = 'http://localhost:7890';
432
+ const projectId = `proj_${crypto.randomBytes(8).toString('hex')}`;
433
+
434
+ const config: UtopiaConfig = {
435
+ version: '0.1.0',
436
+ projectId,
437
+ cloudProvider,
438
+ service: service.trim(),
439
+ deploymentMethod,
440
+ isStandalone,
441
+ dataEndpoint,
442
+ language: detectedLanguages,
443
+ framework: detectedFramework,
444
+ dataMode: dataMode as UtopiaConfig['dataMode'],
445
+ probeGoal: probeGoal as UtopiaConfig['probeGoal'],
446
+ agent: agent as AgentType,
447
+ };
448
+
449
+ await saveConfig(config, cwd);
450
+
451
+ // Write environment variables to the appropriate .env file
452
+ try {
453
+ const { written, fileName } = writeEnvVars(cwd, config);
454
+ if (written > 0) {
455
+ console.log(chalk.green(`\n Added ${written} environment variable(s) to ${fileName}`));
456
+ } else {
457
+ console.log(chalk.dim(`\n Environment variables already present in ${fileName}`));
458
+ }
459
+ } catch (err) {
460
+ console.log(chalk.yellow(`\n Could not write environment variables: ${(err as Error).message}`));
461
+ console.log(chalk.dim(' You can set them manually:'));
462
+ console.log(chalk.dim(` UTOPIA_ENDPOINT=${config.dataEndpoint}`));
463
+ console.log(chalk.dim(` UTOPIA_PROJECT_ID=${config.projectId}`));
464
+ }
465
+
466
+ // Set up MCP server for the chosen agent
467
+ const agentLabel = config.agent === 'codex' ? 'Codex' : 'Claude Code';
468
+ try {
469
+ setupMcpServer(cwd, config);
470
+ console.log(chalk.green(`\n MCP server configured for ${agentLabel}`));
471
+ } catch (err) {
472
+ console.log(chalk.yellow(`\n Could not configure MCP server: ${(err as Error).message}`));
473
+ }
474
+
475
+ try {
476
+ setupAgentInstructions(cwd, config);
477
+ const instrFile = config.agent === 'codex' ? 'AGENTS.md' : 'CLAUDE.md';
478
+ console.log(chalk.green(` ${instrFile} updated with Utopia instructions`));
479
+ } catch (err) {
480
+ console.log(chalk.yellow(` Could not update agent instructions: ${(err as Error).message}`));
481
+ }
482
+
483
+ // Print summary
484
+ console.log(chalk.bold.green('\n Utopia initialized successfully!\n'));
485
+ console.log(chalk.white(' Configuration saved to .utopia/config.json'));
486
+ console.log(chalk.dim(' (.utopia/ is gitignored by default)\n'));
487
+
488
+ console.log(chalk.bold(' Project Summary:'));
489
+ console.log(` Project ID: ${chalk.cyan(projectId)}`);
490
+ console.log(` Provider: ${chalk.cyan(cloudProvider)}`);
491
+ console.log(` Deploy via: ${chalk.cyan(deploymentMethod)}`);
492
+ console.log(` Languages: ${chalk.cyan(detectedLanguages.join(', '))}`);
493
+ console.log(` Framework: ${chalk.cyan(detectedFramework)}`);
494
+ console.log(` Agent: ${chalk.cyan(agent === 'codex' ? 'Codex (OpenAI)' : 'Claude Code')}`);
495
+ console.log(` Data mode: ${chalk.cyan(dataMode === 'full' ? 'Full data context' : 'Schemas & shapes only')}`);
496
+ console.log(` Probe goal: ${chalk.cyan(probeGoal === 'both' ? 'Debugging + Security' : probeGoal.charAt(0).toUpperCase() + probeGoal.slice(1))}`);
497
+
498
+ console.log(chalk.bold('\n Next Steps:\n'));
499
+ console.log(` 1. ${chalk.white('utopia instrument')} — Add probes to your codebase`);
500
+ console.log(` 2. ${chalk.white('utopia validate')} — Verify probes are valid`);
501
+ console.log(` 3. ${chalk.white('utopia serve -b')} — Start the data service`);
502
+ console.log(` 4. Run your app and browse around`);
503
+ console.log(` 5. ${chalk.white('utopia status')} — See probe data flowing`);
504
+ console.log('');
505
+ });