@datalyr/wizard 1.0.0 → 1.0.1

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