@datalyr/wizard 1.0.0 → 1.0.2

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.
package/dist/index.mjs CHANGED
@@ -228,6 +228,8 @@ function getInstallCommand(pm, packages, dev = false) {
228
228
  return dev ? `pnpm add -D ${pkgList}` : `pnpm add ${pkgList}`;
229
229
  case "bun":
230
230
  return dev ? `bun add -d ${pkgList}` : `bun add ${pkgList}`;
231
+ default:
232
+ return dev ? `npm install -D ${pkgList}` : `npm install ${pkgList}`;
231
233
  }
232
234
  }
233
235
 
@@ -453,29 +455,39 @@ export { datalyr };
453
455
 
454
456
  // src/generators/templates/react-native.ts
455
457
  function generateReactNativeFiles(options) {
456
- const { language, isExpo, apiKey } = options;
458
+ const { language, isExpo } = options;
457
459
  const ext = language === "typescript" ? "ts" : "js";
458
460
  return [
459
461
  {
460
462
  path: `src/utils/datalyr.${ext}`,
461
463
  action: "create",
462
464
  description: "Datalyr initialization",
463
- content: generateReactNativeInit(language, isExpo, apiKey)
465
+ content: generateReactNativeInit(language, isExpo)
464
466
  }
465
467
  ];
466
468
  }
467
- function generateReactNativeInit(language, isExpo, apiKey) {
469
+ function generateReactNativeInit(language, isExpo) {
468
470
  const importPath = isExpo ? "@datalyr/react-native/expo" : "@datalyr/react-native";
469
471
  if (language === "typescript") {
470
472
  return `import { Datalyr } from '${importPath}';
471
473
 
474
+ // IMPORTANT: Set DATALYR_API_KEY in your environment
475
+ // For Expo: Add to app.config.js extra field and use expo-constants
476
+ // For bare RN: Use react-native-config or babel-plugin-transform-inline-environment-variables
477
+ const DATALYR_API_KEY = process.env.DATALYR_API_KEY;
478
+
472
479
  let initialized = false;
473
480
 
474
481
  export async function initDatalyr(): Promise<void> {
475
482
  if (initialized) return;
476
483
 
484
+ if (!DATALYR_API_KEY) {
485
+ console.warn('Datalyr: API key not configured. Set DATALYR_API_KEY environment variable.');
486
+ return;
487
+ }
488
+
477
489
  await Datalyr.initialize({
478
- apiKey: '${apiKey}',
490
+ apiKey: DATALYR_API_KEY,
479
491
  enableAutoEvents: true,
480
492
  enableAttribution: true,
481
493
  debug: __DEV__,
@@ -500,13 +512,23 @@ export function identifyUser(userId: string, traits?: Record<string, unknown>):
500
512
  }
501
513
  return `import { Datalyr } from '${importPath}';
502
514
 
515
+ // IMPORTANT: Set DATALYR_API_KEY in your environment
516
+ // For Expo: Add to app.config.js extra field and use expo-constants
517
+ // For bare RN: Use react-native-config or babel-plugin-transform-inline-environment-variables
518
+ const DATALYR_API_KEY = process.env.DATALYR_API_KEY;
519
+
503
520
  let initialized = false;
504
521
 
505
522
  export async function initDatalyr() {
506
523
  if (initialized) return;
507
524
 
525
+ if (!DATALYR_API_KEY) {
526
+ console.warn('Datalyr: API key not configured. Set DATALYR_API_KEY environment variable.');
527
+ return;
528
+ }
529
+
508
530
  await Datalyr.initialize({
509
- apiKey: '${apiKey}',
531
+ apiKey: DATALYR_API_KEY,
510
532
  enableAutoEvents: true,
511
533
  enableAttribution: true,
512
534
  debug: __DEV__,
@@ -537,22 +559,22 @@ import path4 from "path";
537
559
  import ora from "ora";
538
560
  import chalk from "chalk";
539
561
  var currentSpinner = null;
540
- function startSpinner(text) {
562
+ function startSpinner(text2) {
541
563
  if (currentSpinner) {
542
564
  currentSpinner.stop();
543
565
  }
544
- currentSpinner = ora({ text, color: "cyan" }).start();
566
+ currentSpinner = ora({ text: text2, color: "cyan" }).start();
545
567
  return currentSpinner;
546
568
  }
547
- function succeedSpinner(text) {
569
+ function succeedSpinner(text2) {
548
570
  if (currentSpinner) {
549
- currentSpinner.succeed(text);
571
+ currentSpinner.succeed(text2);
550
572
  currentSpinner = null;
551
573
  }
552
574
  }
553
- function failSpinner(text) {
575
+ function failSpinner(text2) {
554
576
  if (currentSpinner) {
555
- currentSpinner.fail(text);
577
+ currentSpinner.fail(text2);
556
578
  currentSpinner = null;
557
579
  }
558
580
  }
@@ -652,7 +674,7 @@ async function updateEnvFile(cwd, envVars, options = {}) {
652
674
  if (options.dryRun) {
653
675
  return true;
654
676
  }
655
- const spinner = startSpinner(`Updating ${envFileName}...`);
677
+ const spinner2 = startSpinner(`Updating ${envFileName}...`);
656
678
  try {
657
679
  let existingContent = "";
658
680
  if (await fileExists(envPath)) {
@@ -715,9 +737,27 @@ function parseEnvFile(content) {
715
737
  }
716
738
  function getEnvVarsForFramework(framework, apiKey) {
717
739
  const vars = [];
740
+ const getPublicEnvVarKey = (fw) => {
741
+ switch (fw) {
742
+ case "react":
743
+ case "react-vite":
744
+ return "VITE_DATALYR_WORKSPACE_ID";
745
+ case "svelte":
746
+ case "sveltekit":
747
+ return "PUBLIC_DATALYR_WORKSPACE_ID";
748
+ case "vue":
749
+ case "nuxt":
750
+ return "NUXT_PUBLIC_DATALYR_WORKSPACE_ID";
751
+ case "remix":
752
+ case "astro":
753
+ case "nextjs":
754
+ default:
755
+ return "NEXT_PUBLIC_DATALYR_WORKSPACE_ID";
756
+ }
757
+ };
718
758
  if (["nextjs", "react", "react-vite", "svelte", "sveltekit", "vue", "nuxt", "remix", "astro"].includes(framework)) {
719
759
  vars.push({
720
- key: "NEXT_PUBLIC_DATALYR_WORKSPACE_ID",
760
+ key: getPublicEnvVarKey(framework),
721
761
  value: "",
722
762
  description: "Your Datalyr workspace ID (get from dashboard)",
723
763
  isPublic: true
@@ -753,7 +793,7 @@ function generateFilesForFramework(context, apiKey) {
753
793
  case "nextjs":
754
794
  return generateNextJSFiles({
755
795
  language,
756
- hasAppRouter: context.entryPoints.some((p) => p.includes("app/layout")),
796
+ hasAppRouter: context.entryPoints.some((p2) => p2.includes("app/layout")),
757
797
  apiKey
758
798
  });
759
799
  case "react":
@@ -766,8 +806,7 @@ function generateFilesForFramework(context, apiKey) {
766
806
  case "expo":
767
807
  return generateReactNativeFiles({
768
808
  language,
769
- isExpo: framework === "expo",
770
- apiKey
809
+ isExpo: framework === "expo"
771
810
  });
772
811
  case "sveltekit":
773
812
  return generateSvelteKitFiles(language, apiKey);
@@ -1017,7 +1056,7 @@ async function installPackages(cwd, packages, options = {}) {
1017
1056
  if (options.dryRun) return true;
1018
1057
  const pm = await detectPackageManager(cwd);
1019
1058
  const command = getInstallCommand(pm, packages, options.dev);
1020
- const spinner = startSpinner(`Installing ${packages.join(", ")}...`);
1059
+ const spinner2 = startSpinner(`Installing ${packages.join(", ")}...`);
1021
1060
  try {
1022
1061
  const [cmd, ...args] = command.split(" ");
1023
1062
  await execa(cmd, args, { cwd, stdio: "pipe" });
@@ -1113,6 +1152,2336 @@ var logger = {
1113
1152
  blank: () => console.log()
1114
1153
  };
1115
1154
 
1155
+ // src/agent/runner.ts
1156
+ import * as p from "@clack/prompts";
1157
+ import chalk4 from "chalk";
1158
+
1159
+ // src/agent/interface.ts
1160
+ var AGENT_SIGNALS = {
1161
+ STATUS: "[STATUS]",
1162
+ ERROR_MISSING_KEY: "[ERROR_MISSING_KEY]",
1163
+ ERROR_FAILED: "[ERROR_FAILED]",
1164
+ SUCCESS: "[SUCCESS]"
1165
+ };
1166
+ var ALLOWED_COMMANDS = [
1167
+ // Package managers
1168
+ "npm",
1169
+ "yarn",
1170
+ "pnpm",
1171
+ "bun",
1172
+ "npx",
1173
+ // Build tools
1174
+ "tsc",
1175
+ "node",
1176
+ // iOS
1177
+ "pod",
1178
+ "xcodebuild",
1179
+ // File operations (read-only)
1180
+ "cat",
1181
+ "ls",
1182
+ "find",
1183
+ "grep",
1184
+ "head",
1185
+ "tail",
1186
+ "wc",
1187
+ // Git (read-only)
1188
+ "git status",
1189
+ "git log",
1190
+ "git diff",
1191
+ "git branch"
1192
+ ];
1193
+ var BLOCKED_PATTERNS = [
1194
+ /;/,
1195
+ // Command chaining
1196
+ /`/,
1197
+ // Backticks
1198
+ /\$\(/,
1199
+ // Command substitution
1200
+ /\$\w/,
1201
+ // Variable expansion (could contain malicious code)
1202
+ /\|\s*sh/,
1203
+ // Piping to shell
1204
+ /\|\s*bash/,
1205
+ // Piping to bash
1206
+ /\|\s*zsh/,
1207
+ // Piping to zsh
1208
+ /\|\|/,
1209
+ // Or operator (allows fallback commands)
1210
+ /\s&\s/,
1211
+ // Background execution mid-command
1212
+ /\s&$/,
1213
+ // Background execution at end
1214
+ /rm\s+-rf/,
1215
+ // Dangerous rm
1216
+ /rm\s+-r/,
1217
+ // Recursive rm
1218
+ />\s*\//,
1219
+ // Overwriting system files
1220
+ /&&\s*rm/,
1221
+ // rm after &&
1222
+ /\|\s*rm/
1223
+ // rm after pipe
1224
+ ];
1225
+ function validateBashCommand(command) {
1226
+ for (const pattern of BLOCKED_PATTERNS) {
1227
+ if (pattern.test(command)) {
1228
+ return { allowed: false, reason: `Blocked pattern detected: ${pattern}` };
1229
+ }
1230
+ }
1231
+ const baseCommand = command.trim().split(/\s+/)[0];
1232
+ const isAllowed = ALLOWED_COMMANDS.some((allowed) => {
1233
+ if (allowed.includes(" ")) {
1234
+ return command.startsWith(allowed);
1235
+ }
1236
+ return baseCommand === allowed;
1237
+ });
1238
+ if (!isAllowed) {
1239
+ return { allowed: false, reason: `Command not in allowlist: ${baseCommand}` };
1240
+ }
1241
+ return { allowed: true };
1242
+ }
1243
+ function buildSystemPrompt() {
1244
+ return `<role>
1245
+ You are a senior developer specializing in SDK integrations. You have 10+ years of experience integrating analytics tools into React, Next.js, Svelte, React Native, and iOS projects. You are meticulous, always read code before modifying it, and preserve existing functionality.
1246
+ </role>
1247
+
1248
+ <task>
1249
+ Install and configure the Datalyr analytics SDK in the user's project. Complete this task by:
1250
+ 1. Detecting the framework and understanding the project structure
1251
+ 2. Installing the correct SDK packages
1252
+ 3. Creating initialization code in the appropriate entry point
1253
+ 4. Configuring environment variables
1254
+ </task>
1255
+
1256
+ <sdks>
1257
+ | SDK | Use Case | Install Command |
1258
+ |-----|----------|-----------------|
1259
+ | @datalyr/web | Browser apps (React, Vue, Svelte, Next.js client) | npm install @datalyr/web |
1260
+ | @datalyr/api | Server-side (Next.js API routes, Express, Node) | npm install @datalyr/api |
1261
+ | @datalyr/react-native | React Native & Expo mobile apps | npm install @datalyr/react-native |
1262
+ | DatalyrSDK | Native iOS Swift apps | Swift Package Manager |
1263
+ </sdks>
1264
+
1265
+ <rules>
1266
+ 1. ALWAYS read files before modifying them - never edit blind
1267
+ 2. PRESERVE existing code - only add Datalyr, never remove functionality
1268
+ 3. MATCH the project's code style (indentation, quotes, semicolons)
1269
+ 4. USE TypeScript if the project uses TypeScript
1270
+ 5. PLACE initialization in the correct entry point for the framework
1271
+ 6. UPDATE .env or .env.local with NEXT_PUBLIC_DATALYR_WORKSPACE_ID
1272
+ 7. CHECK for existing Datalyr setup first - don't duplicate
1273
+ </rules>
1274
+
1275
+ <workflow>
1276
+ Step 1: Read package.json to detect framework and package manager
1277
+ Step 2: List files to find entry points (app/layout.tsx, src/main.tsx, App.tsx, etc.)
1278
+ Step 3: Read entry point files to understand current structure
1279
+ Step 4: Install SDK packages using detected package manager
1280
+ Step 5: Create initialization file (lib/datalyr.ts or similar)
1281
+ Step 6: Update entry point to import and initialize Datalyr
1282
+ Step 7: Update or create .env.local with workspace ID placeholder
1283
+ Step 8: Call task_complete with summary of changes
1284
+ </workflow>
1285
+
1286
+ <examples>
1287
+ <example name="nextjs-app-router">
1288
+ For Next.js 13+ with App Router, create app/providers.tsx:
1289
+ \`\`\`tsx
1290
+ 'use client';
1291
+ import datalyr from '@datalyr/web';
1292
+ import { useEffect } from 'react';
1293
+
1294
+ export function DatalyrProvider({ children }: { children: React.ReactNode }) {
1295
+ useEffect(() => {
1296
+ datalyr.init({
1297
+ workspaceId: process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID!,
1298
+ debug: process.env.NODE_ENV === 'development',
1299
+ });
1300
+ }, []);
1301
+ return <>{children}</>;
1302
+ }
1303
+ \`\`\`
1304
+ Then wrap children in app/layout.tsx with <DatalyrProvider>.
1305
+ </example>
1306
+
1307
+ <example name="react-vite">
1308
+ For React + Vite, create src/lib/datalyr.ts:
1309
+ \`\`\`ts
1310
+ import datalyr from '@datalyr/web';
1311
+
1312
+ let initialized = false;
1313
+
1314
+ export function initDatalyr() {
1315
+ if (initialized) return;
1316
+ datalyr.init({
1317
+ workspaceId: import.meta.env.VITE_DATALYR_WORKSPACE_ID,
1318
+ debug: import.meta.env.DEV,
1319
+ });
1320
+ initialized = true;
1321
+ }
1322
+
1323
+ export { datalyr };
1324
+ \`\`\`
1325
+ Then call initDatalyr() at the top of src/main.tsx.
1326
+ </example>
1327
+
1328
+ <example name="react-native">
1329
+ For React Native, create src/utils/datalyr.ts:
1330
+ \`\`\`ts
1331
+ import { Datalyr } from '@datalyr/react-native';
1332
+
1333
+ export async function initDatalyr(apiKey: string) {
1334
+ await Datalyr.initialize({
1335
+ apiKey,
1336
+ enableAutoEvents: true,
1337
+ debug: __DEV__,
1338
+ });
1339
+ }
1340
+
1341
+ export { Datalyr };
1342
+ \`\`\`
1343
+ Then call initDatalyr() in App.tsx useEffect.
1344
+ </example>
1345
+ </examples>
1346
+
1347
+ <signals>
1348
+ When complete: ${AGENT_SIGNALS.SUCCESS}
1349
+ On error: ${AGENT_SIGNALS.ERROR_FAILED}
1350
+ Status updates: ${AGENT_SIGNALS.STATUS} <message>
1351
+ </signals>`;
1352
+ }
1353
+
1354
+ // src/agent/docs/index.ts
1355
+ function getFrameworkDocs(framework, apiKey) {
1356
+ switch (framework) {
1357
+ case "nextjs":
1358
+ return getNextjsDocs(apiKey);
1359
+ case "react":
1360
+ case "react-vite":
1361
+ return getReactDocs(apiKey);
1362
+ case "sveltekit":
1363
+ return getSvelteKitDocs(apiKey);
1364
+ case "svelte":
1365
+ return getSvelteDocs(apiKey);
1366
+ case "react-native":
1367
+ case "expo":
1368
+ return getReactNativeDocs(apiKey, framework === "expo");
1369
+ case "ios":
1370
+ return getIOSDocs(apiKey);
1371
+ case "node":
1372
+ return getNodeDocs(apiKey);
1373
+ default:
1374
+ return getGenericWebDocs(apiKey);
1375
+ }
1376
+ }
1377
+ function getNextjsDocs(apiKey) {
1378
+ return `
1379
+ # Datalyr Next.js Integration
1380
+
1381
+ ## Packages to Install
1382
+ - @datalyr/web (client-side tracking)
1383
+ - @datalyr/api (server-side tracking)
1384
+
1385
+ ## Environment Variables
1386
+ Add to .env.local:
1387
+ \`\`\`
1388
+ NEXT_PUBLIC_DATALYR_WORKSPACE_ID=your_workspace_id
1389
+ DATALYR_API_KEY=${apiKey}
1390
+ \`\`\`
1391
+
1392
+ ## App Router Setup (Next.js 13+)
1393
+
1394
+ ### 1. Create Provider Component
1395
+ Create \`app/providers.tsx\`:
1396
+ \`\`\`tsx
1397
+ 'use client';
1398
+
1399
+ import datalyr from '@datalyr/web';
1400
+ import { useEffect } from 'react';
1401
+
1402
+ export function DatalyrProvider({ children }: { children: React.ReactNode }) {
1403
+ useEffect(() => {
1404
+ datalyr.init({
1405
+ workspaceId: process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID!,
1406
+ debug: process.env.NODE_ENV === 'development',
1407
+ trackSPA: true,
1408
+ });
1409
+ }, []);
1410
+
1411
+ return <>{children}</>;
1412
+ }
1413
+ \`\`\`
1414
+
1415
+ ### 2. Wrap Layout
1416
+ Update \`app/layout.tsx\` to wrap children with the provider:
1417
+ \`\`\`tsx
1418
+ import { DatalyrProvider } from './providers';
1419
+
1420
+ export default function RootLayout({ children }) {
1421
+ return (
1422
+ <html>
1423
+ <body>
1424
+ <DatalyrProvider>
1425
+ {children}
1426
+ </DatalyrProvider>
1427
+ </body>
1428
+ </html>
1429
+ );
1430
+ }
1431
+ \`\`\`
1432
+
1433
+ ### 3. Server-Side Instance (Optional)
1434
+ Create \`lib/datalyr.ts\` for server-side tracking:
1435
+ \`\`\`ts
1436
+ import Datalyr from '@datalyr/api';
1437
+
1438
+ export const datalyr = new Datalyr(process.env.DATALYR_API_KEY!);
1439
+ \`\`\`
1440
+
1441
+ ## Pages Router Setup (Legacy)
1442
+
1443
+ ### 1. Create Hook
1444
+ Create \`lib/datalyr.ts\`:
1445
+ \`\`\`ts
1446
+ import datalyr from '@datalyr/web';
1447
+
1448
+ let initialized = false;
1449
+
1450
+ export function initDatalyr() {
1451
+ if (initialized || typeof window === 'undefined') return;
1452
+
1453
+ datalyr.init({
1454
+ workspaceId: process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID!,
1455
+ debug: process.env.NODE_ENV === 'development',
1456
+ trackSPA: true,
1457
+ });
1458
+
1459
+ initialized = true;
1460
+ }
1461
+
1462
+ export { datalyr };
1463
+ \`\`\`
1464
+
1465
+ ### 2. Initialize in _app.tsx
1466
+ \`\`\`tsx
1467
+ import { useEffect } from 'react';
1468
+ import { initDatalyr } from '../lib/datalyr';
1469
+
1470
+ function MyApp({ Component, pageProps }) {
1471
+ useEffect(() => {
1472
+ initDatalyr();
1473
+ }, []);
1474
+
1475
+ return <Component {...pageProps} />;
1476
+ }
1477
+ \`\`\`
1478
+ `;
1479
+ }
1480
+ function getReactDocs(apiKey) {
1481
+ return `
1482
+ # Datalyr React Integration
1483
+
1484
+ ## Package to Install
1485
+ - @datalyr/web
1486
+
1487
+ ## Environment Variables
1488
+ Add to .env (or .env.local for Vite):
1489
+ \`\`\`
1490
+ VITE_DATALYR_WORKSPACE_ID=your_workspace_id
1491
+ # Or for Create React App:
1492
+ REACT_APP_DATALYR_WORKSPACE_ID=your_workspace_id
1493
+ \`\`\`
1494
+
1495
+ ## Setup
1496
+
1497
+ ### 1. Create Initialization Module
1498
+ Create \`src/lib/datalyr.ts\`:
1499
+ \`\`\`ts
1500
+ import datalyr from '@datalyr/web';
1501
+
1502
+ let initialized = false;
1503
+
1504
+ export function initDatalyr() {
1505
+ if (initialized || typeof window === 'undefined') return;
1506
+
1507
+ datalyr.init({
1508
+ // Vite uses import.meta.env, CRA uses process.env
1509
+ workspaceId: import.meta.env?.VITE_DATALYR_WORKSPACE_ID ||
1510
+ process.env.REACT_APP_DATALYR_WORKSPACE_ID,
1511
+ debug: import.meta.env?.DEV || process.env.NODE_ENV === 'development',
1512
+ trackSPA: true,
1513
+ });
1514
+
1515
+ initialized = true;
1516
+ }
1517
+
1518
+ export { datalyr };
1519
+ \`\`\`
1520
+
1521
+ ### 2. Initialize in Entry Point
1522
+ Update \`src/main.tsx\` (Vite) or \`src/index.tsx\` (CRA):
1523
+ \`\`\`tsx
1524
+ import { initDatalyr } from './lib/datalyr';
1525
+
1526
+ // Initialize before rendering
1527
+ initDatalyr();
1528
+
1529
+ // ... rest of your app setup
1530
+ \`\`\`
1531
+
1532
+ ## Tracking Events
1533
+ \`\`\`ts
1534
+ import { datalyr } from './lib/datalyr';
1535
+
1536
+ // Track an event
1537
+ datalyr.track('button_clicked', { button_id: 'signup' });
1538
+
1539
+ // Identify a user
1540
+ datalyr.identify('user_123', { email: 'user@example.com' });
1541
+ \`\`\`
1542
+ `;
1543
+ }
1544
+ function getSvelteKitDocs(apiKey) {
1545
+ return `
1546
+ # Datalyr SvelteKit Integration
1547
+
1548
+ ## Packages to Install
1549
+ - @datalyr/web (client-side)
1550
+ - @datalyr/api (server-side)
1551
+
1552
+ ## Environment Variables
1553
+ Add to .env:
1554
+ \`\`\`
1555
+ PUBLIC_DATALYR_WORKSPACE_ID=your_workspace_id
1556
+ DATALYR_API_KEY=${apiKey}
1557
+ \`\`\`
1558
+
1559
+ ## Setup
1560
+
1561
+ ### 1. Client-Side Initialization
1562
+ Create \`src/lib/datalyr.ts\`:
1563
+ \`\`\`ts
1564
+ import datalyr from '@datalyr/web';
1565
+ import { browser } from '$app/environment';
1566
+
1567
+ let initialized = false;
1568
+
1569
+ export function initDatalyr() {
1570
+ if (initialized || !browser) return;
1571
+
1572
+ datalyr.init({
1573
+ workspaceId: import.meta.env.PUBLIC_DATALYR_WORKSPACE_ID,
1574
+ debug: import.meta.env.DEV,
1575
+ trackSPA: true,
1576
+ });
1577
+
1578
+ initialized = true;
1579
+ }
1580
+
1581
+ export { datalyr };
1582
+ \`\`\`
1583
+
1584
+ ### 2. Initialize in Layout
1585
+ Update \`src/routes/+layout.svelte\`:
1586
+ \`\`\`svelte
1587
+ <script>
1588
+ import { onMount } from 'svelte';
1589
+ import { initDatalyr } from '$lib/datalyr';
1590
+
1591
+ onMount(() => {
1592
+ initDatalyr();
1593
+ });
1594
+ </script>
1595
+
1596
+ <slot />
1597
+ \`\`\`
1598
+
1599
+ ### 3. Server-Side Instance
1600
+ Create \`src/lib/server/datalyr.ts\`:
1601
+ \`\`\`ts
1602
+ import Datalyr from '@datalyr/api';
1603
+ import { DATALYR_API_KEY } from '$env/static/private';
1604
+
1605
+ export const datalyr = new Datalyr(DATALYR_API_KEY);
1606
+ \`\`\`
1607
+ `;
1608
+ }
1609
+ function getSvelteDocs(apiKey) {
1610
+ return `
1611
+ # Datalyr Svelte Integration
1612
+
1613
+ ## Package to Install
1614
+ - @datalyr/web
1615
+
1616
+ ## Environment Variables
1617
+ Add to .env:
1618
+ \`\`\`
1619
+ VITE_DATALYR_WORKSPACE_ID=your_workspace_id
1620
+ \`\`\`
1621
+
1622
+ ## Setup
1623
+
1624
+ ### 1. Create Initialization Module
1625
+ Create \`src/lib/datalyr.ts\`:
1626
+ \`\`\`ts
1627
+ import datalyr from '@datalyr/web';
1628
+
1629
+ let initialized = false;
1630
+
1631
+ export function initDatalyr() {
1632
+ if (initialized || typeof window === 'undefined') return;
1633
+
1634
+ datalyr.init({
1635
+ workspaceId: import.meta.env.VITE_DATALYR_WORKSPACE_ID,
1636
+ debug: import.meta.env.DEV,
1637
+ trackSPA: true,
1638
+ });
1639
+
1640
+ initialized = true;
1641
+ }
1642
+
1643
+ export { datalyr };
1644
+ \`\`\`
1645
+
1646
+ ### 2. Initialize in App
1647
+ Update \`src/App.svelte\`:
1648
+ \`\`\`svelte
1649
+ <script>
1650
+ import { onMount } from 'svelte';
1651
+ import { initDatalyr } from './lib/datalyr';
1652
+
1653
+ onMount(() => {
1654
+ initDatalyr();
1655
+ });
1656
+ </script>
1657
+
1658
+ <!-- Your app content -->
1659
+ \`\`\`
1660
+ `;
1661
+ }
1662
+ function getReactNativeDocs(_apiKey, isExpo) {
1663
+ const importPath = isExpo ? "@datalyr/react-native/expo" : "@datalyr/react-native";
1664
+ const configImport = isExpo ? "import Constants from 'expo-constants';" : "";
1665
+ const configAccess = isExpo ? "Constants.expoConfig?.extra?.datalyrApiKey || ''" : "process.env.DATALYR_API_KEY || ''";
1666
+ return `
1667
+ # Datalyr React Native Integration${isExpo ? " (Expo)" : ""}
1668
+
1669
+ ## Package to Install
1670
+ - @datalyr/react-native
1671
+ ${isExpo ? "- expo-constants (for config access)" : ""}
1672
+
1673
+ ## Post-Install (iOS)
1674
+ After installing, run:
1675
+ \`\`\`bash
1676
+ cd ios && pod install
1677
+ \`\`\`
1678
+
1679
+ ## Environment Configuration
1680
+ ${isExpo ? `
1681
+ ### For Expo
1682
+ Add to \`app.config.js\` or \`app.json\`:
1683
+ \`\`\`js
1684
+ export default {
1685
+ expo: {
1686
+ extra: {
1687
+ datalyrApiKey: process.env.DATALYR_API_KEY,
1688
+ datalyrWorkspaceId: process.env.DATALYR_WORKSPACE_ID,
1689
+ },
1690
+ },
1691
+ };
1692
+ \`\`\`
1693
+
1694
+ Create \`.env\` file:
1695
+ \`\`\`
1696
+ DATALYR_API_KEY=your_api_key
1697
+ DATALYR_WORKSPACE_ID=your_workspace_id
1698
+ \`\`\`
1699
+ ` : `
1700
+ ### For React Native CLI
1701
+ Create \`.env\` file and use react-native-config:
1702
+ \`\`\`
1703
+ DATALYR_API_KEY=your_api_key
1704
+ DATALYR_WORKSPACE_ID=your_workspace_id
1705
+ \`\`\`
1706
+ `}
1707
+ **Security Note**: Never commit API keys to source control. Use environment variables or secrets management.
1708
+
1709
+ ## Setup
1710
+
1711
+ ### 1. Create Initialization Module
1712
+ Create \`src/utils/datalyr.ts\`:
1713
+ \`\`\`ts
1714
+ import { Datalyr } from '${importPath}';
1715
+ ${configImport}
1716
+
1717
+ let initialized = false;
1718
+
1719
+ export async function initDatalyr() {
1720
+ if (initialized) return;
1721
+
1722
+ const apiKey = ${configAccess};
1723
+
1724
+ if (!apiKey) {
1725
+ console.warn('[Datalyr] API key not configured');
1726
+ return;
1727
+ }
1728
+
1729
+ await Datalyr.initialize({
1730
+ apiKey,
1731
+ enableAutoEvents: true,
1732
+ enableAttribution: true,
1733
+ debug: __DEV__,
1734
+ });
1735
+
1736
+ initialized = true;
1737
+ }
1738
+
1739
+ export { Datalyr };
1740
+ \`\`\`
1741
+
1742
+ ### 2. Initialize in App
1743
+ Update \`App.tsx\`:
1744
+ \`\`\`tsx
1745
+ import { useEffect } from 'react';
1746
+ import { initDatalyr } from './src/utils/datalyr';
1747
+
1748
+ export default function App() {
1749
+ useEffect(() => {
1750
+ initDatalyr();
1751
+ }, []);
1752
+
1753
+ return (
1754
+ // Your app content
1755
+ );
1756
+ }
1757
+ \`\`\`
1758
+
1759
+ ## Tracking Events
1760
+ \`\`\`ts
1761
+ import { Datalyr } from './src/utils/datalyr';
1762
+
1763
+ // Track an event
1764
+ Datalyr.track('purchase_completed', { amount: 99.99 });
1765
+
1766
+ // Identify a user
1767
+ Datalyr.identify('user_123', { email: 'user@example.com' });
1768
+ \`\`\`
1769
+ `;
1770
+ }
1771
+ function getNodeDocs(apiKey) {
1772
+ return `
1773
+ # Datalyr Node.js Integration
1774
+
1775
+ ## Package to Install
1776
+ - @datalyr/api
1777
+
1778
+ ## Environment Variables
1779
+ Add to .env:
1780
+ \`\`\`
1781
+ DATALYR_API_KEY=${apiKey}
1782
+ \`\`\`
1783
+
1784
+ ## Setup
1785
+
1786
+ ### 1. Create Datalyr Instance
1787
+ Create \`src/lib/datalyr.ts\`:
1788
+ \`\`\`ts
1789
+ import Datalyr from '@datalyr/api';
1790
+
1791
+ const apiKey = process.env.DATALYR_API_KEY;
1792
+ if (!apiKey) {
1793
+ throw new Error('DATALYR_API_KEY is required');
1794
+ }
1795
+
1796
+ export const datalyr = new Datalyr(apiKey);
1797
+ \`\`\`
1798
+
1799
+ ### 2. Track Events
1800
+ \`\`\`ts
1801
+ import { datalyr } from './lib/datalyr';
1802
+
1803
+ // Track server-side events
1804
+ await datalyr.track({
1805
+ event: 'order_completed',
1806
+ userId: 'user_123',
1807
+ properties: {
1808
+ orderId: 'order_456',
1809
+ amount: 99.99,
1810
+ },
1811
+ });
1812
+
1813
+ // Identify a user
1814
+ await datalyr.identify({
1815
+ userId: 'user_123',
1816
+ traits: {
1817
+ email: 'user@example.com',
1818
+ plan: 'premium',
1819
+ },
1820
+ });
1821
+ \`\`\`
1822
+ `;
1823
+ }
1824
+ function getIOSDocs(_apiKey) {
1825
+ return `
1826
+ # Datalyr iOS (Swift) Integration
1827
+
1828
+ ## Package to Install
1829
+ Add via Swift Package Manager:
1830
+ - https://github.com/datalyr/datalyr-ios-sdk
1831
+
1832
+ ## Setup
1833
+
1834
+ ### 1. Add Keys to Info.plist
1835
+ Add both keys to your Info.plist (or use xcconfig for different environments):
1836
+ \`\`\`xml
1837
+ <key>DATALYR_WORKSPACE_ID</key>
1838
+ <string>YOUR_WORKSPACE_ID</string>
1839
+ <key>DATALYR_API_KEY</key>
1840
+ <string>YOUR_API_KEY</string>
1841
+ \`\`\`
1842
+
1843
+ **Security Note**: For production apps, consider using xcconfig files or build-time environment variables to inject these values, keeping them out of source control.
1844
+
1845
+ ### 2. Add Configuration File
1846
+ Create \`DatalyrConfig.swift\`:
1847
+ \`\`\`swift
1848
+ import DatalyrSDK
1849
+ import Foundation
1850
+
1851
+ struct DatalyrConfig {
1852
+ static func initialize() {
1853
+ guard let workspaceId = Bundle.main.object(forInfoDictionaryKey: "DATALYR_WORKSPACE_ID") as? String,
1854
+ let apiKey = Bundle.main.object(forInfoDictionaryKey: "DATALYR_API_KEY") as? String else {
1855
+ #if DEBUG
1856
+ fatalError("DATALYR_WORKSPACE_ID or DATALYR_API_KEY not found in Info.plist")
1857
+ #else
1858
+ print("[Datalyr] Warning: SDK not configured - missing Info.plist keys")
1859
+ return
1860
+ #endif
1861
+ }
1862
+
1863
+ Datalyr.shared.configure(
1864
+ apiKey: apiKey,
1865
+ workspaceId: workspaceId,
1866
+ options: DatalyrOptions(
1867
+ debug: false,
1868
+ enableAutoEvents: true,
1869
+ enableAttribution: true
1870
+ )
1871
+ )
1872
+ }
1873
+ }
1874
+ \`\`\`
1875
+
1876
+ ### 3. Initialize in App
1877
+ For SwiftUI (\`@main App\`):
1878
+ \`\`\`swift
1879
+ @main
1880
+ struct MyApp: App {
1881
+ init() {
1882
+ DatalyrConfig.initialize()
1883
+ }
1884
+
1885
+ var body: some Scene {
1886
+ WindowGroup {
1887
+ ContentView()
1888
+ }
1889
+ }
1890
+ }
1891
+ \`\`\`
1892
+
1893
+ For UIKit (AppDelegate):
1894
+ \`\`\`swift
1895
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions...) -> Bool {
1896
+ DatalyrConfig.initialize()
1897
+ return true
1898
+ }
1899
+ \`\`\`
1900
+
1901
+ ## Tracking Events
1902
+ \`\`\`swift
1903
+ import DatalyrSDK
1904
+
1905
+ // Track an event
1906
+ Datalyr.shared.track("purchase_completed", properties: [
1907
+ "amount": 99.99,
1908
+ "currency": "USD"
1909
+ ])
1910
+
1911
+ // Identify a user
1912
+ Datalyr.shared.identify("user_123", traits: [
1913
+ "email": "user@example.com",
1914
+ "plan": "premium"
1915
+ ])
1916
+ \`\`\`
1917
+ `;
1918
+ }
1919
+ function getGenericWebDocs(apiKey) {
1920
+ return `
1921
+ # Datalyr Web Integration
1922
+
1923
+ ## Package to Install
1924
+ - @datalyr/web
1925
+
1926
+ ## Setup
1927
+
1928
+ ### Via NPM
1929
+ \`\`\`ts
1930
+ import datalyr from '@datalyr/web';
1931
+
1932
+ datalyr.init({
1933
+ workspaceId: 'YOUR_WORKSPACE_ID',
1934
+ debug: true,
1935
+ });
1936
+
1937
+ // Track events
1938
+ datalyr.track('page_viewed', { page: '/home' });
1939
+ \`\`\`
1940
+
1941
+ ### Via Script Tag
1942
+ \`\`\`html
1943
+ <script src="https://track.datalyr.com/dl.js"
1944
+ data-workspace-id="YOUR_WORKSPACE_ID">
1945
+ </script>
1946
+ \`\`\`
1947
+ `;
1948
+ }
1949
+
1950
+ // src/agent/configs/types.ts
1951
+ var NEXTJS_CONFIG = {
1952
+ id: "nextjs",
1953
+ name: "Next.js",
1954
+ detectPackage: "next",
1955
+ sdks: ["@datalyr/web", "@datalyr/api"],
1956
+ envVars: [
1957
+ {
1958
+ key: "NEXT_PUBLIC_DATALYR_WORKSPACE_ID",
1959
+ description: "Your Datalyr workspace ID",
1960
+ isPublic: true
1961
+ },
1962
+ {
1963
+ key: "DATALYR_API_KEY",
1964
+ description: "Server-side API key",
1965
+ isPublic: false
1966
+ }
1967
+ ],
1968
+ estimatedTime: 3,
1969
+ docsUrl: "https://docs.datalyr.com/sdks/nextjs",
1970
+ postInstallSteps: [
1971
+ "Add your workspace ID to .env.local",
1972
+ "Wrap your layout with DatalyrProvider",
1973
+ "Start tracking events with datalyr.track()"
1974
+ ],
1975
+ routerDetection: {
1976
+ appRouter: ["app/layout.tsx", "app/layout.jsx", "app/layout.ts", "app/layout.js"],
1977
+ pagesRouter: ["pages/_app.tsx", "pages/_app.jsx", "pages/_app.ts", "pages/_app.js"]
1978
+ }
1979
+ };
1980
+ var REACT_CONFIG = {
1981
+ id: "react",
1982
+ name: "React",
1983
+ detectPackage: "react",
1984
+ sdks: ["@datalyr/web"],
1985
+ envVars: [
1986
+ {
1987
+ key: "VITE_DATALYR_WORKSPACE_ID",
1988
+ description: "Your Datalyr workspace ID",
1989
+ isPublic: true
1990
+ }
1991
+ ],
1992
+ estimatedTime: 2,
1993
+ docsUrl: "https://docs.datalyr.com/sdks/react",
1994
+ postInstallSteps: [
1995
+ "Add your workspace ID to .env",
1996
+ "Call initDatalyr() in your main file"
1997
+ ]
1998
+ };
1999
+ var REACT_NATIVE_CONFIG = {
2000
+ id: "react-native",
2001
+ name: "React Native",
2002
+ detectPackage: "react-native",
2003
+ sdks: ["@datalyr/react-native"],
2004
+ envVars: [],
2005
+ estimatedTime: 5,
2006
+ docsUrl: "https://docs.datalyr.com/sdks/react-native",
2007
+ postInstallSteps: [
2008
+ "Run: cd ios && pod install",
2009
+ "Call initDatalyr() in App.tsx",
2010
+ "For attribution, configure Meta/TikTok app IDs"
2011
+ ]
2012
+ };
2013
+ var EXPO_CONFIG = {
2014
+ id: "expo",
2015
+ name: "Expo",
2016
+ detectPackage: "expo",
2017
+ sdks: ["@datalyr/react-native"],
2018
+ envVars: [],
2019
+ estimatedTime: 3,
2020
+ docsUrl: "https://docs.datalyr.com/sdks/expo",
2021
+ postInstallSteps: [
2022
+ "Call initDatalyr() in App.tsx",
2023
+ "For attribution, configure Meta/TikTok app IDs in app.json"
2024
+ ]
2025
+ };
2026
+ var SVELTEKIT_CONFIG = {
2027
+ id: "sveltekit",
2028
+ name: "SvelteKit",
2029
+ detectPackage: "@sveltejs/kit",
2030
+ sdks: ["@datalyr/web", "@datalyr/api"],
2031
+ envVars: [
2032
+ {
2033
+ key: "PUBLIC_DATALYR_WORKSPACE_ID",
2034
+ description: "Your Datalyr workspace ID",
2035
+ isPublic: true
2036
+ },
2037
+ {
2038
+ key: "DATALYR_API_KEY",
2039
+ description: "Server-side API key",
2040
+ isPublic: false
2041
+ }
2042
+ ],
2043
+ estimatedTime: 3,
2044
+ docsUrl: "https://docs.datalyr.com/sdks/sveltekit",
2045
+ postInstallSteps: [
2046
+ "Add your workspace ID to .env",
2047
+ "Call initDatalyr() in +layout.svelte"
2048
+ ]
2049
+ };
2050
+ var NODE_CONFIG = {
2051
+ id: "node",
2052
+ name: "Node.js",
2053
+ detectPackage: "express",
2054
+ sdks: ["@datalyr/api"],
2055
+ envVars: [
2056
+ {
2057
+ key: "DATALYR_API_KEY",
2058
+ description: "Your Datalyr API key",
2059
+ isPublic: false
2060
+ }
2061
+ ],
2062
+ estimatedTime: 2,
2063
+ docsUrl: "https://docs.datalyr.com/sdks/node",
2064
+ postInstallSteps: [
2065
+ "Add your API key to .env",
2066
+ "Track events with datalyr.track()"
2067
+ ]
2068
+ };
2069
+ var VUE_CONFIG = {
2070
+ id: "vue",
2071
+ name: "Vue.js",
2072
+ detectPackage: "vue",
2073
+ sdks: ["@datalyr/web"],
2074
+ envVars: [
2075
+ {
2076
+ key: "VITE_DATALYR_WORKSPACE_ID",
2077
+ description: "Your Datalyr workspace ID",
2078
+ isPublic: true
2079
+ }
2080
+ ],
2081
+ estimatedTime: 2,
2082
+ docsUrl: "https://docs.datalyr.com/sdks/vue",
2083
+ postInstallSteps: [
2084
+ "Add your workspace ID to .env",
2085
+ "Call datalyr.init() in main.ts",
2086
+ "Track events with datalyr.track()"
2087
+ ]
2088
+ };
2089
+ var NUXT_CONFIG = {
2090
+ id: "nuxt",
2091
+ name: "Nuxt",
2092
+ detectPackage: "nuxt",
2093
+ sdks: ["@datalyr/web", "@datalyr/api"],
2094
+ envVars: [
2095
+ {
2096
+ key: "NUXT_PUBLIC_DATALYR_WORKSPACE_ID",
2097
+ description: "Your Datalyr workspace ID",
2098
+ isPublic: true
2099
+ },
2100
+ {
2101
+ key: "DATALYR_API_KEY",
2102
+ description: "Server-side API key",
2103
+ isPublic: false
2104
+ }
2105
+ ],
2106
+ estimatedTime: 3,
2107
+ docsUrl: "https://docs.datalyr.com/sdks/nuxt",
2108
+ postInstallSteps: [
2109
+ "Add your workspace ID to .env",
2110
+ "Create a Nuxt plugin for initialization",
2111
+ "Track events with datalyr.track()"
2112
+ ]
2113
+ };
2114
+ var REMIX_CONFIG = {
2115
+ id: "remix",
2116
+ name: "Remix",
2117
+ detectPackage: "@remix-run/react",
2118
+ sdks: ["@datalyr/web", "@datalyr/api"],
2119
+ envVars: [
2120
+ {
2121
+ key: "DATALYR_WORKSPACE_ID",
2122
+ description: "Your Datalyr workspace ID",
2123
+ isPublic: true
2124
+ },
2125
+ {
2126
+ key: "DATALYR_API_KEY",
2127
+ description: "Server-side API key",
2128
+ isPublic: false
2129
+ }
2130
+ ],
2131
+ estimatedTime: 3,
2132
+ docsUrl: "https://docs.datalyr.com/sdks/remix",
2133
+ postInstallSteps: [
2134
+ "Add your workspace ID to .env",
2135
+ "Initialize in root.tsx",
2136
+ "Track events with datalyr.track()"
2137
+ ]
2138
+ };
2139
+ var ASTRO_CONFIG = {
2140
+ id: "astro",
2141
+ name: "Astro",
2142
+ detectPackage: "astro",
2143
+ sdks: ["@datalyr/web"],
2144
+ envVars: [
2145
+ {
2146
+ key: "PUBLIC_DATALYR_WORKSPACE_ID",
2147
+ description: "Your Datalyr workspace ID",
2148
+ isPublic: true
2149
+ }
2150
+ ],
2151
+ estimatedTime: 2,
2152
+ docsUrl: "https://docs.datalyr.com/sdks/astro",
2153
+ postInstallSteps: [
2154
+ "Add your workspace ID to .env",
2155
+ "Add script to Layout component",
2156
+ "Track events with datalyr.track()"
2157
+ ]
2158
+ };
2159
+ var IOS_CONFIG = {
2160
+ id: "ios",
2161
+ name: "iOS (Swift)",
2162
+ detectPackage: "",
2163
+ // Detected by Package.swift or .xcodeproj
2164
+ sdks: ["DatalyrSDK"],
2165
+ envVars: [
2166
+ {
2167
+ key: "DATALYR_WORKSPACE_ID",
2168
+ description: "Your Datalyr workspace ID (in Info.plist)",
2169
+ isPublic: false
2170
+ },
2171
+ {
2172
+ key: "DATALYR_API_KEY",
2173
+ description: "Your Datalyr API key (in Info.plist)",
2174
+ isPublic: false
2175
+ }
2176
+ ],
2177
+ estimatedTime: 5,
2178
+ docsUrl: "https://docs.datalyr.com/sdks/ios",
2179
+ postInstallSteps: [
2180
+ "Add DatalyrSDK via Swift Package Manager",
2181
+ "Add keys to Info.plist",
2182
+ "Call DatalyrConfig.initialize() in App init"
2183
+ ]
2184
+ };
2185
+ var SVELTE_CONFIG = {
2186
+ id: "svelte",
2187
+ name: "Svelte",
2188
+ detectPackage: "svelte",
2189
+ sdks: ["@datalyr/web"],
2190
+ envVars: [
2191
+ {
2192
+ key: "VITE_DATALYR_WORKSPACE_ID",
2193
+ description: "Your Datalyr workspace ID",
2194
+ isPublic: true
2195
+ }
2196
+ ],
2197
+ estimatedTime: 2,
2198
+ docsUrl: "https://docs.datalyr.com/sdks/svelte",
2199
+ postInstallSteps: [
2200
+ "Add your workspace ID to .env",
2201
+ "Call initDatalyr() in App.svelte"
2202
+ ]
2203
+ };
2204
+ var FRAMEWORK_CONFIGS = {
2205
+ nextjs: NEXTJS_CONFIG,
2206
+ react: REACT_CONFIG,
2207
+ "react-vite": REACT_CONFIG,
2208
+ svelte: SVELTE_CONFIG,
2209
+ sveltekit: SVELTEKIT_CONFIG,
2210
+ vue: VUE_CONFIG,
2211
+ nuxt: NUXT_CONFIG,
2212
+ remix: REMIX_CONFIG,
2213
+ astro: ASTRO_CONFIG,
2214
+ "react-native": REACT_NATIVE_CONFIG,
2215
+ expo: EXPO_CONFIG,
2216
+ ios: IOS_CONFIG,
2217
+ node: NODE_CONFIG,
2218
+ unknown: void 0
2219
+ };
2220
+ function getSdksForFramework(framework) {
2221
+ const config = FRAMEWORK_CONFIGS[framework];
2222
+ if (!config) {
2223
+ return ["@datalyr/web"];
2224
+ }
2225
+ return config.sdks;
2226
+ }
2227
+
2228
+ // src/agent/events/suggestions.ts
2229
+ var BUSINESS_TYPES = [
2230
+ {
2231
+ id: "saas",
2232
+ label: "SaaS / Web App",
2233
+ description: "Subscription or freemium product",
2234
+ hint: "Trials, signups, features, upgrades"
2235
+ },
2236
+ {
2237
+ id: "mobile_app",
2238
+ label: "Mobile App",
2239
+ description: "Consumer or B2B mobile app",
2240
+ hint: "Installs, in-app events, attribution"
2241
+ },
2242
+ {
2243
+ id: "lead_gen",
2244
+ label: "Lead Gen / Marketing",
2245
+ description: "Capture leads and conversions",
2246
+ hint: "Funnels, forms, demos, signups"
2247
+ },
2248
+ {
2249
+ id: "b2b",
2250
+ label: "B2B Product",
2251
+ description: "Business software",
2252
+ hint: "Demos, trials, team invites, usage"
2253
+ },
2254
+ {
2255
+ id: "agency",
2256
+ label: "Agency / Client Work",
2257
+ description: "Building for clients",
2258
+ hint: "Multi-site, white-label tracking"
2259
+ }
2260
+ ];
2261
+ var EVENT_SUGGESTIONS = {
2262
+ saas: [
2263
+ {
2264
+ name: "signed_up",
2265
+ description: "User created an account",
2266
+ properties: ["method", "referrer"],
2267
+ priority: "high"
2268
+ },
2269
+ {
2270
+ name: "trial_started",
2271
+ description: "User started a free trial",
2272
+ properties: ["plan"],
2273
+ priority: "high"
2274
+ },
2275
+ {
2276
+ name: "subscription_started",
2277
+ description: "User converted to paid",
2278
+ properties: ["plan", "value", "currency"],
2279
+ priority: "high"
2280
+ },
2281
+ {
2282
+ name: "onboarding_completed",
2283
+ description: "User finished onboarding",
2284
+ properties: ["steps_completed"],
2285
+ priority: "medium"
2286
+ }
2287
+ ],
2288
+ mobile_app: [
2289
+ {
2290
+ name: "signed_up",
2291
+ description: "User created an account",
2292
+ properties: ["method", "referrer"],
2293
+ priority: "high"
2294
+ },
2295
+ {
2296
+ name: "onboarding_started",
2297
+ description: "User started onboarding flow",
2298
+ properties: ["source"],
2299
+ priority: "high"
2300
+ },
2301
+ {
2302
+ name: "onboarding_completed",
2303
+ description: "User finished onboarding",
2304
+ properties: ["steps_completed"],
2305
+ priority: "high"
2306
+ },
2307
+ {
2308
+ name: "paywall_viewed",
2309
+ description: "User saw the paywall/pricing",
2310
+ properties: ["source", "paywall_id"],
2311
+ priority: "high"
2312
+ },
2313
+ {
2314
+ name: "trial_started",
2315
+ description: "User started a free trial",
2316
+ properties: ["plan"],
2317
+ priority: "high"
2318
+ },
2319
+ {
2320
+ name: "subscription_started",
2321
+ description: "User converted to paid subscription",
2322
+ properties: ["plan", "value", "currency"],
2323
+ priority: "high"
2324
+ },
2325
+ {
2326
+ name: "purchase",
2327
+ description: "User made in-app purchase",
2328
+ properties: ["product_id", "value", "currency"],
2329
+ priority: "medium"
2330
+ }
2331
+ ],
2332
+ lead_gen: [
2333
+ {
2334
+ name: "lead",
2335
+ description: "User submitted their info",
2336
+ properties: ["form_name", "source"],
2337
+ priority: "high"
2338
+ },
2339
+ {
2340
+ name: "signed_up",
2341
+ description: "User signed up / joined waitlist",
2342
+ properties: ["method", "referrer"],
2343
+ priority: "high"
2344
+ },
2345
+ {
2346
+ name: "demo_requested",
2347
+ description: "User requested a demo",
2348
+ properties: ["product"],
2349
+ priority: "high"
2350
+ },
2351
+ {
2352
+ name: "cta_clicked",
2353
+ description: "User clicked a CTA",
2354
+ properties: ["button_name", "page"],
2355
+ priority: "medium"
2356
+ }
2357
+ ],
2358
+ b2b: [
2359
+ {
2360
+ name: "signed_up",
2361
+ description: "User created an account",
2362
+ properties: ["method", "company_size"],
2363
+ priority: "high"
2364
+ },
2365
+ {
2366
+ name: "demo_requested",
2367
+ description: "User requested a demo",
2368
+ properties: ["product", "company_size"],
2369
+ priority: "high"
2370
+ },
2371
+ {
2372
+ name: "trial_started",
2373
+ description: "User started a trial",
2374
+ properties: ["plan"],
2375
+ priority: "high"
2376
+ },
2377
+ {
2378
+ name: "lead",
2379
+ description: "Lead captured",
2380
+ properties: ["source", "company_size"],
2381
+ priority: "high"
2382
+ }
2383
+ ],
2384
+ agency: [
2385
+ {
2386
+ name: "lead",
2387
+ description: "Lead captured",
2388
+ properties: ["form_name", "client_id"],
2389
+ priority: "high"
2390
+ },
2391
+ {
2392
+ name: "conversion",
2393
+ description: "Conversion event",
2394
+ properties: ["conversion_type", "value", "client_id"],
2395
+ priority: "high"
2396
+ },
2397
+ {
2398
+ name: "form_submitted",
2399
+ description: "User submitted a form",
2400
+ properties: ["form_name", "client_id"],
2401
+ priority: "high"
2402
+ },
2403
+ {
2404
+ name: "cta_clicked",
2405
+ description: "User clicked a CTA",
2406
+ properties: ["button_name", "client_id"],
2407
+ priority: "medium"
2408
+ }
2409
+ ]
2410
+ };
2411
+ function getEventSuggestions(businessType) {
2412
+ return EVENT_SUGGESTIONS[businessType] || EVENT_SUGGESTIONS.saas;
2413
+ }
2414
+ function formatEventDescription(event) {
2415
+ const propsDisplay = event.properties.length > 0 ? ` (${event.properties.join(", ")})` : "";
2416
+ return `${event.description}${propsDisplay}`;
2417
+ }
2418
+ function buildEventSelectOptions(events, includeAllOption = true) {
2419
+ const options = [];
2420
+ if (includeAllOption) {
2421
+ const highPriorityCount = events.filter((e) => e.priority === "high").length;
2422
+ options.push({
2423
+ value: "__all_recommended__",
2424
+ label: "All recommended events",
2425
+ hint: `Select all ${highPriorityCount} high-priority events`
2426
+ });
2427
+ }
2428
+ for (const event of events) {
2429
+ const priorityBadge = event.priority === "high" ? "[recommended] " : "";
2430
+ options.push({
2431
+ value: event.name,
2432
+ label: event.name,
2433
+ hint: `${priorityBadge}${formatEventDescription(event)}`
2434
+ });
2435
+ }
2436
+ return options;
2437
+ }
2438
+ function resolveSelectedEvents(selectedValues, allEvents) {
2439
+ if (selectedValues.includes("__all_recommended__")) {
2440
+ return allEvents.filter((e) => e.priority === "high");
2441
+ }
2442
+ return allEvents.filter((e) => selectedValues.includes(e.name));
2443
+ }
2444
+
2445
+ // src/agent/platform/config.ts
2446
+ var PLATFORM_TYPES = [
2447
+ {
2448
+ id: "web",
2449
+ label: "Web Only",
2450
+ description: "Website or web application",
2451
+ hint: "React, Next.js, Svelte, etc."
2452
+ },
2453
+ {
2454
+ id: "mobile",
2455
+ label: "Mobile Only",
2456
+ description: "iOS or Android app",
2457
+ hint: "React Native, Expo, Swift"
2458
+ },
2459
+ {
2460
+ id: "both",
2461
+ label: "Web + Mobile",
2462
+ description: "Both web and mobile apps",
2463
+ hint: "Full cross-platform attribution"
2464
+ }
2465
+ ];
2466
+ var AD_PLATFORMS = [
2467
+ {
2468
+ id: "meta",
2469
+ label: "Meta Ads (Facebook/Instagram)",
2470
+ description: "Track conversions from Meta campaigns",
2471
+ requiresServerSide: true,
2472
+ configKeys: ["META_PIXEL_ID", "META_ACCESS_TOKEN", "META_APP_ID"]
2473
+ },
2474
+ {
2475
+ id: "google",
2476
+ label: "Google Ads",
2477
+ description: "Track conversions from Google campaigns",
2478
+ requiresServerSide: true,
2479
+ configKeys: ["GOOGLE_ADS_CUSTOMER_ID", "GOOGLE_ADS_CONVERSION_ID"]
2480
+ },
2481
+ {
2482
+ id: "tiktok",
2483
+ label: "TikTok Ads",
2484
+ description: "Track conversions from TikTok campaigns",
2485
+ requiresServerSide: true,
2486
+ configKeys: ["TIKTOK_PIXEL_ID", "TIKTOK_ACCESS_TOKEN", "TIKTOK_APP_ID"]
2487
+ },
2488
+ {
2489
+ id: "apple_search_ads",
2490
+ label: "Apple Search Ads",
2491
+ description: "Track iOS app install attribution",
2492
+ requiresServerSide: false,
2493
+ configKeys: []
2494
+ },
2495
+ {
2496
+ id: "none",
2497
+ label: "No ad platforms",
2498
+ description: "Skip ad platform setup",
2499
+ requiresServerSide: false,
2500
+ configKeys: []
2501
+ }
2502
+ ];
2503
+ function getSdksForPlatform(platformType, adPlatforms) {
2504
+ const sdks = [];
2505
+ if (platformType === "web" || platformType === "both") {
2506
+ sdks.push("@datalyr/web");
2507
+ }
2508
+ if (platformType === "mobile" || platformType === "both") {
2509
+ sdks.push("@datalyr/react-native");
2510
+ }
2511
+ const needsServerSide = adPlatforms.some(
2512
+ (p2) => AD_PLATFORMS.find((ap) => ap.id === p2)?.requiresServerSide
2513
+ );
2514
+ if (needsServerSide) {
2515
+ sdks.push("@datalyr/api");
2516
+ }
2517
+ return sdks;
2518
+ }
2519
+ function getAttributionEvents(config) {
2520
+ const events = [];
2521
+ if (config.platformType === "mobile" || config.platformType === "both") {
2522
+ events.push({
2523
+ name: "app_install",
2524
+ description: "User installed the app (auto-tracked)",
2525
+ properties: ["attribution_source", "campaign_id", "ad_group_id"],
2526
+ serverSide: false
2527
+ });
2528
+ events.push({
2529
+ name: "app_open",
2530
+ description: "User opened the app",
2531
+ properties: ["source", "deep_link_url", "is_first_open"],
2532
+ serverSide: false
2533
+ });
2534
+ }
2535
+ if (config.enableDeepLinking) {
2536
+ events.push({
2537
+ name: "deep_link_opened",
2538
+ description: "User opened a deep link",
2539
+ properties: ["url", "source", "campaign"],
2540
+ serverSide: false
2541
+ });
2542
+ events.push({
2543
+ name: "deferred_deep_link",
2544
+ description: "User installed via deferred deep link",
2545
+ properties: ["original_url", "install_time", "open_time"],
2546
+ serverSide: false
2547
+ });
2548
+ }
2549
+ if (config.enableServerSideConversions) {
2550
+ if (config.adPlatforms.includes("meta")) {
2551
+ events.push({
2552
+ name: "purchase",
2553
+ description: "Purchase event for Meta CAPI",
2554
+ properties: ["value", "currency", "email", "phone", "fbc", "fbp"],
2555
+ serverSide: true
2556
+ });
2557
+ events.push({
2558
+ name: "lead",
2559
+ description: "Lead event for Meta CAPI",
2560
+ properties: ["email", "phone", "fbc", "fbp"],
2561
+ serverSide: true
2562
+ });
2563
+ }
2564
+ if (config.adPlatforms.includes("tiktok")) {
2565
+ events.push({
2566
+ name: "complete_payment",
2567
+ description: "Purchase event for TikTok Events API",
2568
+ properties: ["value", "currency", "email", "phone", "ttclid"],
2569
+ serverSide: true
2570
+ });
2571
+ }
2572
+ if (config.adPlatforms.includes("google")) {
2573
+ events.push({
2574
+ name: "conversion",
2575
+ description: "Conversion event for Google Ads",
2576
+ properties: ["value", "currency", "email", "phone", "gclid"],
2577
+ serverSide: true
2578
+ });
2579
+ }
2580
+ }
2581
+ return events;
2582
+ }
2583
+ function getPlatformPostInstallSteps(config) {
2584
+ const steps = [];
2585
+ if (config.platformType === "mobile" || config.platformType === "both") {
2586
+ steps.push("Run `cd ios && pod install` to install native dependencies");
2587
+ if (config.enableDeepLinking) {
2588
+ steps.push("Configure URL schemes in Xcode/Android manifest for deep linking");
2589
+ steps.push("Set up Associated Domains (iOS) or App Links (Android)");
2590
+ }
2591
+ }
2592
+ for (const platformId of config.adPlatforms) {
2593
+ const platform = AD_PLATFORMS.find((p2) => p2.id === platformId);
2594
+ if (!platform || platformId === "none") continue;
2595
+ steps.push(`Add ${platform.label} credentials to your environment variables`);
2596
+ if (platform.requiresServerSide) {
2597
+ steps.push(`Set up server-side endpoint for ${platform.label} conversions`);
2598
+ }
2599
+ if (config.platformType !== "web") {
2600
+ if (platformId === "meta") {
2601
+ steps.push("Add Meta App ID to Info.plist (iOS) and AndroidManifest.xml");
2602
+ }
2603
+ if (platformId === "tiktok") {
2604
+ steps.push("Add TikTok App ID to Info.plist (iOS) and AndroidManifest.xml");
2605
+ }
2606
+ if (platformId === "apple_search_ads") {
2607
+ steps.push("Enable AdServices.framework in Xcode");
2608
+ }
2609
+ }
2610
+ }
2611
+ return steps;
2612
+ }
2613
+
2614
+ // src/generators/ai-context.ts
2615
+ function inferPropertyType(propName) {
2616
+ const lowerName = propName.toLowerCase();
2617
+ if (["value", "price", "amount", "total", "quantity", "count", "steps_completed"].some((n) => lowerName.includes(n))) {
2618
+ return "number";
2619
+ }
2620
+ if (["is_", "has_", "enable", "disable"].some((n) => lowerName.startsWith(n) || lowerName.includes(n))) {
2621
+ return "boolean";
2622
+ }
2623
+ if (lowerName === "currency") {
2624
+ return 'string; // ISO 4217 code (e.g., "USD", "EUR")';
2625
+ }
2626
+ if (lowerName === "email") {
2627
+ return "string; // User email for attribution matching";
2628
+ }
2629
+ if (lowerName === "phone") {
2630
+ return "string; // User phone for attribution matching";
2631
+ }
2632
+ if (lowerName.endsWith("_id") || lowerName === "id") {
2633
+ return "string";
2634
+ }
2635
+ return "string";
2636
+ }
2637
+ function generateAIContextDoc(params) {
2638
+ const {
2639
+ workspaceName,
2640
+ workspaceId,
2641
+ framework,
2642
+ platformType,
2643
+ adPlatforms,
2644
+ businessType,
2645
+ selectedEvents,
2646
+ sdks,
2647
+ enableServerSideConversions,
2648
+ enableContainer = true
2649
+ } = params;
2650
+ const eventsList = selectedEvents.map((e) => {
2651
+ const propsWithTypes = e.properties.map((p2) => {
2652
+ const type = inferPropertyType(p2);
2653
+ return ` ${p2}: ${type};`;
2654
+ }).join("\n");
2655
+ return `### \`${e.name}\`
2656
+ ${e.description}
2657
+
2658
+ \`\`\`typescript
2659
+ datalyr.track('${e.name}', {
2660
+ ${propsWithTypes}
2661
+ });
2662
+ \`\`\``;
2663
+ }).join("\n\n");
2664
+ const trackingExamples = selectedEvents.slice(0, 3).map((e) => {
2665
+ const props = e.properties.slice(0, 2).map((p2) => `${p2}: '...'`).join(", ");
2666
+ return `datalyr.track('${e.name}', { ${props} });`;
2667
+ }).join("\n");
2668
+ const serverSideExample = enableServerSideConversions ? `
2669
+ ## Server-Side Tracking (CAPI)
2670
+
2671
+ For conversion events that need server-side tracking:
2672
+
2673
+ \`\`\`typescript
2674
+ // In your API route or server action
2675
+ import { datalyr } from '@/lib/datalyr.server';
2676
+
2677
+ await datalyr.track({
2678
+ event: 'purchase',
2679
+ userId: user.id,
2680
+ properties: {
2681
+ value: 99.00,
2682
+ currency: 'USD',
2683
+ email: user.email, // Required for ad platform matching
2684
+ phone: user.phone, // Optional, improves match rate
2685
+ },
2686
+ });
2687
+ \`\`\`
2688
+ ` : "";
2689
+ const adPlatformsSection = adPlatforms.length > 0 ? `
2690
+ ## Attribution Setup
2691
+
2692
+ Ad platforms configured: ${adPlatforms.join(", ")}
2693
+
2694
+ For proper attribution matching, include user data in conversion events:
2695
+ - \`email\`: User's email (hashed automatically)
2696
+ - \`phone\`: User's phone number (hashed automatically)
2697
+ ${adPlatforms.includes("meta") ? "- `fbc`, `fbp`: Meta click/browser IDs (from cookies)" : ""}
2698
+ ${adPlatforms.includes("google") ? "- `gclid`: Google click ID (from URL params)" : ""}
2699
+ ${adPlatforms.includes("tiktok") ? "- `ttclid`: TikTok click ID (from URL params)" : ""}
2700
+ ` : "";
2701
+ const containerSection = enableContainer ? `
2702
+ ## Container Scripts
2703
+
2704
+ Container scripts are **enabled**. Third-party pixels (Meta Pixel, Google Tag, TikTok Pixel)
2705
+ are managed through the Datalyr dashboard, not in code.
2706
+
2707
+ Configure pixels at: https://app.datalyr.com/dashboard/${workspaceId}/settings/pixels
2708
+
2709
+ Benefits:
2710
+ - Add/remove pixels without code changes
2711
+ - Events tracked with \`datalyr.track()\` auto-fire to all configured pixels
2712
+ - Server-side fallback for ad blockers
2713
+ ` : "";
2714
+ return `# Datalyr Analytics Setup
2715
+
2716
+ This file documents the Datalyr analytics configuration for AI coding assistants.
2717
+
2718
+ ## Project Info
2719
+
2720
+ - **Workspace**: ${workspaceName}
2721
+ - **Workspace ID**: \`${workspaceId}\`
2722
+ - **Framework**: ${framework}
2723
+ - **Platform**: ${platformType}
2724
+ - **Business Type**: ${businessType}
2725
+ - **SDKs**: ${sdks.join(", ")}
2726
+
2727
+ ## Quick Start
2728
+
2729
+ ${framework === "ios" ? `\`\`\`swift
2730
+ import DatalyrSDK
2731
+
2732
+ // Track an event
2733
+ Datalyr.shared.track("event_name", properties: ["key": "value"])
2734
+ \`\`\`` : `\`\`\`typescript
2735
+ import datalyr from '@datalyr/web';
2736
+
2737
+ // Track an event
2738
+ ${trackingExamples}
2739
+ \`\`\``}
2740
+
2741
+ ## Events to Track
2742
+
2743
+ ${eventsList}
2744
+
2745
+ ## Usage Patterns
2746
+
2747
+ ### Identify Users
2748
+
2749
+ \`\`\`typescript
2750
+ // After user signs in
2751
+ datalyr.identify(user.id, {
2752
+ email: user.email,
2753
+ name: user.name,
2754
+ plan: user.plan,
2755
+ });
2756
+ \`\`\`
2757
+
2758
+ ### Track Events
2759
+
2760
+ \`\`\`typescript
2761
+ // Track any event with properties
2762
+ datalyr.track('event_name', {
2763
+ property: 'value',
2764
+ value: 100,
2765
+ });
2766
+ \`\`\`
2767
+ ${serverSideExample}${adPlatformsSection}${containerSection}
2768
+ ## Dashboard
2769
+
2770
+ View your analytics at:
2771
+ https://app.datalyr.com/dashboard/${workspaceId}/events
2772
+
2773
+ ## Documentation
2774
+
2775
+ - SDK Docs: https://docs.datalyr.com/sdks/${framework}
2776
+ - API Reference: https://docs.datalyr.com/api
2777
+ `;
2778
+ }
2779
+
2780
+ // src/agent/runner.ts
2781
+ import { writeFile as writeFile2, readFile as readFile2, mkdir } from "fs/promises";
2782
+ import { join, dirname, resolve } from "path";
2783
+ import { existsSync } from "fs";
2784
+ import { exec } from "child_process";
2785
+ import { promisify } from "util";
2786
+ import { glob as glob2 } from "glob";
2787
+ var execAsync = promisify(exec);
2788
+ var LLM_GATEWAY_URL = process.env.DATALYR_LLM_GATEWAY || "https://wizard.datalyr.com";
2789
+ async function runAgentWizard(_config, options = {}) {
2790
+ const cwd = options.cwd || process.cwd();
2791
+ p.intro(chalk4.cyan("Datalyr AI Wizard"));
2792
+ const useAI = await p.confirm({
2793
+ message: "This wizard uses AI to analyze your project and install Datalyr. Continue?",
2794
+ initialValue: true
2795
+ });
2796
+ if (p.isCancel(useAI) || !useAI) {
2797
+ p.cancel("Wizard cancelled");
2798
+ return { success: false, error: "User cancelled" };
2799
+ }
2800
+ let apiKey = options.apiKey;
2801
+ let workspace = null;
2802
+ let keyAttempts = 0;
2803
+ const maxAttempts = 3;
2804
+ while (!workspace && keyAttempts < maxAttempts) {
2805
+ keyAttempts++;
2806
+ if (!apiKey) {
2807
+ if (keyAttempts === 1) {
2808
+ p.note(
2809
+ `To get your API key:
2810
+ 1. Go to ${chalk4.cyan("https://app.datalyr.com/settings/api")}
2811
+ 2. Create a new API key (or copy existing)
2812
+ 3. Paste it below`,
2813
+ "API Key Required"
2814
+ );
2815
+ }
2816
+ const keyInput = await p.text({
2817
+ message: "Enter your Datalyr API key (starts with dk_):",
2818
+ placeholder: "dk_live_...",
2819
+ validate: (value) => {
2820
+ if (!value) return "API key is required";
2821
+ if (!value.startsWith("dk_")) return "API key must start with dk_";
2822
+ if (value.length < 20) return "API key seems too short";
2823
+ return void 0;
2824
+ }
2825
+ });
2826
+ if (p.isCancel(keyInput)) {
2827
+ p.cancel("Wizard cancelled");
2828
+ return { success: false, error: "User cancelled" };
2829
+ }
2830
+ apiKey = keyInput;
2831
+ }
2832
+ const validateSpinner = p.spinner();
2833
+ validateSpinner.start("Validating API key...");
2834
+ try {
2835
+ const defaultWorkspace = await validateApiKey(apiKey);
2836
+ const allWorkspaces = await fetchWorkspaces(apiKey);
2837
+ if (allWorkspaces.length > 1) {
2838
+ validateSpinner.stop("API key validated");
2839
+ const workspaceChoice = await p.select({
2840
+ message: "Select a workspace to configure:",
2841
+ options: allWorkspaces.map((w) => ({
2842
+ value: w.id,
2843
+ label: w.name,
2844
+ hint: w.domain || void 0
2845
+ }))
2846
+ });
2847
+ if (p.isCancel(workspaceChoice)) {
2848
+ p.cancel("Wizard cancelled");
2849
+ return { success: false, error: "User cancelled" };
2850
+ }
2851
+ const selectedWorkspace = allWorkspaces.find((w) => w.id === workspaceChoice);
2852
+ if (selectedWorkspace) {
2853
+ workspace = {
2854
+ id: selectedWorkspace.id,
2855
+ name: selectedWorkspace.name,
2856
+ timezone: null,
2857
+ domain: selectedWorkspace.domain
2858
+ };
2859
+ } else {
2860
+ workspace = defaultWorkspace;
2861
+ }
2862
+ p.log.info(`Selected workspace: ${chalk4.cyan(workspace.name)}`);
2863
+ } else {
2864
+ workspace = defaultWorkspace;
2865
+ validateSpinner.stop(`Workspace: ${chalk4.cyan(workspace.name)}`);
2866
+ }
2867
+ } catch (error) {
2868
+ validateSpinner.stop(chalk4.red("Invalid API key"));
2869
+ const errorMessage = error instanceof Error ? error.message : "Failed to validate API key";
2870
+ p.log.error(errorMessage);
2871
+ apiKey = void 0;
2872
+ if (keyAttempts < maxAttempts) {
2873
+ const action = await p.select({
2874
+ message: "What would you like to do?",
2875
+ options: [
2876
+ { value: "retry", label: "Try a different API key" },
2877
+ { value: "signup", label: "Create a free account", hint: "Opens browser" },
2878
+ { value: "exit", label: "Exit wizard" }
2879
+ ]
2880
+ });
2881
+ if (p.isCancel(action) || action === "exit") {
2882
+ p.cancel("Wizard cancelled");
2883
+ return { success: false, error: "User cancelled" };
2884
+ }
2885
+ if (action === "signup") {
2886
+ const signupUrl = "https://app.datalyr.com/signup?ref=wizard";
2887
+ p.log.info(`Opening ${chalk4.cyan(signupUrl)} in your browser...`);
2888
+ try {
2889
+ const { exec: exec2 } = await import("child_process");
2890
+ const { promisify: promisify2 } = await import("util");
2891
+ const execAsync2 = promisify2(exec2);
2892
+ const platform = process.platform;
2893
+ const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
2894
+ await execAsync2(`${cmd} "${signupUrl}"`);
2895
+ p.log.success("Browser opened! Create your account and come back with your API key.");
2896
+ await p.text({
2897
+ message: "Press Enter when you have your API key ready..."
2898
+ });
2899
+ } catch {
2900
+ p.log.warn(`Please visit: ${chalk4.cyan(signupUrl)}`);
2901
+ }
2902
+ }
2903
+ }
2904
+ }
2905
+ }
2906
+ if (!workspace || !apiKey) {
2907
+ p.log.error(`Failed to validate API key after ${maxAttempts} attempts.`);
2908
+ p.log.info(`Get help at ${chalk4.cyan("https://docs.datalyr.com/getting-started")}`);
2909
+ return { success: false, error: "API key validation failed" };
2910
+ }
2911
+ const validatedApiKey = apiKey;
2912
+ const detectSpinner = p.spinner();
2913
+ detectSpinner.start("Analyzing your project...");
2914
+ let detection = await detectFramework(cwd);
2915
+ let framework = detection.framework;
2916
+ if (framework === "unknown" && !options.framework) {
2917
+ detectSpinner.stop("Could not auto-detect framework");
2918
+ const frameworkChoice = await p.select({
2919
+ message: "Select your framework:",
2920
+ options: [
2921
+ { value: "nextjs", label: "Next.js" },
2922
+ { value: "react", label: "React" },
2923
+ { value: "react-vite", label: "React (Vite)" },
2924
+ { value: "svelte", label: "Svelte" },
2925
+ { value: "sveltekit", label: "SvelteKit" },
2926
+ { value: "react-native", label: "React Native" },
2927
+ { value: "expo", label: "Expo" },
2928
+ { value: "ios", label: "iOS (Swift)" },
2929
+ { value: "node", label: "Node.js" }
2930
+ ]
2931
+ });
2932
+ if (p.isCancel(frameworkChoice)) {
2933
+ p.cancel("Wizard cancelled");
2934
+ return { success: false, error: "User cancelled" };
2935
+ }
2936
+ framework = frameworkChoice;
2937
+ } else if (options.framework) {
2938
+ framework = options.framework;
2939
+ }
2940
+ detectSpinner.stop(`Detected: ${chalk4.cyan(getFrameworkDisplayName(framework))}`);
2941
+ if (detection.framework !== framework) {
2942
+ detection = { ...detection, framework, sdks: getSdksForFramework(framework) };
2943
+ }
2944
+ const isMobileFramework = ["react-native", "expo", "ios"].includes(framework);
2945
+ const isWebFramework = ["nextjs", "react", "react-vite", "svelte", "sveltekit"].includes(framework);
2946
+ let platformType = "web";
2947
+ if (isMobileFramework) {
2948
+ platformType = "mobile";
2949
+ } else if (!isMobileFramework && !isWebFramework) {
2950
+ const platformChoice = await p.select({
2951
+ message: "What platforms are you targeting?",
2952
+ options: PLATFORM_TYPES.map((type) => ({
2953
+ value: type.id,
2954
+ label: type.label,
2955
+ hint: type.hint
2956
+ }))
2957
+ });
2958
+ if (p.isCancel(platformChoice)) {
2959
+ p.cancel("Wizard cancelled");
2960
+ return { success: false, error: "User cancelled" };
2961
+ }
2962
+ platformType = platformChoice;
2963
+ }
2964
+ const runningAds = await p.confirm({
2965
+ message: "Are you running paid ads (Meta, Google, TikTok)?",
2966
+ initialValue: false
2967
+ });
2968
+ let adPlatforms = [];
2969
+ let platformConfig = {
2970
+ platformType,
2971
+ adPlatforms: [],
2972
+ enableDeepLinking: false,
2973
+ enableServerSideConversions: false
2974
+ };
2975
+ if (!p.isCancel(runningAds) && runningAds) {
2976
+ const adPlatformChoices = await p.multiselect({
2977
+ message: "Select your ad platforms:",
2978
+ options: AD_PLATFORMS.filter((p2) => p2.id !== "none").map((platform) => ({
2979
+ value: platform.id,
2980
+ label: platform.label,
2981
+ hint: platform.description
2982
+ })),
2983
+ required: false
2984
+ });
2985
+ if (!p.isCancel(adPlatformChoices)) {
2986
+ adPlatforms = adPlatformChoices;
2987
+ if (adPlatforms.length > 0) {
2988
+ const serverSide = await p.confirm({
2989
+ message: "Enable server-side conversion tracking (CAPI)?",
2990
+ initialValue: true
2991
+ });
2992
+ platformConfig.enableServerSideConversions = !p.isCancel(serverSide) && serverSide;
2993
+ }
2994
+ }
2995
+ }
2996
+ if (platformType === "mobile" || platformType === "both") {
2997
+ const deepLinking = await p.confirm({
2998
+ message: "Set up deep linking / deferred deep links?",
2999
+ initialValue: adPlatforms.length > 0
3000
+ });
3001
+ platformConfig.enableDeepLinking = !p.isCancel(deepLinking) && deepLinking;
3002
+ }
3003
+ platformConfig.adPlatforms = adPlatforms;
3004
+ let enableContainer = options.enableContainer;
3005
+ if (enableContainer === void 0 && platformType !== "mobile") {
3006
+ const containerChoice = await p.confirm({
3007
+ message: "Manage third-party pixels through Datalyr dashboard?",
3008
+ initialValue: adPlatforms.length > 0
3009
+ // Default yes if using ad platforms
3010
+ });
3011
+ if (!p.isCancel(containerChoice)) {
3012
+ enableContainer = containerChoice;
3013
+ if (containerChoice) {
3014
+ p.note(
3015
+ `Container scripts let you:
3016
+ ${chalk4.green("\u2022")} Add/remove Meta, Google, TikTok pixels without code
3017
+ ${chalk4.green("\u2022")} Auto-fire datalyr.track() events to all pixels
3018
+ ${chalk4.green("\u2022")} Inject custom scripts (Hotjar, Intercom, etc.)`,
3019
+ "Container Scripts"
3020
+ );
3021
+ }
3022
+ }
3023
+ }
3024
+ const containerEnabled = enableContainer !== false;
3025
+ const businessTypeChoice = await p.select({
3026
+ message: "What type of app are you building?",
3027
+ options: BUSINESS_TYPES.map((type) => ({
3028
+ value: type.id,
3029
+ label: type.label,
3030
+ hint: type.hint
3031
+ }))
3032
+ });
3033
+ if (p.isCancel(businessTypeChoice)) {
3034
+ p.cancel("Wizard cancelled");
3035
+ return { success: false, error: "User cancelled" };
3036
+ }
3037
+ const selectedBusinessType = businessTypeChoice;
3038
+ const suggestedEvents = getEventSuggestions(selectedBusinessType);
3039
+ const attributionEvents = getAttributionEvents(platformConfig);
3040
+ const autoTrackedEvents = platformType === "mobile" || platformType === "both" ? ["page_view / screen_view", "session_start", "app_install (mobile)", "attribution data"] : ["page_view", "session_start", "referrer", "UTM parameters"];
3041
+ p.note(
3042
+ `${chalk4.bold("Auto-tracked (no code needed):")}
3043
+ ` + autoTrackedEvents.map((e) => ` ${chalk4.dim("\u2022")} ${e}`).join("\n") + `
3044
+
3045
+ ${chalk4.bold("High-value events")} are the key actions that matter for your business:
3046
+ ${chalk4.dim("\u2022")} Conversions (signups, purchases, leads)
3047
+ ${chalk4.dim("\u2022")} Engagement (feature usage, key interactions)
3048
+ ${chalk4.dim("\u2022")} Revenue (transactions, subscriptions)`,
3049
+ "Event Tracking"
3050
+ );
3051
+ const allEvents = [...suggestedEvents, ...attributionEvents.map((e) => ({
3052
+ name: e.name,
3053
+ description: e.description,
3054
+ properties: e.properties,
3055
+ priority: "high"
3056
+ }))];
3057
+ const uniqueEvents = allEvents.filter(
3058
+ (event, index, self) => index === self.findIndex((e) => e.name === event.name)
3059
+ );
3060
+ const eventOptions = buildEventSelectOptions(uniqueEvents, true);
3061
+ const businessTypeLabel = BUSINESS_TYPES.find((b) => b.id === selectedBusinessType)?.label || selectedBusinessType;
3062
+ p.note(
3063
+ `Based on your ${chalk4.cyan(businessTypeLabel)} app, we suggest these events:
3064
+
3065
+ ` + uniqueEvents.filter((e) => e.priority === "high").map((e) => ` ${chalk4.green("\u2022")} ${chalk4.bold(e.name)}: ${formatEventDescription(e)}`).join("\n"),
3066
+ "Suggested Events"
3067
+ );
3068
+ const selectedEventNames = await p.multiselect({
3069
+ message: "Select events to track:",
3070
+ options: eventOptions,
3071
+ initialValues: ["__all_recommended__"],
3072
+ required: false
3073
+ });
3074
+ let selectedEvents = p.isCancel(selectedEventNames) ? uniqueEvents.filter((e) => e.priority === "high") : resolveSelectedEvents(selectedEventNames, uniqueEvents);
3075
+ const addCustom = await p.confirm({
3076
+ message: "Want to add any custom events specific to your app?",
3077
+ initialValue: false
3078
+ });
3079
+ if (!p.isCancel(addCustom) && addCustom) {
3080
+ const customEventInput = await p.text({
3081
+ message: "Enter custom event names (comma-separated):",
3082
+ placeholder: "checkout_completed, feature_clicked, ..."
3083
+ });
3084
+ if (!p.isCancel(customEventInput) && customEventInput) {
3085
+ const customEvents = customEventInput.split(",").map((e) => e.trim()).filter((e) => e.length > 0).map((name) => ({
3086
+ name,
3087
+ description: "Custom event",
3088
+ properties: ["value", "context"],
3089
+ priority: "high"
3090
+ }));
3091
+ selectedEvents = [...selectedEvents, ...customEvents];
3092
+ }
3093
+ }
3094
+ if (selectedEvents.length > 0) {
3095
+ const eventList = selectedEvents.slice(0, 6).map((e) => ` ${chalk4.green("\u2022")} ${chalk4.bold(e.name)}`).join("\n");
3096
+ const moreCount = selectedEvents.length > 6 ? ` (+${selectedEvents.length - 6} more)` : "";
3097
+ p.note(
3098
+ `Events you'll track:
3099
+
3100
+ ${eventList}${moreCount}`,
3101
+ "Your Events"
3102
+ );
3103
+ }
3104
+ const platformSdks = getSdksForPlatform(platformType, adPlatforms);
3105
+ const allSdks = [.../* @__PURE__ */ new Set([...detection.sdks, ...platformSdks])];
3106
+ const platformSteps = getPlatformPostInstallSteps(platformConfig);
3107
+ p.note(
3108
+ `The wizard will:
3109
+ ${chalk4.green("\u2022")} Install ${chalk4.cyan(allSdks.join(", "))}
3110
+ ${chalk4.green("\u2022")} Create initialization code
3111
+ ${chalk4.green("\u2022")} Configure environment variables
3112
+ ${chalk4.green("\u2022")} Set up workspace: ${chalk4.cyan(workspace.name)}
3113
+ ${adPlatforms.length > 0 ? `${chalk4.green("\u2022")} Configure attribution for: ${chalk4.cyan(adPlatforms.join(", "))}` : ""}
3114
+ ${containerEnabled && platformType !== "mobile" ? `${chalk4.green("\u2022")} Enable container scripts for pixel management` : ""}`,
3115
+ "Installation Plan"
3116
+ );
3117
+ const proceed = await p.confirm({
3118
+ message: "Proceed with installation?",
3119
+ initialValue: true
3120
+ });
3121
+ if (p.isCancel(proceed) || !proceed) {
3122
+ p.cancel("Wizard cancelled");
3123
+ return { success: false, error: "User cancelled" };
3124
+ }
3125
+ const agentSpinner = p.spinner();
3126
+ agentSpinner.start("AI agent is working...");
3127
+ try {
3128
+ const result = await executeAgent({
3129
+ framework,
3130
+ apiKey: validatedApiKey,
3131
+ cwd,
3132
+ docs: getFrameworkDocs(framework, validatedApiKey),
3133
+ debug: options.debug,
3134
+ enableContainer: containerEnabled,
3135
+ workspaceId: workspace.id
3136
+ });
3137
+ if (result.success) {
3138
+ agentSpinner.stop(chalk4.green("Installation complete!"));
3139
+ if (!options.skipVerification) {
3140
+ const verifyResult = await verifyInstallation(validatedApiKey, workspace.id);
3141
+ if (verifyResult.success) {
3142
+ p.log.success("SDK verified and ready to track events!");
3143
+ } else {
3144
+ p.log.warn("Could not verify SDK installation. This may be normal if events haven't been sent yet.");
3145
+ p.log.info(`Check your dashboard at: ${chalk4.cyan(`https://app.datalyr.com/dashboard/${workspace.id}/events`)}`);
3146
+ }
3147
+ }
3148
+ try {
3149
+ const aiContextDoc = generateAIContextDoc({
3150
+ workspaceName: workspace.name,
3151
+ workspaceId: workspace.id,
3152
+ framework,
3153
+ platformType,
3154
+ adPlatforms,
3155
+ businessType: selectedBusinessType,
3156
+ selectedEvents,
3157
+ sdks: allSdks,
3158
+ enableServerSideConversions: platformConfig.enableServerSideConversions,
3159
+ enableContainer: containerEnabled
3160
+ });
3161
+ await writeFile2(join(cwd, ".datalyr.md"), aiContextDoc);
3162
+ p.log.success("Created .datalyr.md for AI coding assistants");
3163
+ } catch {
3164
+ }
3165
+ const eventExamples = selectedEvents.slice(0, 3).map(
3166
+ (e) => `datalyr.track('${e.name}', { ${e.properties.slice(0, 2).map((p2) => `${p2}: '...'`).join(", ")} })`
3167
+ ).join("\n ");
3168
+ const stepsText = platformSteps.length > 0 ? `
3169
+
3170
+ ${chalk4.bold("Next steps:")}
3171
+ ${platformSteps.map((s) => ` ${chalk4.yellow("\u2192")} ${s}`).join("\n")}` : "";
3172
+ p.note(
3173
+ `Your workspace "${workspace.name}" is ready!
3174
+
3175
+ ${chalk4.bold("Start tracking events:")}
3176
+ ${eventExamples || "datalyr.track('event_name', { property: 'value' })"}
3177
+
3178
+ ${chalk4.bold("View your data:")}
3179
+ ${chalk4.cyan(`https://app.datalyr.com/dashboard/${workspace.id}/events`)}${stepsText}`,
3180
+ "Success!"
3181
+ );
3182
+ p.outro(chalk4.green("Datalyr is ready to use!"));
3183
+ } else {
3184
+ agentSpinner.stop(chalk4.red("Installation failed"));
3185
+ p.log.error(result.error || "Unknown error");
3186
+ }
3187
+ return result;
3188
+ } catch (error) {
3189
+ agentSpinner.stop(chalk4.red("Agent error"));
3190
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
3191
+ p.log.error(errorMessage);
3192
+ return { success: false, error: errorMessage };
3193
+ }
3194
+ }
3195
+ async function executeAgent(params) {
3196
+ const { framework, apiKey, cwd, docs, debug, enableContainer = true, workspaceId } = params;
3197
+ const initialMessage = buildIntegrationPrompt(framework, apiKey, docs, enableContainer, workspaceId);
3198
+ const messages = [
3199
+ { role: "user", content: initialMessage }
3200
+ ];
3201
+ const filesModified = [];
3202
+ const maxIterations = 20;
3203
+ let iterations = 0;
3204
+ while (iterations < maxIterations) {
3205
+ iterations++;
3206
+ if (debug) {
3207
+ console.log(`[DEBUG] Agent iteration ${iterations}`);
3208
+ }
3209
+ try {
3210
+ const controller = new AbortController();
3211
+ const timeoutId = setTimeout(() => controller.abort(), 6e4);
3212
+ const response = await fetch(`${LLM_GATEWAY_URL}/agent`, {
3213
+ method: "POST",
3214
+ headers: {
3215
+ "Content-Type": "application/json"
3216
+ },
3217
+ body: JSON.stringify({
3218
+ messages,
3219
+ system: buildSystemPrompt()
3220
+ }),
3221
+ signal: controller.signal
3222
+ });
3223
+ clearTimeout(timeoutId);
3224
+ if (!response.ok) {
3225
+ const error = await response.text();
3226
+ return { success: false, error: `Gateway error: ${error}` };
3227
+ }
3228
+ const claudeResponse = await response.json();
3229
+ if (debug) {
3230
+ console.log("[DEBUG] Response:", JSON.stringify(claudeResponse, null, 2));
3231
+ }
3232
+ messages.push({ role: "assistant", content: claudeResponse.content });
3233
+ if (claudeResponse.stop_reason === "end_turn") {
3234
+ const taskComplete = claudeResponse.content.find(
3235
+ (block) => block.type === "tool_use" && block.name === "task_complete"
3236
+ );
3237
+ if (taskComplete) {
3238
+ const input2 = taskComplete.input;
3239
+ return {
3240
+ success: input2.success,
3241
+ message: input2.summary,
3242
+ filesModified: input2.files_modified || filesModified
3243
+ };
3244
+ }
3245
+ if (filesModified.length > 0) {
3246
+ return {
3247
+ success: false,
3248
+ message: "Agent stopped unexpectedly. Files were modified but installation may be incomplete.",
3249
+ error: "Agent did not call task_complete. Please verify the installation manually.",
3250
+ filesModified
3251
+ };
3252
+ }
3253
+ return {
3254
+ success: false,
3255
+ error: "Agent stopped without completing the installation task."
3256
+ };
3257
+ }
3258
+ if (claudeResponse.stop_reason === "tool_use") {
3259
+ const toolUseBlocks = claudeResponse.content.filter(
3260
+ (block) => block.type === "tool_use"
3261
+ );
3262
+ const toolResults = [];
3263
+ for (const toolUse of toolUseBlocks) {
3264
+ if (toolUse.name === "task_complete") {
3265
+ const input2 = toolUse.input;
3266
+ return {
3267
+ success: input2.success,
3268
+ message: input2.summary,
3269
+ filesModified: input2.files_modified || filesModified
3270
+ };
3271
+ }
3272
+ const result = await executeTool(toolUse, cwd, debug);
3273
+ if (toolUse.name === "write_file" && !result.is_error) {
3274
+ const path6 = toolUse.input.path;
3275
+ if (!filesModified.includes(path6)) {
3276
+ filesModified.push(path6);
3277
+ }
3278
+ }
3279
+ toolResults.push({
3280
+ type: "tool_result",
3281
+ tool_use_id: toolUse.id,
3282
+ content: result.content,
3283
+ is_error: result.is_error
3284
+ });
3285
+ }
3286
+ messages.push({ role: "user", content: toolResults });
3287
+ }
3288
+ } catch (error) {
3289
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
3290
+ if (debug) {
3291
+ console.error("[DEBUG] Agent error:", error);
3292
+ }
3293
+ return { success: false, error: `Agent error: ${errorMessage}` };
3294
+ }
3295
+ }
3296
+ return { success: false, error: "Agent exceeded maximum iterations" };
3297
+ }
3298
+ function isPathSafe(basePath, targetPath) {
3299
+ const resolvedBase = resolve(basePath);
3300
+ const resolvedTarget = resolve(basePath, targetPath);
3301
+ return resolvedTarget.startsWith(resolvedBase + "/") || resolvedTarget === resolvedBase;
3302
+ }
3303
+ async function executeTool(toolUse, cwd, debug) {
3304
+ const { name, input: input2 } = toolUse;
3305
+ if (debug) {
3306
+ console.log(`[DEBUG] Executing tool: ${name}`, input2);
3307
+ }
3308
+ try {
3309
+ switch (name) {
3310
+ case "read_file": {
3311
+ if (typeof input2.path !== "string" || !input2.path.trim()) {
3312
+ return { content: "Error: path must be a non-empty string", is_error: true };
3313
+ }
3314
+ const path6 = input2.path.trim();
3315
+ if (!isPathSafe(cwd, path6)) {
3316
+ return { content: "Error: path traversal detected - access denied", is_error: true };
3317
+ }
3318
+ const fullPath = join(cwd, path6);
3319
+ const content = await readFile2(fullPath, "utf-8");
3320
+ return { content };
3321
+ }
3322
+ case "write_file": {
3323
+ if (typeof input2.path !== "string" || !input2.path.trim()) {
3324
+ return { content: "Error: path must be a non-empty string", is_error: true };
3325
+ }
3326
+ if (typeof input2.content !== "string") {
3327
+ return { content: "Error: content must be a string", is_error: true };
3328
+ }
3329
+ const path6 = input2.path.trim();
3330
+ const content = input2.content;
3331
+ if (!isPathSafe(cwd, path6)) {
3332
+ return { content: "Error: path traversal detected - access denied", is_error: true };
3333
+ }
3334
+ const fullPath = join(cwd, path6);
3335
+ const dir = dirname(fullPath);
3336
+ if (!existsSync(dir)) {
3337
+ await mkdir(dir, { recursive: true });
3338
+ }
3339
+ await writeFile2(fullPath, content, "utf-8");
3340
+ return { content: `Successfully wrote ${path6}` };
3341
+ }
3342
+ case "run_command": {
3343
+ if (typeof input2.command !== "string" || !input2.command.trim()) {
3344
+ return { content: "Error: command must be a non-empty string", is_error: true };
3345
+ }
3346
+ const command = input2.command.trim();
3347
+ const validation = validateBashCommand(command);
3348
+ if (!validation.allowed) {
3349
+ return {
3350
+ content: `Command blocked: ${validation.reason}`,
3351
+ is_error: true
3352
+ };
3353
+ }
3354
+ const { stdout, stderr } = await execAsync(command, { cwd, timeout: 12e4 });
3355
+ return { content: stdout || stderr || "Command completed successfully" };
3356
+ }
3357
+ case "list_files": {
3358
+ if (typeof input2.path !== "string") {
3359
+ return { content: "Error: path must be a string", is_error: true };
3360
+ }
3361
+ const path6 = input2.path.trim() || ".";
3362
+ const pattern = typeof input2.pattern === "string" ? input2.pattern : "*";
3363
+ if (!isPathSafe(cwd, path6)) {
3364
+ return { content: "Error: path traversal detected - access denied", is_error: true };
3365
+ }
3366
+ const fullPath = join(cwd, path6);
3367
+ const files = await glob2(pattern, {
3368
+ cwd: fullPath,
3369
+ nodir: false,
3370
+ maxDepth: 3
3371
+ });
3372
+ return { content: files.slice(0, 100).join("\n") };
3373
+ }
3374
+ default:
3375
+ return { content: `Unknown tool: ${name}`, is_error: true };
3376
+ }
3377
+ } catch (error) {
3378
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
3379
+ return { content: `Error: ${errorMessage}`, is_error: true };
3380
+ }
3381
+ }
3382
+ function buildIntegrationPrompt(framework, apiKey, docs, enableContainer, workspaceId) {
3383
+ const containerConfig = enableContainer ? " enableContainer: true, // Load third-party pixels from Datalyr dashboard" : " enableContainer: false, // Container scripts disabled";
3384
+ const envVarName = framework === "nextjs" ? "NEXT_PUBLIC_DATALYR_WORKSPACE_ID" : framework === "sveltekit" ? "PUBLIC_DATALYR_WORKSPACE_ID" : framework.startsWith("react") && framework !== "react-native" ? "VITE_DATALYR_WORKSPACE_ID" : "DATALYR_WORKSPACE_ID";
3385
+ return `Install Datalyr analytics into this ${getFrameworkDisplayName(framework)} project.
3386
+
3387
+ ## Workspace ID
3388
+ ${workspaceId}
3389
+
3390
+ ## API Key (for server-side only)
3391
+ ${apiKey}
3392
+
3393
+ ## Integration Documentation
3394
+ ${docs}
3395
+
3396
+ ## SDK Configuration
3397
+ When initializing the SDK, use these options:
3398
+ \`\`\`typescript
3399
+ datalyr.init({
3400
+ workspaceId: process.env.${envVarName},
3401
+ ${containerConfig}
3402
+ });
3403
+ \`\`\`
3404
+
3405
+ ## Environment Variable
3406
+ Add to .env.local (or .env):
3407
+ ${envVarName}=${workspaceId}
3408
+
3409
+ ## Your Task
3410
+
3411
+ 1. Read package.json to understand the project
3412
+ 2. Install the SDK: run the appropriate install command
3413
+ 3. Create initialization code following the documentation
3414
+ 4. Add the environment variable to .env.local or .env
3415
+ 5. Call task_complete when done
3416
+
3417
+ Start by reading package.json.`;
3418
+ }
3419
+ async function validateApiKey(apiKey) {
3420
+ const controller = new AbortController();
3421
+ const timeoutId = setTimeout(() => controller.abort(), 3e4);
3422
+ const response = await fetch(`${LLM_GATEWAY_URL}/validate-key`, {
3423
+ method: "POST",
3424
+ headers: {
3425
+ "Content-Type": "application/json"
3426
+ },
3427
+ body: JSON.stringify({ apiKey }),
3428
+ signal: controller.signal
3429
+ });
3430
+ clearTimeout(timeoutId);
3431
+ if (!response.ok) {
3432
+ const error = await response.json();
3433
+ throw new Error(error.error || "Failed to validate API key");
3434
+ }
3435
+ const result = await response.json();
3436
+ if (!result.success || !result.workspace) {
3437
+ throw new Error(result.error || "Invalid API key");
3438
+ }
3439
+ return result.workspace;
3440
+ }
3441
+ async function fetchWorkspaces(apiKey) {
3442
+ const controller = new AbortController();
3443
+ const timeoutId = setTimeout(() => controller.abort(), 3e4);
3444
+ const response = await fetch(`${LLM_GATEWAY_URL}/workspaces`, {
3445
+ method: "POST",
3446
+ headers: {
3447
+ "Content-Type": "application/json"
3448
+ },
3449
+ body: JSON.stringify({ apiKey }),
3450
+ signal: controller.signal
3451
+ });
3452
+ clearTimeout(timeoutId);
3453
+ if (!response.ok) {
3454
+ return [];
3455
+ }
3456
+ const result = await response.json();
3457
+ if (!result.success || !result.workspaces) {
3458
+ return [];
3459
+ }
3460
+ return result.workspaces;
3461
+ }
3462
+ async function verifyInstallation(apiKey, workspaceId) {
3463
+ try {
3464
+ const controller = new AbortController();
3465
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
3466
+ const response = await fetch(`${LLM_GATEWAY_URL}/verify`, {
3467
+ method: "POST",
3468
+ headers: {
3469
+ "Content-Type": "application/json"
3470
+ },
3471
+ body: JSON.stringify({ apiKey, workspaceId }),
3472
+ signal: controller.signal
3473
+ });
3474
+ clearTimeout(timeoutId);
3475
+ if (!response.ok) {
3476
+ return { success: false };
3477
+ }
3478
+ const result = await response.json();
3479
+ return result;
3480
+ } catch {
3481
+ return { success: false };
3482
+ }
3483
+ }
3484
+
1116
3485
  // src/index.ts
1117
3486
  async function runWizard(options = {}) {
1118
3487
  const cwd = options.cwd || process.cwd();
@@ -1130,9 +3499,9 @@ async function runWizard(options = {}) {
1130
3499
  if (!options.json) {
1131
3500
  logger.step(2, 5, "Analyzing your project...");
1132
3501
  }
1133
- const spinner = options.json ? null : startSpinner("Scanning project structure...");
3502
+ const spinner2 = options.json ? null : startSpinner("Scanning project structure...");
1134
3503
  let detection = await detectFramework(cwd);
1135
- if (spinner) {
3504
+ if (spinner2) {
1136
3505
  succeedSpinner(`Detected ${getFrameworkDisplayName(detection.framework)}`);
1137
3506
  }
1138
3507
  if (detection.framework === "unknown" || options.framework) {
@@ -1270,6 +3639,7 @@ async function findEntryPoints(cwd, framework) {
1270
3639
  export {
1271
3640
  detectFramework,
1272
3641
  generateInstallationPlan,
3642
+ runAgentWizard,
1273
3643
  runWizard
1274
3644
  };
1275
3645
  //# sourceMappingURL=index.mjs.map