@datalyr/wizard 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2580 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+ "use strict";
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ // bin/wizard.ts
28
+ var import_yargs = __toESM(require("yargs"));
29
+ var import_helpers = require("yargs/helpers");
30
+
31
+ // src/agent/runner.ts
32
+ var p = __toESM(require("@clack/prompts"));
33
+ var import_chalk = __toESM(require("chalk"));
34
+
35
+ // src/agent/interface.ts
36
+ var AGENT_SIGNALS = {
37
+ STATUS: "[STATUS]",
38
+ ERROR_MISSING_KEY: "[ERROR_MISSING_KEY]",
39
+ ERROR_FAILED: "[ERROR_FAILED]",
40
+ SUCCESS: "[SUCCESS]"
41
+ };
42
+ var ALLOWED_COMMANDS = [
43
+ // Package managers
44
+ "npm",
45
+ "yarn",
46
+ "pnpm",
47
+ "bun",
48
+ "npx",
49
+ // Build tools
50
+ "tsc",
51
+ "node",
52
+ // iOS
53
+ "pod",
54
+ "xcodebuild",
55
+ // File operations (read-only)
56
+ "cat",
57
+ "ls",
58
+ "find",
59
+ "grep",
60
+ "head",
61
+ "tail",
62
+ "wc",
63
+ // Git (read-only)
64
+ "git status",
65
+ "git log",
66
+ "git diff",
67
+ "git branch"
68
+ ];
69
+ var BLOCKED_PATTERNS = [
70
+ /;/,
71
+ // Command chaining
72
+ /`/,
73
+ // Backticks
74
+ /\$\(/,
75
+ // Command substitution
76
+ /\|\s*sh/,
77
+ // Piping to shell
78
+ /\|\s*bash/,
79
+ // Piping to bash
80
+ /rm\s+-rf/,
81
+ // Dangerous rm
82
+ />\s*\//,
83
+ // Overwriting system files
84
+ /&&\s*rm/
85
+ // rm after &&
86
+ ];
87
+ function validateBashCommand(command) {
88
+ for (const pattern of BLOCKED_PATTERNS) {
89
+ if (pattern.test(command)) {
90
+ return { allowed: false, reason: `Blocked pattern detected: ${pattern}` };
91
+ }
92
+ }
93
+ const baseCommand = command.trim().split(/\s+/)[0];
94
+ const isAllowed = ALLOWED_COMMANDS.some((allowed) => {
95
+ if (allowed.includes(" ")) {
96
+ return command.startsWith(allowed);
97
+ }
98
+ return baseCommand === allowed;
99
+ });
100
+ if (!isAllowed) {
101
+ return { allowed: false, reason: `Command not in allowlist: ${baseCommand}` };
102
+ }
103
+ return { allowed: true };
104
+ }
105
+ function buildSystemPrompt() {
106
+ return `<role>
107
+ 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.
108
+ </role>
109
+
110
+ <task>
111
+ Install and configure the Datalyr analytics SDK in the user's project. Complete this task by:
112
+ 1. Detecting the framework and understanding the project structure
113
+ 2. Installing the correct SDK packages
114
+ 3. Creating initialization code in the appropriate entry point
115
+ 4. Configuring environment variables
116
+ </task>
117
+
118
+ <sdks>
119
+ | SDK | Use Case | Install Command |
120
+ |-----|----------|-----------------|
121
+ | @datalyr/web | Browser apps (React, Vue, Svelte, Next.js client) | npm install @datalyr/web |
122
+ | @datalyr/api | Server-side (Next.js API routes, Express, Node) | npm install @datalyr/api |
123
+ | @datalyr/react-native | React Native & Expo mobile apps | npm install @datalyr/react-native |
124
+ | DatalyrSDK | Native iOS Swift apps | Swift Package Manager |
125
+ </sdks>
126
+
127
+ <rules>
128
+ 1. ALWAYS read files before modifying them - never edit blind
129
+ 2. PRESERVE existing code - only add Datalyr, never remove functionality
130
+ 3. MATCH the project's code style (indentation, quotes, semicolons)
131
+ 4. USE TypeScript if the project uses TypeScript
132
+ 5. PLACE initialization in the correct entry point for the framework
133
+ 6. UPDATE .env or .env.local with NEXT_PUBLIC_DATALYR_WORKSPACE_ID
134
+ 7. CHECK for existing Datalyr setup first - don't duplicate
135
+ </rules>
136
+
137
+ <workflow>
138
+ Step 1: Read package.json to detect framework and package manager
139
+ Step 2: List files to find entry points (app/layout.tsx, src/main.tsx, App.tsx, etc.)
140
+ Step 3: Read entry point files to understand current structure
141
+ Step 4: Install SDK packages using detected package manager
142
+ Step 5: Create initialization file (lib/datalyr.ts or similar)
143
+ Step 6: Update entry point to import and initialize Datalyr
144
+ Step 7: Update or create .env.local with workspace ID placeholder
145
+ Step 8: Call task_complete with summary of changes
146
+ </workflow>
147
+
148
+ <examples>
149
+ <example name="nextjs-app-router">
150
+ For Next.js 13+ with App Router, create app/providers.tsx:
151
+ \`\`\`tsx
152
+ 'use client';
153
+ import datalyr from '@datalyr/web';
154
+ import { useEffect } from 'react';
155
+
156
+ export function DatalyrProvider({ children }: { children: React.ReactNode }) {
157
+ useEffect(() => {
158
+ datalyr.init({
159
+ workspaceId: process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID!,
160
+ debug: process.env.NODE_ENV === 'development',
161
+ });
162
+ }, []);
163
+ return <>{children}</>;
164
+ }
165
+ \`\`\`
166
+ Then wrap children in app/layout.tsx with <DatalyrProvider>.
167
+ </example>
168
+
169
+ <example name="react-vite">
170
+ For React + Vite, create src/lib/datalyr.ts:
171
+ \`\`\`ts
172
+ import datalyr from '@datalyr/web';
173
+
174
+ let initialized = false;
175
+
176
+ export function initDatalyr() {
177
+ if (initialized) return;
178
+ datalyr.init({
179
+ workspaceId: import.meta.env.VITE_DATALYR_WORKSPACE_ID,
180
+ debug: import.meta.env.DEV,
181
+ });
182
+ initialized = true;
183
+ }
184
+
185
+ export { datalyr };
186
+ \`\`\`
187
+ Then call initDatalyr() at the top of src/main.tsx.
188
+ </example>
189
+
190
+ <example name="react-native">
191
+ For React Native, create src/utils/datalyr.ts:
192
+ \`\`\`ts
193
+ import { Datalyr } from '@datalyr/react-native';
194
+
195
+ export async function initDatalyr(apiKey: string) {
196
+ await Datalyr.initialize({
197
+ apiKey,
198
+ enableAutoEvents: true,
199
+ debug: __DEV__,
200
+ });
201
+ }
202
+
203
+ export { Datalyr };
204
+ \`\`\`
205
+ Then call initDatalyr() in App.tsx useEffect.
206
+ </example>
207
+ </examples>
208
+
209
+ <signals>
210
+ When complete: ${AGENT_SIGNALS.SUCCESS}
211
+ On error: ${AGENT_SIGNALS.ERROR_FAILED}
212
+ Status updates: ${AGENT_SIGNALS.STATUS} <message>
213
+ </signals>`;
214
+ }
215
+
216
+ // src/detection/detector.ts
217
+ var import_path2 = __toESM(require("path"));
218
+
219
+ // src/utils/fs.ts
220
+ var import_fs_extra = __toESM(require("fs-extra"));
221
+ var import_path = __toESM(require("path"));
222
+ var import_glob = require("glob");
223
+ async function fileExists(filePath) {
224
+ try {
225
+ await import_fs_extra.default.access(filePath);
226
+ return true;
227
+ } catch {
228
+ return false;
229
+ }
230
+ }
231
+ async function readJson(filePath) {
232
+ try {
233
+ return await import_fs_extra.default.readJson(filePath);
234
+ } catch {
235
+ return null;
236
+ }
237
+ }
238
+ async function readPackageJson(cwd) {
239
+ return readJson(import_path.default.join(cwd, "package.json"));
240
+ }
241
+ async function findFile(cwd, patterns) {
242
+ for (const pattern of patterns) {
243
+ const matches = await (0, import_glob.glob)(pattern, { cwd, nodir: true });
244
+ if (matches.length > 0) {
245
+ return import_path.default.join(cwd, matches[0]);
246
+ }
247
+ }
248
+ return null;
249
+ }
250
+
251
+ // src/detection/detector.ts
252
+ async function detectFramework(cwd) {
253
+ const signals = [];
254
+ const pkg = await readPackageJson(cwd);
255
+ const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
256
+ if (await fileExists(import_path2.default.join(cwd, "Package.swift"))) {
257
+ signals.push({ framework: "ios", score: 100, signal: "Package.swift found" });
258
+ }
259
+ const xcodeprojs = await findFile(cwd, ["*.xcodeproj", "*.xcworkspace"]);
260
+ if (xcodeprojs) {
261
+ signals.push({ framework: "ios", score: 90, signal: "Xcode project found" });
262
+ }
263
+ if (!pkg) {
264
+ const iosScore = signals.filter((s) => s.framework === "ios").reduce((a, b) => a + b.score, 0);
265
+ if (iosScore > 0) {
266
+ return buildResult("ios", signals, null);
267
+ }
268
+ return buildResult("unknown", [], null);
269
+ }
270
+ if (deps.next) {
271
+ signals.push({ framework: "nextjs", score: 100, signal: "next in dependencies" });
272
+ if (await fileExists(import_path2.default.join(cwd, "app", "layout.tsx")) || await fileExists(import_path2.default.join(cwd, "app", "layout.js"))) {
273
+ signals.push({ framework: "nextjs", score: 20, signal: "App Router detected" });
274
+ }
275
+ if (await fileExists(import_path2.default.join(cwd, "pages", "_app.tsx")) || await fileExists(import_path2.default.join(cwd, "pages", "_app.js"))) {
276
+ signals.push({ framework: "nextjs", score: 15, signal: "Pages Router detected" });
277
+ }
278
+ }
279
+ if (deps.expo) {
280
+ signals.push({ framework: "expo", score: 100, signal: "expo in dependencies" });
281
+ }
282
+ if (deps["react-native"] && !deps.expo) {
283
+ signals.push({ framework: "react-native", score: 100, signal: "react-native in dependencies" });
284
+ }
285
+ if (deps["@remix-run/react"] || deps["@remix-run/node"]) {
286
+ signals.push({ framework: "remix", score: 100, signal: "@remix-run packages found" });
287
+ }
288
+ if (deps.astro) {
289
+ signals.push({ framework: "astro", score: 100, signal: "astro in dependencies" });
290
+ }
291
+ if (deps["@sveltejs/kit"]) {
292
+ signals.push({ framework: "sveltekit", score: 100, signal: "@sveltejs/kit in dependencies" });
293
+ } else if (deps.svelte) {
294
+ signals.push({ framework: "svelte", score: 80, signal: "svelte in dependencies" });
295
+ }
296
+ if (deps.nuxt) {
297
+ signals.push({ framework: "nuxt", score: 100, signal: "nuxt in dependencies" });
298
+ } else if (deps.vue) {
299
+ signals.push({ framework: "vue", score: 80, signal: "vue in dependencies" });
300
+ }
301
+ if (deps.react && deps["react-dom"]) {
302
+ if (!deps.next && !deps["@remix-run/react"] && !deps["react-native"] && !deps.expo) {
303
+ if (deps.vite || deps["@vitejs/plugin-react"]) {
304
+ signals.push({ framework: "react-vite", score: 90, signal: "React + Vite" });
305
+ } else if (deps["react-scripts"]) {
306
+ signals.push({ framework: "react", score: 85, signal: "Create React App" });
307
+ } else {
308
+ signals.push({ framework: "react", score: 70, signal: "React project" });
309
+ }
310
+ }
311
+ }
312
+ if (deps.express || deps.fastify || deps.koa || deps.hapi) {
313
+ if (!deps.react && !deps.vue && !deps.svelte) {
314
+ signals.push({ framework: "node", score: 80, signal: "Node.js server framework" });
315
+ }
316
+ }
317
+ const frameworkScores = /* @__PURE__ */ new Map();
318
+ signals.forEach((s) => {
319
+ const current = frameworkScores.get(s.framework) || 0;
320
+ frameworkScores.set(s.framework, current + s.score);
321
+ });
322
+ let bestFramework = "unknown";
323
+ let bestScore = 0;
324
+ frameworkScores.forEach((score, framework) => {
325
+ if (score > bestScore) {
326
+ bestScore = score;
327
+ bestFramework = framework;
328
+ }
329
+ });
330
+ return buildResult(bestFramework, signals, pkg);
331
+ }
332
+ function buildResult(framework, signals, pkg) {
333
+ const relevantSignals = signals.filter((s) => s.framework === framework).map((s) => s.signal);
334
+ const confidence = Math.min(100, signals.filter((s) => s.framework === framework).reduce((a, b) => a + b.score, 0));
335
+ const language = detectLanguage(pkg);
336
+ const sdks = getSDKsForFramework(framework);
337
+ const hasAppRouter = framework === "nextjs" && relevantSignals.some((s) => s.includes("App Router"));
338
+ return {
339
+ framework,
340
+ confidence,
341
+ signals: relevantSignals,
342
+ sdks,
343
+ language,
344
+ hasAppRouter,
345
+ isExpo: framework === "expo"
346
+ };
347
+ }
348
+ function detectLanguage(pkg) {
349
+ if (!pkg) return "javascript";
350
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
351
+ return deps.typescript ? "typescript" : "javascript";
352
+ }
353
+ function getSDKsForFramework(framework) {
354
+ switch (framework) {
355
+ case "nextjs":
356
+ case "remix":
357
+ case "sveltekit":
358
+ case "nuxt":
359
+ case "astro":
360
+ return ["@datalyr/web", "@datalyr/api"];
361
+ case "react":
362
+ case "react-vite":
363
+ case "svelte":
364
+ case "vue":
365
+ return ["@datalyr/web"];
366
+ case "react-native":
367
+ case "expo":
368
+ return ["@datalyr/react-native"];
369
+ case "ios":
370
+ return ["DatalyrSDK"];
371
+ case "node":
372
+ return ["@datalyr/api"];
373
+ default:
374
+ return ["@datalyr/web"];
375
+ }
376
+ }
377
+ function getFrameworkDisplayName(framework) {
378
+ const names = {
379
+ nextjs: "Next.js",
380
+ react: "React (Create React App)",
381
+ "react-vite": "React (Vite)",
382
+ svelte: "Svelte",
383
+ sveltekit: "SvelteKit",
384
+ vue: "Vue",
385
+ nuxt: "Nuxt",
386
+ remix: "Remix",
387
+ astro: "Astro",
388
+ "react-native": "React Native",
389
+ expo: "Expo",
390
+ ios: "iOS (Swift)",
391
+ node: "Node.js",
392
+ unknown: "Unknown"
393
+ };
394
+ return names[framework];
395
+ }
396
+
397
+ // src/agent/docs/index.ts
398
+ function getFrameworkDocs(framework, apiKey) {
399
+ switch (framework) {
400
+ case "nextjs":
401
+ return getNextjsDocs(apiKey);
402
+ case "react":
403
+ case "react-vite":
404
+ return getReactDocs(apiKey);
405
+ case "sveltekit":
406
+ return getSvelteKitDocs(apiKey);
407
+ case "svelte":
408
+ return getSvelteDocs(apiKey);
409
+ case "react-native":
410
+ case "expo":
411
+ return getReactNativeDocs(apiKey, framework === "expo");
412
+ case "ios":
413
+ return getIOSDocs(apiKey);
414
+ case "node":
415
+ return getNodeDocs(apiKey);
416
+ default:
417
+ return getGenericWebDocs(apiKey);
418
+ }
419
+ }
420
+ function getNextjsDocs(apiKey) {
421
+ return `
422
+ # Datalyr Next.js Integration
423
+
424
+ ## Packages to Install
425
+ - @datalyr/web (client-side tracking)
426
+ - @datalyr/api (server-side tracking)
427
+
428
+ ## Environment Variables
429
+ Add to .env.local:
430
+ \`\`\`
431
+ NEXT_PUBLIC_DATALYR_WORKSPACE_ID=your_workspace_id
432
+ DATALYR_API_KEY=${apiKey}
433
+ \`\`\`
434
+
435
+ ## App Router Setup (Next.js 13+)
436
+
437
+ ### 1. Create Provider Component
438
+ Create \`app/providers.tsx\`:
439
+ \`\`\`tsx
440
+ 'use client';
441
+
442
+ import datalyr from '@datalyr/web';
443
+ import { useEffect } from 'react';
444
+
445
+ export function DatalyrProvider({ children }: { children: React.ReactNode }) {
446
+ useEffect(() => {
447
+ datalyr.init({
448
+ workspaceId: process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID!,
449
+ debug: process.env.NODE_ENV === 'development',
450
+ trackSPA: true,
451
+ });
452
+ }, []);
453
+
454
+ return <>{children}</>;
455
+ }
456
+ \`\`\`
457
+
458
+ ### 2. Wrap Layout
459
+ Update \`app/layout.tsx\` to wrap children with the provider:
460
+ \`\`\`tsx
461
+ import { DatalyrProvider } from './providers';
462
+
463
+ export default function RootLayout({ children }) {
464
+ return (
465
+ <html>
466
+ <body>
467
+ <DatalyrProvider>
468
+ {children}
469
+ </DatalyrProvider>
470
+ </body>
471
+ </html>
472
+ );
473
+ }
474
+ \`\`\`
475
+
476
+ ### 3. Server-Side Instance (Optional)
477
+ Create \`lib/datalyr.ts\` for server-side tracking:
478
+ \`\`\`ts
479
+ import Datalyr from '@datalyr/api';
480
+
481
+ export const datalyr = new Datalyr(process.env.DATALYR_API_KEY!);
482
+ \`\`\`
483
+
484
+ ## Pages Router Setup (Legacy)
485
+
486
+ ### 1. Create Hook
487
+ Create \`lib/datalyr.ts\`:
488
+ \`\`\`ts
489
+ import datalyr from '@datalyr/web';
490
+
491
+ let initialized = false;
492
+
493
+ export function initDatalyr() {
494
+ if (initialized || typeof window === 'undefined') return;
495
+
496
+ datalyr.init({
497
+ workspaceId: process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID!,
498
+ debug: process.env.NODE_ENV === 'development',
499
+ trackSPA: true,
500
+ });
501
+
502
+ initialized = true;
503
+ }
504
+
505
+ export { datalyr };
506
+ \`\`\`
507
+
508
+ ### 2. Initialize in _app.tsx
509
+ \`\`\`tsx
510
+ import { useEffect } from 'react';
511
+ import { initDatalyr } from '../lib/datalyr';
512
+
513
+ function MyApp({ Component, pageProps }) {
514
+ useEffect(() => {
515
+ initDatalyr();
516
+ }, []);
517
+
518
+ return <Component {...pageProps} />;
519
+ }
520
+ \`\`\`
521
+ `;
522
+ }
523
+ function getReactDocs(apiKey) {
524
+ return `
525
+ # Datalyr React Integration
526
+
527
+ ## Package to Install
528
+ - @datalyr/web
529
+
530
+ ## Environment Variables
531
+ Add to .env (or .env.local for Vite):
532
+ \`\`\`
533
+ VITE_DATALYR_WORKSPACE_ID=your_workspace_id
534
+ # Or for Create React App:
535
+ REACT_APP_DATALYR_WORKSPACE_ID=your_workspace_id
536
+ \`\`\`
537
+
538
+ ## Setup
539
+
540
+ ### 1. Create Initialization Module
541
+ Create \`src/lib/datalyr.ts\`:
542
+ \`\`\`ts
543
+ import datalyr from '@datalyr/web';
544
+
545
+ let initialized = false;
546
+
547
+ export function initDatalyr() {
548
+ if (initialized || typeof window === 'undefined') return;
549
+
550
+ datalyr.init({
551
+ // Vite uses import.meta.env, CRA uses process.env
552
+ workspaceId: import.meta.env?.VITE_DATALYR_WORKSPACE_ID ||
553
+ process.env.REACT_APP_DATALYR_WORKSPACE_ID,
554
+ debug: import.meta.env?.DEV || process.env.NODE_ENV === 'development',
555
+ trackSPA: true,
556
+ });
557
+
558
+ initialized = true;
559
+ }
560
+
561
+ export { datalyr };
562
+ \`\`\`
563
+
564
+ ### 2. Initialize in Entry Point
565
+ Update \`src/main.tsx\` (Vite) or \`src/index.tsx\` (CRA):
566
+ \`\`\`tsx
567
+ import { initDatalyr } from './lib/datalyr';
568
+
569
+ // Initialize before rendering
570
+ initDatalyr();
571
+
572
+ // ... rest of your app setup
573
+ \`\`\`
574
+
575
+ ## Tracking Events
576
+ \`\`\`ts
577
+ import { datalyr } from './lib/datalyr';
578
+
579
+ // Track an event
580
+ datalyr.track('button_clicked', { button_id: 'signup' });
581
+
582
+ // Identify a user
583
+ datalyr.identify('user_123', { email: 'user@example.com' });
584
+ \`\`\`
585
+ `;
586
+ }
587
+ function getSvelteKitDocs(apiKey) {
588
+ return `
589
+ # Datalyr SvelteKit Integration
590
+
591
+ ## Packages to Install
592
+ - @datalyr/web (client-side)
593
+ - @datalyr/api (server-side)
594
+
595
+ ## Environment Variables
596
+ Add to .env:
597
+ \`\`\`
598
+ PUBLIC_DATALYR_WORKSPACE_ID=your_workspace_id
599
+ DATALYR_API_KEY=${apiKey}
600
+ \`\`\`
601
+
602
+ ## Setup
603
+
604
+ ### 1. Client-Side Initialization
605
+ Create \`src/lib/datalyr.ts\`:
606
+ \`\`\`ts
607
+ import datalyr from '@datalyr/web';
608
+ import { browser } from '$app/environment';
609
+
610
+ let initialized = false;
611
+
612
+ export function initDatalyr() {
613
+ if (initialized || !browser) return;
614
+
615
+ datalyr.init({
616
+ workspaceId: import.meta.env.PUBLIC_DATALYR_WORKSPACE_ID,
617
+ debug: import.meta.env.DEV,
618
+ trackSPA: true,
619
+ });
620
+
621
+ initialized = true;
622
+ }
623
+
624
+ export { datalyr };
625
+ \`\`\`
626
+
627
+ ### 2. Initialize in Layout
628
+ Update \`src/routes/+layout.svelte\`:
629
+ \`\`\`svelte
630
+ <script>
631
+ import { onMount } from 'svelte';
632
+ import { initDatalyr } from '$lib/datalyr';
633
+
634
+ onMount(() => {
635
+ initDatalyr();
636
+ });
637
+ </script>
638
+
639
+ <slot />
640
+ \`\`\`
641
+
642
+ ### 3. Server-Side Instance
643
+ Create \`src/lib/server/datalyr.ts\`:
644
+ \`\`\`ts
645
+ import Datalyr from '@datalyr/api';
646
+ import { DATALYR_API_KEY } from '$env/static/private';
647
+
648
+ export const datalyr = new Datalyr(DATALYR_API_KEY);
649
+ \`\`\`
650
+ `;
651
+ }
652
+ function getSvelteDocs(apiKey) {
653
+ return `
654
+ # Datalyr Svelte Integration
655
+
656
+ ## Package to Install
657
+ - @datalyr/web
658
+
659
+ ## Environment Variables
660
+ Add to .env:
661
+ \`\`\`
662
+ VITE_DATALYR_WORKSPACE_ID=your_workspace_id
663
+ \`\`\`
664
+
665
+ ## Setup
666
+
667
+ ### 1. Create Initialization Module
668
+ Create \`src/lib/datalyr.ts\`:
669
+ \`\`\`ts
670
+ import datalyr from '@datalyr/web';
671
+
672
+ let initialized = false;
673
+
674
+ export function initDatalyr() {
675
+ if (initialized || typeof window === 'undefined') return;
676
+
677
+ datalyr.init({
678
+ workspaceId: import.meta.env.VITE_DATALYR_WORKSPACE_ID,
679
+ debug: import.meta.env.DEV,
680
+ trackSPA: true,
681
+ });
682
+
683
+ initialized = true;
684
+ }
685
+
686
+ export { datalyr };
687
+ \`\`\`
688
+
689
+ ### 2. Initialize in App
690
+ Update \`src/App.svelte\`:
691
+ \`\`\`svelte
692
+ <script>
693
+ import { onMount } from 'svelte';
694
+ import { initDatalyr } from './lib/datalyr';
695
+
696
+ onMount(() => {
697
+ initDatalyr();
698
+ });
699
+ </script>
700
+
701
+ <!-- Your app content -->
702
+ \`\`\`
703
+ `;
704
+ }
705
+ function getReactNativeDocs(_apiKey, isExpo) {
706
+ const importPath = isExpo ? "@datalyr/react-native/expo" : "@datalyr/react-native";
707
+ const configImport = isExpo ? "import Constants from 'expo-constants';" : "";
708
+ const configAccess = isExpo ? "Constants.expoConfig?.extra?.datalyrApiKey || ''" : "process.env.DATALYR_API_KEY || ''";
709
+ return `
710
+ # Datalyr React Native Integration${isExpo ? " (Expo)" : ""}
711
+
712
+ ## Package to Install
713
+ - @datalyr/react-native
714
+ ${isExpo ? "- expo-constants (for config access)" : ""}
715
+
716
+ ## Post-Install (iOS)
717
+ After installing, run:
718
+ \`\`\`bash
719
+ cd ios && pod install
720
+ \`\`\`
721
+
722
+ ## Environment Configuration
723
+ ${isExpo ? `
724
+ ### For Expo
725
+ Add to \`app.config.js\` or \`app.json\`:
726
+ \`\`\`js
727
+ export default {
728
+ expo: {
729
+ extra: {
730
+ datalyrApiKey: process.env.DATALYR_API_KEY,
731
+ datalyrWorkspaceId: process.env.DATALYR_WORKSPACE_ID,
732
+ },
733
+ },
734
+ };
735
+ \`\`\`
736
+
737
+ Create \`.env\` file:
738
+ \`\`\`
739
+ DATALYR_API_KEY=your_api_key
740
+ DATALYR_WORKSPACE_ID=your_workspace_id
741
+ \`\`\`
742
+ ` : `
743
+ ### For React Native CLI
744
+ Create \`.env\` file and use react-native-config:
745
+ \`\`\`
746
+ DATALYR_API_KEY=your_api_key
747
+ DATALYR_WORKSPACE_ID=your_workspace_id
748
+ \`\`\`
749
+ `}
750
+ **Security Note**: Never commit API keys to source control. Use environment variables or secrets management.
751
+
752
+ ## Setup
753
+
754
+ ### 1. Create Initialization Module
755
+ Create \`src/utils/datalyr.ts\`:
756
+ \`\`\`ts
757
+ import { Datalyr } from '${importPath}';
758
+ ${configImport}
759
+
760
+ let initialized = false;
761
+
762
+ export async function initDatalyr() {
763
+ if (initialized) return;
764
+
765
+ const apiKey = ${configAccess};
766
+
767
+ if (!apiKey) {
768
+ console.warn('[Datalyr] API key not configured');
769
+ return;
770
+ }
771
+
772
+ await Datalyr.initialize({
773
+ apiKey,
774
+ enableAutoEvents: true,
775
+ enableAttribution: true,
776
+ debug: __DEV__,
777
+ });
778
+
779
+ initialized = true;
780
+ }
781
+
782
+ export { Datalyr };
783
+ \`\`\`
784
+
785
+ ### 2. Initialize in App
786
+ Update \`App.tsx\`:
787
+ \`\`\`tsx
788
+ import { useEffect } from 'react';
789
+ import { initDatalyr } from './src/utils/datalyr';
790
+
791
+ export default function App() {
792
+ useEffect(() => {
793
+ initDatalyr();
794
+ }, []);
795
+
796
+ return (
797
+ // Your app content
798
+ );
799
+ }
800
+ \`\`\`
801
+
802
+ ## Tracking Events
803
+ \`\`\`ts
804
+ import { Datalyr } from './src/utils/datalyr';
805
+
806
+ // Track an event
807
+ Datalyr.track('purchase_completed', { amount: 99.99 });
808
+
809
+ // Identify a user
810
+ Datalyr.identify('user_123', { email: 'user@example.com' });
811
+ \`\`\`
812
+ `;
813
+ }
814
+ function getNodeDocs(apiKey) {
815
+ return `
816
+ # Datalyr Node.js Integration
817
+
818
+ ## Package to Install
819
+ - @datalyr/api
820
+
821
+ ## Environment Variables
822
+ Add to .env:
823
+ \`\`\`
824
+ DATALYR_API_KEY=${apiKey}
825
+ \`\`\`
826
+
827
+ ## Setup
828
+
829
+ ### 1. Create Datalyr Instance
830
+ Create \`src/lib/datalyr.ts\`:
831
+ \`\`\`ts
832
+ import Datalyr from '@datalyr/api';
833
+
834
+ const apiKey = process.env.DATALYR_API_KEY;
835
+ if (!apiKey) {
836
+ throw new Error('DATALYR_API_KEY is required');
837
+ }
838
+
839
+ export const datalyr = new Datalyr(apiKey);
840
+ \`\`\`
841
+
842
+ ### 2. Track Events
843
+ \`\`\`ts
844
+ import { datalyr } from './lib/datalyr';
845
+
846
+ // Track server-side events
847
+ await datalyr.track({
848
+ event: 'order_completed',
849
+ userId: 'user_123',
850
+ properties: {
851
+ orderId: 'order_456',
852
+ amount: 99.99,
853
+ },
854
+ });
855
+
856
+ // Identify a user
857
+ await datalyr.identify({
858
+ userId: 'user_123',
859
+ traits: {
860
+ email: 'user@example.com',
861
+ plan: 'premium',
862
+ },
863
+ });
864
+ \`\`\`
865
+ `;
866
+ }
867
+ function getIOSDocs(_apiKey) {
868
+ return `
869
+ # Datalyr iOS (Swift) Integration
870
+
871
+ ## Package to Install
872
+ Add via Swift Package Manager:
873
+ - https://github.com/datalyr/datalyr-ios-sdk
874
+
875
+ ## Setup
876
+
877
+ ### 1. Add Keys to Info.plist
878
+ Add both keys to your Info.plist (or use xcconfig for different environments):
879
+ \`\`\`xml
880
+ <key>DATALYR_WORKSPACE_ID</key>
881
+ <string>YOUR_WORKSPACE_ID</string>
882
+ <key>DATALYR_API_KEY</key>
883
+ <string>YOUR_API_KEY</string>
884
+ \`\`\`
885
+
886
+ **Security Note**: For production apps, consider using xcconfig files or build-time environment variables to inject these values, keeping them out of source control.
887
+
888
+ ### 2. Add Configuration File
889
+ Create \`DatalyrConfig.swift\`:
890
+ \`\`\`swift
891
+ import DatalyrSDK
892
+ import Foundation
893
+
894
+ struct DatalyrConfig {
895
+ static func initialize() {
896
+ guard let workspaceId = Bundle.main.object(forInfoDictionaryKey: "DATALYR_WORKSPACE_ID") as? String,
897
+ let apiKey = Bundle.main.object(forInfoDictionaryKey: "DATALYR_API_KEY") as? String else {
898
+ #if DEBUG
899
+ fatalError("DATALYR_WORKSPACE_ID or DATALYR_API_KEY not found in Info.plist")
900
+ #else
901
+ print("[Datalyr] Warning: SDK not configured - missing Info.plist keys")
902
+ return
903
+ #endif
904
+ }
905
+
906
+ Datalyr.shared.configure(
907
+ apiKey: apiKey,
908
+ workspaceId: workspaceId,
909
+ options: DatalyrOptions(
910
+ debug: false,
911
+ enableAutoEvents: true,
912
+ enableAttribution: true
913
+ )
914
+ )
915
+ }
916
+ }
917
+ \`\`\`
918
+
919
+ ### 3. Initialize in App
920
+ For SwiftUI (\`@main App\`):
921
+ \`\`\`swift
922
+ @main
923
+ struct MyApp: App {
924
+ init() {
925
+ DatalyrConfig.initialize()
926
+ }
927
+
928
+ var body: some Scene {
929
+ WindowGroup {
930
+ ContentView()
931
+ }
932
+ }
933
+ }
934
+ \`\`\`
935
+
936
+ For UIKit (AppDelegate):
937
+ \`\`\`swift
938
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions...) -> Bool {
939
+ DatalyrConfig.initialize()
940
+ return true
941
+ }
942
+ \`\`\`
943
+
944
+ ## Tracking Events
945
+ \`\`\`swift
946
+ import DatalyrSDK
947
+
948
+ // Track an event
949
+ Datalyr.shared.track("purchase_completed", properties: [
950
+ "amount": 99.99,
951
+ "currency": "USD"
952
+ ])
953
+
954
+ // Identify a user
955
+ Datalyr.shared.identify("user_123", traits: [
956
+ "email": "user@example.com",
957
+ "plan": "premium"
958
+ ])
959
+ \`\`\`
960
+ `;
961
+ }
962
+ function getGenericWebDocs(apiKey) {
963
+ return `
964
+ # Datalyr Web Integration
965
+
966
+ ## Package to Install
967
+ - @datalyr/web
968
+
969
+ ## Setup
970
+
971
+ ### Via NPM
972
+ \`\`\`ts
973
+ import datalyr from '@datalyr/web';
974
+
975
+ datalyr.init({
976
+ workspaceId: 'YOUR_WORKSPACE_ID',
977
+ debug: true,
978
+ });
979
+
980
+ // Track events
981
+ datalyr.track('page_viewed', { page: '/home' });
982
+ \`\`\`
983
+
984
+ ### Via Script Tag
985
+ \`\`\`html
986
+ <script src="https://track.datalyr.com/dl.js"
987
+ data-workspace-id="YOUR_WORKSPACE_ID">
988
+ </script>
989
+ \`\`\`
990
+ `;
991
+ }
992
+
993
+ // src/agent/configs/types.ts
994
+ var NEXTJS_CONFIG = {
995
+ id: "nextjs",
996
+ name: "Next.js",
997
+ detectPackage: "next",
998
+ sdks: ["@datalyr/web", "@datalyr/api"],
999
+ envVars: [
1000
+ {
1001
+ key: "NEXT_PUBLIC_DATALYR_WORKSPACE_ID",
1002
+ description: "Your Datalyr workspace ID",
1003
+ isPublic: true
1004
+ },
1005
+ {
1006
+ key: "DATALYR_API_KEY",
1007
+ description: "Server-side API key",
1008
+ isPublic: false
1009
+ }
1010
+ ],
1011
+ estimatedTime: 3,
1012
+ docsUrl: "https://docs.datalyr.com/sdks/nextjs",
1013
+ postInstallSteps: [
1014
+ "Add your workspace ID to .env.local",
1015
+ "Wrap your layout with DatalyrProvider",
1016
+ "Start tracking events with datalyr.track()"
1017
+ ],
1018
+ routerDetection: {
1019
+ appRouter: ["app/layout.tsx", "app/layout.jsx", "app/layout.ts", "app/layout.js"],
1020
+ pagesRouter: ["pages/_app.tsx", "pages/_app.jsx", "pages/_app.ts", "pages/_app.js"]
1021
+ }
1022
+ };
1023
+ var REACT_CONFIG = {
1024
+ id: "react",
1025
+ name: "React",
1026
+ detectPackage: "react",
1027
+ sdks: ["@datalyr/web"],
1028
+ envVars: [
1029
+ {
1030
+ key: "VITE_DATALYR_WORKSPACE_ID",
1031
+ description: "Your Datalyr workspace ID",
1032
+ isPublic: true
1033
+ }
1034
+ ],
1035
+ estimatedTime: 2,
1036
+ docsUrl: "https://docs.datalyr.com/sdks/react",
1037
+ postInstallSteps: [
1038
+ "Add your workspace ID to .env",
1039
+ "Call initDatalyr() in your main file"
1040
+ ]
1041
+ };
1042
+ var REACT_NATIVE_CONFIG = {
1043
+ id: "react-native",
1044
+ name: "React Native",
1045
+ detectPackage: "react-native",
1046
+ sdks: ["@datalyr/react-native"],
1047
+ envVars: [],
1048
+ estimatedTime: 5,
1049
+ docsUrl: "https://docs.datalyr.com/sdks/react-native",
1050
+ postInstallSteps: [
1051
+ "Run: cd ios && pod install",
1052
+ "Call initDatalyr() in App.tsx",
1053
+ "For attribution, configure Meta/TikTok app IDs"
1054
+ ]
1055
+ };
1056
+ var EXPO_CONFIG = {
1057
+ id: "expo",
1058
+ name: "Expo",
1059
+ detectPackage: "expo",
1060
+ sdks: ["@datalyr/react-native"],
1061
+ envVars: [],
1062
+ estimatedTime: 3,
1063
+ docsUrl: "https://docs.datalyr.com/sdks/expo",
1064
+ postInstallSteps: [
1065
+ "Call initDatalyr() in App.tsx",
1066
+ "For attribution, configure Meta/TikTok app IDs in app.json"
1067
+ ]
1068
+ };
1069
+ var SVELTEKIT_CONFIG = {
1070
+ id: "sveltekit",
1071
+ name: "SvelteKit",
1072
+ detectPackage: "@sveltejs/kit",
1073
+ sdks: ["@datalyr/web", "@datalyr/api"],
1074
+ envVars: [
1075
+ {
1076
+ key: "PUBLIC_DATALYR_WORKSPACE_ID",
1077
+ description: "Your Datalyr workspace ID",
1078
+ isPublic: true
1079
+ },
1080
+ {
1081
+ key: "DATALYR_API_KEY",
1082
+ description: "Server-side API key",
1083
+ isPublic: false
1084
+ }
1085
+ ],
1086
+ estimatedTime: 3,
1087
+ docsUrl: "https://docs.datalyr.com/sdks/sveltekit",
1088
+ postInstallSteps: [
1089
+ "Add your workspace ID to .env",
1090
+ "Call initDatalyr() in +layout.svelte"
1091
+ ]
1092
+ };
1093
+ var NODE_CONFIG = {
1094
+ id: "node",
1095
+ name: "Node.js",
1096
+ detectPackage: "express",
1097
+ sdks: ["@datalyr/api"],
1098
+ envVars: [
1099
+ {
1100
+ key: "DATALYR_API_KEY",
1101
+ description: "Your Datalyr API key",
1102
+ isPublic: false
1103
+ }
1104
+ ],
1105
+ estimatedTime: 2,
1106
+ docsUrl: "https://docs.datalyr.com/sdks/node",
1107
+ postInstallSteps: [
1108
+ "Add your API key to .env",
1109
+ "Track events with datalyr.track()"
1110
+ ]
1111
+ };
1112
+ var VUE_CONFIG = {
1113
+ id: "vue",
1114
+ name: "Vue.js",
1115
+ detectPackage: "vue",
1116
+ sdks: ["@datalyr/web"],
1117
+ envVars: [
1118
+ {
1119
+ key: "VITE_DATALYR_WORKSPACE_ID",
1120
+ description: "Your Datalyr workspace ID",
1121
+ isPublic: true
1122
+ }
1123
+ ],
1124
+ estimatedTime: 2,
1125
+ docsUrl: "https://docs.datalyr.com/sdks/vue",
1126
+ postInstallSteps: [
1127
+ "Add your workspace ID to .env",
1128
+ "Call datalyr.init() in main.ts",
1129
+ "Track events with datalyr.track()"
1130
+ ]
1131
+ };
1132
+ var NUXT_CONFIG = {
1133
+ id: "nuxt",
1134
+ name: "Nuxt",
1135
+ detectPackage: "nuxt",
1136
+ sdks: ["@datalyr/web", "@datalyr/api"],
1137
+ envVars: [
1138
+ {
1139
+ key: "NUXT_PUBLIC_DATALYR_WORKSPACE_ID",
1140
+ description: "Your Datalyr workspace ID",
1141
+ isPublic: true
1142
+ },
1143
+ {
1144
+ key: "DATALYR_API_KEY",
1145
+ description: "Server-side API key",
1146
+ isPublic: false
1147
+ }
1148
+ ],
1149
+ estimatedTime: 3,
1150
+ docsUrl: "https://docs.datalyr.com/sdks/nuxt",
1151
+ postInstallSteps: [
1152
+ "Add your workspace ID to .env",
1153
+ "Create a Nuxt plugin for initialization",
1154
+ "Track events with datalyr.track()"
1155
+ ]
1156
+ };
1157
+ var REMIX_CONFIG = {
1158
+ id: "remix",
1159
+ name: "Remix",
1160
+ detectPackage: "@remix-run/react",
1161
+ sdks: ["@datalyr/web", "@datalyr/api"],
1162
+ envVars: [
1163
+ {
1164
+ key: "DATALYR_WORKSPACE_ID",
1165
+ description: "Your Datalyr workspace ID",
1166
+ isPublic: true
1167
+ },
1168
+ {
1169
+ key: "DATALYR_API_KEY",
1170
+ description: "Server-side API key",
1171
+ isPublic: false
1172
+ }
1173
+ ],
1174
+ estimatedTime: 3,
1175
+ docsUrl: "https://docs.datalyr.com/sdks/remix",
1176
+ postInstallSteps: [
1177
+ "Add your workspace ID to .env",
1178
+ "Initialize in root.tsx",
1179
+ "Track events with datalyr.track()"
1180
+ ]
1181
+ };
1182
+ var ASTRO_CONFIG = {
1183
+ id: "astro",
1184
+ name: "Astro",
1185
+ detectPackage: "astro",
1186
+ sdks: ["@datalyr/web"],
1187
+ envVars: [
1188
+ {
1189
+ key: "PUBLIC_DATALYR_WORKSPACE_ID",
1190
+ description: "Your Datalyr workspace ID",
1191
+ isPublic: true
1192
+ }
1193
+ ],
1194
+ estimatedTime: 2,
1195
+ docsUrl: "https://docs.datalyr.com/sdks/astro",
1196
+ postInstallSteps: [
1197
+ "Add your workspace ID to .env",
1198
+ "Add script to Layout component",
1199
+ "Track events with datalyr.track()"
1200
+ ]
1201
+ };
1202
+ var IOS_CONFIG = {
1203
+ id: "ios",
1204
+ name: "iOS (Swift)",
1205
+ detectPackage: "",
1206
+ // Detected by Package.swift or .xcodeproj
1207
+ sdks: ["DatalyrSDK"],
1208
+ envVars: [
1209
+ {
1210
+ key: "DATALYR_WORKSPACE_ID",
1211
+ description: "Your Datalyr workspace ID (in Info.plist)",
1212
+ isPublic: false
1213
+ },
1214
+ {
1215
+ key: "DATALYR_API_KEY",
1216
+ description: "Your Datalyr API key (in Info.plist)",
1217
+ isPublic: false
1218
+ }
1219
+ ],
1220
+ estimatedTime: 5,
1221
+ docsUrl: "https://docs.datalyr.com/sdks/ios",
1222
+ postInstallSteps: [
1223
+ "Add DatalyrSDK via Swift Package Manager",
1224
+ "Add keys to Info.plist",
1225
+ "Call DatalyrConfig.initialize() in App init"
1226
+ ]
1227
+ };
1228
+ var SVELTE_CONFIG = {
1229
+ id: "svelte",
1230
+ name: "Svelte",
1231
+ detectPackage: "svelte",
1232
+ sdks: ["@datalyr/web"],
1233
+ envVars: [
1234
+ {
1235
+ key: "VITE_DATALYR_WORKSPACE_ID",
1236
+ description: "Your Datalyr workspace ID",
1237
+ isPublic: true
1238
+ }
1239
+ ],
1240
+ estimatedTime: 2,
1241
+ docsUrl: "https://docs.datalyr.com/sdks/svelte",
1242
+ postInstallSteps: [
1243
+ "Add your workspace ID to .env",
1244
+ "Call initDatalyr() in App.svelte"
1245
+ ]
1246
+ };
1247
+ var FRAMEWORK_CONFIGS = {
1248
+ nextjs: NEXTJS_CONFIG,
1249
+ react: REACT_CONFIG,
1250
+ "react-vite": REACT_CONFIG,
1251
+ svelte: SVELTE_CONFIG,
1252
+ sveltekit: SVELTEKIT_CONFIG,
1253
+ vue: VUE_CONFIG,
1254
+ nuxt: NUXT_CONFIG,
1255
+ remix: REMIX_CONFIG,
1256
+ astro: ASTRO_CONFIG,
1257
+ "react-native": REACT_NATIVE_CONFIG,
1258
+ expo: EXPO_CONFIG,
1259
+ ios: IOS_CONFIG,
1260
+ node: NODE_CONFIG,
1261
+ unknown: void 0
1262
+ };
1263
+ function getSdksForFramework(framework) {
1264
+ const config = FRAMEWORK_CONFIGS[framework];
1265
+ if (!config) {
1266
+ return ["@datalyr/web"];
1267
+ }
1268
+ return config.sdks;
1269
+ }
1270
+
1271
+ // src/agent/events/suggestions.ts
1272
+ var BUSINESS_TYPES = [
1273
+ {
1274
+ id: "saas",
1275
+ label: "SaaS / Web App",
1276
+ description: "Subscription or freemium product",
1277
+ hint: "Trials, signups, features, upgrades"
1278
+ },
1279
+ {
1280
+ id: "mobile_app",
1281
+ label: "Mobile App",
1282
+ description: "Consumer or B2B mobile app",
1283
+ hint: "Installs, in-app events, attribution"
1284
+ },
1285
+ {
1286
+ id: "lead_gen",
1287
+ label: "Lead Gen / Marketing",
1288
+ description: "Capture leads and conversions",
1289
+ hint: "Funnels, forms, demos, signups"
1290
+ },
1291
+ {
1292
+ id: "b2b",
1293
+ label: "B2B Product",
1294
+ description: "Business software",
1295
+ hint: "Demos, trials, team invites, usage"
1296
+ },
1297
+ {
1298
+ id: "agency",
1299
+ label: "Agency / Client Work",
1300
+ description: "Building for clients",
1301
+ hint: "Multi-site, white-label tracking"
1302
+ }
1303
+ ];
1304
+ var EVENT_SUGGESTIONS = {
1305
+ saas: [
1306
+ {
1307
+ name: "signed_up",
1308
+ description: "User created an account",
1309
+ properties: ["method", "referrer"],
1310
+ priority: "high"
1311
+ },
1312
+ {
1313
+ name: "trial_started",
1314
+ description: "User started a free trial",
1315
+ properties: ["plan"],
1316
+ priority: "high"
1317
+ },
1318
+ {
1319
+ name: "subscription_started",
1320
+ description: "User converted to paid",
1321
+ properties: ["plan", "value", "currency"],
1322
+ priority: "high"
1323
+ },
1324
+ {
1325
+ name: "onboarding_completed",
1326
+ description: "User finished onboarding",
1327
+ properties: ["steps_completed"],
1328
+ priority: "medium"
1329
+ }
1330
+ ],
1331
+ mobile_app: [
1332
+ {
1333
+ name: "signed_up",
1334
+ description: "User created an account",
1335
+ properties: ["method", "referrer"],
1336
+ priority: "high"
1337
+ },
1338
+ {
1339
+ name: "onboarding_started",
1340
+ description: "User started onboarding flow",
1341
+ properties: ["source"],
1342
+ priority: "high"
1343
+ },
1344
+ {
1345
+ name: "onboarding_completed",
1346
+ description: "User finished onboarding",
1347
+ properties: ["steps_completed"],
1348
+ priority: "high"
1349
+ },
1350
+ {
1351
+ name: "paywall_viewed",
1352
+ description: "User saw the paywall/pricing",
1353
+ properties: ["source", "paywall_id"],
1354
+ priority: "high"
1355
+ },
1356
+ {
1357
+ name: "trial_started",
1358
+ description: "User started a free trial",
1359
+ properties: ["plan"],
1360
+ priority: "high"
1361
+ },
1362
+ {
1363
+ name: "subscription_started",
1364
+ description: "User converted to paid subscription",
1365
+ properties: ["plan", "value", "currency"],
1366
+ priority: "high"
1367
+ },
1368
+ {
1369
+ name: "purchase",
1370
+ description: "User made in-app purchase",
1371
+ properties: ["product_id", "value", "currency"],
1372
+ priority: "medium"
1373
+ }
1374
+ ],
1375
+ lead_gen: [
1376
+ {
1377
+ name: "lead",
1378
+ description: "User submitted their info",
1379
+ properties: ["form_name", "source"],
1380
+ priority: "high"
1381
+ },
1382
+ {
1383
+ name: "signed_up",
1384
+ description: "User signed up / joined waitlist",
1385
+ properties: ["method", "referrer"],
1386
+ priority: "high"
1387
+ },
1388
+ {
1389
+ name: "demo_requested",
1390
+ description: "User requested a demo",
1391
+ properties: ["product"],
1392
+ priority: "high"
1393
+ },
1394
+ {
1395
+ name: "cta_clicked",
1396
+ description: "User clicked a CTA",
1397
+ properties: ["button_name", "page"],
1398
+ priority: "medium"
1399
+ }
1400
+ ],
1401
+ b2b: [
1402
+ {
1403
+ name: "signed_up",
1404
+ description: "User created an account",
1405
+ properties: ["method", "company_size"],
1406
+ priority: "high"
1407
+ },
1408
+ {
1409
+ name: "demo_requested",
1410
+ description: "User requested a demo",
1411
+ properties: ["product", "company_size"],
1412
+ priority: "high"
1413
+ },
1414
+ {
1415
+ name: "trial_started",
1416
+ description: "User started a trial",
1417
+ properties: ["plan"],
1418
+ priority: "high"
1419
+ },
1420
+ {
1421
+ name: "lead",
1422
+ description: "Lead captured",
1423
+ properties: ["source", "company_size"],
1424
+ priority: "high"
1425
+ }
1426
+ ],
1427
+ agency: [
1428
+ {
1429
+ name: "lead",
1430
+ description: "Lead captured",
1431
+ properties: ["form_name", "client_id"],
1432
+ priority: "high"
1433
+ },
1434
+ {
1435
+ name: "conversion",
1436
+ description: "Conversion event",
1437
+ properties: ["conversion_type", "value", "client_id"],
1438
+ priority: "high"
1439
+ },
1440
+ {
1441
+ name: "form_submitted",
1442
+ description: "User submitted a form",
1443
+ properties: ["form_name", "client_id"],
1444
+ priority: "high"
1445
+ },
1446
+ {
1447
+ name: "cta_clicked",
1448
+ description: "User clicked a CTA",
1449
+ properties: ["button_name", "client_id"],
1450
+ priority: "medium"
1451
+ }
1452
+ ]
1453
+ };
1454
+ function getEventSuggestions(businessType) {
1455
+ return EVENT_SUGGESTIONS[businessType] || EVENT_SUGGESTIONS.saas;
1456
+ }
1457
+ function formatEventDescription(event) {
1458
+ const propsDisplay = event.properties.length > 0 ? ` (${event.properties.join(", ")})` : "";
1459
+ return `${event.description}${propsDisplay}`;
1460
+ }
1461
+ function buildEventSelectOptions(events, includeAllOption = true) {
1462
+ const options = [];
1463
+ if (includeAllOption) {
1464
+ const highPriorityCount = events.filter((e) => e.priority === "high").length;
1465
+ options.push({
1466
+ value: "__all_recommended__",
1467
+ label: "All recommended events",
1468
+ hint: `Select all ${highPriorityCount} high-priority events`
1469
+ });
1470
+ }
1471
+ for (const event of events) {
1472
+ const priorityBadge = event.priority === "high" ? "[recommended] " : "";
1473
+ options.push({
1474
+ value: event.name,
1475
+ label: event.name,
1476
+ hint: `${priorityBadge}${formatEventDescription(event)}`
1477
+ });
1478
+ }
1479
+ return options;
1480
+ }
1481
+ function resolveSelectedEvents(selectedValues, allEvents) {
1482
+ if (selectedValues.includes("__all_recommended__")) {
1483
+ return allEvents.filter((e) => e.priority === "high");
1484
+ }
1485
+ return allEvents.filter((e) => selectedValues.includes(e.name));
1486
+ }
1487
+
1488
+ // src/agent/platform/config.ts
1489
+ var PLATFORM_TYPES = [
1490
+ {
1491
+ id: "web",
1492
+ label: "Web Only",
1493
+ description: "Website or web application",
1494
+ hint: "React, Next.js, Svelte, etc."
1495
+ },
1496
+ {
1497
+ id: "mobile",
1498
+ label: "Mobile Only",
1499
+ description: "iOS or Android app",
1500
+ hint: "React Native, Expo, Swift"
1501
+ },
1502
+ {
1503
+ id: "both",
1504
+ label: "Web + Mobile",
1505
+ description: "Both web and mobile apps",
1506
+ hint: "Full cross-platform attribution"
1507
+ }
1508
+ ];
1509
+ var AD_PLATFORMS = [
1510
+ {
1511
+ id: "meta",
1512
+ label: "Meta Ads (Facebook/Instagram)",
1513
+ description: "Track conversions from Meta campaigns",
1514
+ requiresServerSide: true,
1515
+ configKeys: ["META_PIXEL_ID", "META_ACCESS_TOKEN", "META_APP_ID"]
1516
+ },
1517
+ {
1518
+ id: "google",
1519
+ label: "Google Ads",
1520
+ description: "Track conversions from Google campaigns",
1521
+ requiresServerSide: true,
1522
+ configKeys: ["GOOGLE_ADS_CUSTOMER_ID", "GOOGLE_ADS_CONVERSION_ID"]
1523
+ },
1524
+ {
1525
+ id: "tiktok",
1526
+ label: "TikTok Ads",
1527
+ description: "Track conversions from TikTok campaigns",
1528
+ requiresServerSide: true,
1529
+ configKeys: ["TIKTOK_PIXEL_ID", "TIKTOK_ACCESS_TOKEN", "TIKTOK_APP_ID"]
1530
+ },
1531
+ {
1532
+ id: "apple_search_ads",
1533
+ label: "Apple Search Ads",
1534
+ description: "Track iOS app install attribution",
1535
+ requiresServerSide: false,
1536
+ configKeys: []
1537
+ },
1538
+ {
1539
+ id: "none",
1540
+ label: "No ad platforms",
1541
+ description: "Skip ad platform setup",
1542
+ requiresServerSide: false,
1543
+ configKeys: []
1544
+ }
1545
+ ];
1546
+ function getSdksForPlatform(platformType, adPlatforms) {
1547
+ const sdks = [];
1548
+ if (platformType === "web" || platformType === "both") {
1549
+ sdks.push("@datalyr/web");
1550
+ }
1551
+ if (platformType === "mobile" || platformType === "both") {
1552
+ sdks.push("@datalyr/react-native");
1553
+ }
1554
+ const needsServerSide = adPlatforms.some(
1555
+ (p2) => AD_PLATFORMS.find((ap) => ap.id === p2)?.requiresServerSide
1556
+ );
1557
+ if (needsServerSide) {
1558
+ sdks.push("@datalyr/api");
1559
+ }
1560
+ return sdks;
1561
+ }
1562
+ function getAttributionEvents(config) {
1563
+ const events = [];
1564
+ if (config.platformType === "mobile" || config.platformType === "both") {
1565
+ events.push({
1566
+ name: "app_install",
1567
+ description: "User installed the app (auto-tracked)",
1568
+ properties: ["attribution_source", "campaign_id", "ad_group_id"],
1569
+ serverSide: false
1570
+ });
1571
+ events.push({
1572
+ name: "app_open",
1573
+ description: "User opened the app",
1574
+ properties: ["source", "deep_link_url", "is_first_open"],
1575
+ serverSide: false
1576
+ });
1577
+ }
1578
+ if (config.enableDeepLinking) {
1579
+ events.push({
1580
+ name: "deep_link_opened",
1581
+ description: "User opened a deep link",
1582
+ properties: ["url", "source", "campaign"],
1583
+ serverSide: false
1584
+ });
1585
+ events.push({
1586
+ name: "deferred_deep_link",
1587
+ description: "User installed via deferred deep link",
1588
+ properties: ["original_url", "install_time", "open_time"],
1589
+ serverSide: false
1590
+ });
1591
+ }
1592
+ if (config.enableServerSideConversions) {
1593
+ if (config.adPlatforms.includes("meta")) {
1594
+ events.push({
1595
+ name: "purchase",
1596
+ description: "Purchase event for Meta CAPI",
1597
+ properties: ["value", "currency", "email", "phone", "fbc", "fbp"],
1598
+ serverSide: true
1599
+ });
1600
+ events.push({
1601
+ name: "lead",
1602
+ description: "Lead event for Meta CAPI",
1603
+ properties: ["email", "phone", "fbc", "fbp"],
1604
+ serverSide: true
1605
+ });
1606
+ }
1607
+ if (config.adPlatforms.includes("tiktok")) {
1608
+ events.push({
1609
+ name: "complete_payment",
1610
+ description: "Purchase event for TikTok Events API",
1611
+ properties: ["value", "currency", "email", "phone", "ttclid"],
1612
+ serverSide: true
1613
+ });
1614
+ }
1615
+ if (config.adPlatforms.includes("google")) {
1616
+ events.push({
1617
+ name: "conversion",
1618
+ description: "Conversion event for Google Ads",
1619
+ properties: ["value", "currency", "email", "phone", "gclid"],
1620
+ serverSide: true
1621
+ });
1622
+ }
1623
+ }
1624
+ return events;
1625
+ }
1626
+ function getPlatformPostInstallSteps(config) {
1627
+ const steps = [];
1628
+ if (config.platformType === "mobile" || config.platformType === "both") {
1629
+ steps.push("Run `cd ios && pod install` to install native dependencies");
1630
+ if (config.enableDeepLinking) {
1631
+ steps.push("Configure URL schemes in Xcode/Android manifest for deep linking");
1632
+ steps.push("Set up Associated Domains (iOS) or App Links (Android)");
1633
+ }
1634
+ }
1635
+ for (const platformId of config.adPlatforms) {
1636
+ const platform = AD_PLATFORMS.find((p2) => p2.id === platformId);
1637
+ if (!platform || platformId === "none") continue;
1638
+ steps.push(`Add ${platform.label} credentials to your environment variables`);
1639
+ if (platform.requiresServerSide) {
1640
+ steps.push(`Set up server-side endpoint for ${platform.label} conversions`);
1641
+ }
1642
+ if (config.platformType !== "web") {
1643
+ if (platformId === "meta") {
1644
+ steps.push("Add Meta App ID to Info.plist (iOS) and AndroidManifest.xml");
1645
+ }
1646
+ if (platformId === "tiktok") {
1647
+ steps.push("Add TikTok App ID to Info.plist (iOS) and AndroidManifest.xml");
1648
+ }
1649
+ if (platformId === "apple_search_ads") {
1650
+ steps.push("Enable AdServices.framework in Xcode");
1651
+ }
1652
+ }
1653
+ }
1654
+ return steps;
1655
+ }
1656
+
1657
+ // src/generators/ai-context.ts
1658
+ function inferPropertyType(propName) {
1659
+ const lowerName = propName.toLowerCase();
1660
+ if (["value", "price", "amount", "total", "quantity", "count", "steps_completed"].some((n) => lowerName.includes(n))) {
1661
+ return "number";
1662
+ }
1663
+ if (["is_", "has_", "enable", "disable"].some((n) => lowerName.startsWith(n) || lowerName.includes(n))) {
1664
+ return "boolean";
1665
+ }
1666
+ if (lowerName === "currency") {
1667
+ return 'string; // ISO 4217 code (e.g., "USD", "EUR")';
1668
+ }
1669
+ if (lowerName === "email") {
1670
+ return "string; // User email for attribution matching";
1671
+ }
1672
+ if (lowerName === "phone") {
1673
+ return "string; // User phone for attribution matching";
1674
+ }
1675
+ if (lowerName.endsWith("_id") || lowerName === "id") {
1676
+ return "string";
1677
+ }
1678
+ return "string";
1679
+ }
1680
+ function generateAIContextDoc(params) {
1681
+ const {
1682
+ workspaceName,
1683
+ workspaceId,
1684
+ framework,
1685
+ platformType,
1686
+ adPlatforms,
1687
+ businessType,
1688
+ selectedEvents,
1689
+ sdks,
1690
+ enableServerSideConversions,
1691
+ enableContainer = true
1692
+ } = params;
1693
+ const eventsList = selectedEvents.map((e) => {
1694
+ const propsWithTypes = e.properties.map((p2) => {
1695
+ const type = inferPropertyType(p2);
1696
+ return ` ${p2}: ${type};`;
1697
+ }).join("\n");
1698
+ return `### \`${e.name}\`
1699
+ ${e.description}
1700
+
1701
+ \`\`\`typescript
1702
+ datalyr.track('${e.name}', {
1703
+ ${propsWithTypes}
1704
+ });
1705
+ \`\`\``;
1706
+ }).join("\n\n");
1707
+ const trackingExamples = selectedEvents.slice(0, 3).map((e) => {
1708
+ const props = e.properties.slice(0, 2).map((p2) => `${p2}: '...'`).join(", ");
1709
+ return `datalyr.track('${e.name}', { ${props} });`;
1710
+ }).join("\n");
1711
+ const serverSideExample = enableServerSideConversions ? `
1712
+ ## Server-Side Tracking (CAPI)
1713
+
1714
+ For conversion events that need server-side tracking:
1715
+
1716
+ \`\`\`typescript
1717
+ // In your API route or server action
1718
+ import { datalyr } from '@/lib/datalyr.server';
1719
+
1720
+ await datalyr.track({
1721
+ event: 'purchase',
1722
+ userId: user.id,
1723
+ properties: {
1724
+ value: 99.00,
1725
+ currency: 'USD',
1726
+ email: user.email, // Required for ad platform matching
1727
+ phone: user.phone, // Optional, improves match rate
1728
+ },
1729
+ });
1730
+ \`\`\`
1731
+ ` : "";
1732
+ const adPlatformsSection = adPlatforms.length > 0 ? `
1733
+ ## Attribution Setup
1734
+
1735
+ Ad platforms configured: ${adPlatforms.join(", ")}
1736
+
1737
+ For proper attribution matching, include user data in conversion events:
1738
+ - \`email\`: User's email (hashed automatically)
1739
+ - \`phone\`: User's phone number (hashed automatically)
1740
+ ${adPlatforms.includes("meta") ? "- `fbc`, `fbp`: Meta click/browser IDs (from cookies)" : ""}
1741
+ ${adPlatforms.includes("google") ? "- `gclid`: Google click ID (from URL params)" : ""}
1742
+ ${adPlatforms.includes("tiktok") ? "- `ttclid`: TikTok click ID (from URL params)" : ""}
1743
+ ` : "";
1744
+ const containerSection = enableContainer ? `
1745
+ ## Container Scripts
1746
+
1747
+ Container scripts are **enabled**. Third-party pixels (Meta Pixel, Google Tag, TikTok Pixel)
1748
+ are managed through the Datalyr dashboard, not in code.
1749
+
1750
+ Configure pixels at: https://app.datalyr.com/dashboard/${workspaceId}/settings/pixels
1751
+
1752
+ Benefits:
1753
+ - Add/remove pixels without code changes
1754
+ - Events tracked with \`datalyr.track()\` auto-fire to all configured pixels
1755
+ - Server-side fallback for ad blockers
1756
+ ` : "";
1757
+ return `# Datalyr Analytics Setup
1758
+
1759
+ This file documents the Datalyr analytics configuration for AI coding assistants.
1760
+
1761
+ ## Project Info
1762
+
1763
+ - **Workspace**: ${workspaceName}
1764
+ - **Workspace ID**: \`${workspaceId}\`
1765
+ - **Framework**: ${framework}
1766
+ - **Platform**: ${platformType}
1767
+ - **Business Type**: ${businessType}
1768
+ - **SDKs**: ${sdks.join(", ")}
1769
+
1770
+ ## Quick Start
1771
+
1772
+ ${framework === "ios" ? `\`\`\`swift
1773
+ import DatalyrSDK
1774
+
1775
+ // Track an event
1776
+ Datalyr.shared.track("event_name", properties: ["key": "value"])
1777
+ \`\`\`` : `\`\`\`typescript
1778
+ import datalyr from '@datalyr/web';
1779
+
1780
+ // Track an event
1781
+ ${trackingExamples}
1782
+ \`\`\``}
1783
+
1784
+ ## Events to Track
1785
+
1786
+ ${eventsList}
1787
+
1788
+ ## Usage Patterns
1789
+
1790
+ ### Identify Users
1791
+
1792
+ \`\`\`typescript
1793
+ // After user signs in
1794
+ datalyr.identify(user.id, {
1795
+ email: user.email,
1796
+ name: user.name,
1797
+ plan: user.plan,
1798
+ });
1799
+ \`\`\`
1800
+
1801
+ ### Track Events
1802
+
1803
+ \`\`\`typescript
1804
+ // Track any event with properties
1805
+ datalyr.track('event_name', {
1806
+ property: 'value',
1807
+ value: 100,
1808
+ });
1809
+ \`\`\`
1810
+ ${serverSideExample}${adPlatformsSection}${containerSection}
1811
+ ## Dashboard
1812
+
1813
+ View your analytics at:
1814
+ https://app.datalyr.com/dashboard/${workspaceId}/events
1815
+
1816
+ ## Documentation
1817
+
1818
+ - SDK Docs: https://docs.datalyr.com/sdks/${framework}
1819
+ - API Reference: https://docs.datalyr.com/api
1820
+ `;
1821
+ }
1822
+
1823
+ // src/agent/runner.ts
1824
+ var import_promises = require("fs/promises");
1825
+ var import_path3 = require("path");
1826
+ var import_fs2 = require("fs");
1827
+ var import_child_process = require("child_process");
1828
+ var import_util = require("util");
1829
+ var import_glob2 = require("glob");
1830
+ var execAsync = (0, import_util.promisify)(import_child_process.exec);
1831
+ var LLM_GATEWAY_URL = process.env.DATALYR_LLM_GATEWAY || "https://wizard.datalyr.com";
1832
+ async function runAgentWizard(_config, options = {}) {
1833
+ const cwd = options.cwd || process.cwd();
1834
+ p.intro(import_chalk.default.cyan("Datalyr AI Wizard"));
1835
+ const useAI = await p.confirm({
1836
+ message: "This wizard uses AI to analyze your project and install Datalyr. Continue?",
1837
+ initialValue: true
1838
+ });
1839
+ if (p.isCancel(useAI) || !useAI) {
1840
+ p.cancel("Wizard cancelled");
1841
+ return { success: false, error: "User cancelled" };
1842
+ }
1843
+ let apiKey = options.apiKey;
1844
+ let workspace = null;
1845
+ let keyAttempts = 0;
1846
+ const maxAttempts = 3;
1847
+ while (!workspace && keyAttempts < maxAttempts) {
1848
+ keyAttempts++;
1849
+ if (!apiKey) {
1850
+ if (keyAttempts === 1) {
1851
+ p.note(
1852
+ `To get your API key:
1853
+ 1. Go to ${import_chalk.default.cyan("https://app.datalyr.com/settings/api")}
1854
+ 2. Create a new API key (or copy existing)
1855
+ 3. Paste it below`,
1856
+ "API Key Required"
1857
+ );
1858
+ }
1859
+ const keyInput = await p.text({
1860
+ message: "Enter your Datalyr API key (starts with dk_):",
1861
+ placeholder: "dk_live_...",
1862
+ validate: (value) => {
1863
+ if (!value) return "API key is required";
1864
+ if (!value.startsWith("dk_")) return "API key must start with dk_";
1865
+ if (value.length < 20) return "API key seems too short";
1866
+ return void 0;
1867
+ }
1868
+ });
1869
+ if (p.isCancel(keyInput)) {
1870
+ p.cancel("Wizard cancelled");
1871
+ return { success: false, error: "User cancelled" };
1872
+ }
1873
+ apiKey = keyInput;
1874
+ }
1875
+ const validateSpinner = p.spinner();
1876
+ validateSpinner.start("Validating API key...");
1877
+ try {
1878
+ const defaultWorkspace = await validateApiKey(apiKey);
1879
+ const allWorkspaces = await fetchWorkspaces(apiKey);
1880
+ if (allWorkspaces.length > 1) {
1881
+ validateSpinner.stop("API key validated");
1882
+ const workspaceChoice = await p.select({
1883
+ message: "Select a workspace to configure:",
1884
+ options: allWorkspaces.map((w) => ({
1885
+ value: w.id,
1886
+ label: w.name,
1887
+ hint: w.domain || void 0
1888
+ }))
1889
+ });
1890
+ if (p.isCancel(workspaceChoice)) {
1891
+ p.cancel("Wizard cancelled");
1892
+ return { success: false, error: "User cancelled" };
1893
+ }
1894
+ const selectedWorkspace = allWorkspaces.find((w) => w.id === workspaceChoice);
1895
+ if (selectedWorkspace) {
1896
+ workspace = {
1897
+ id: selectedWorkspace.id,
1898
+ name: selectedWorkspace.name,
1899
+ timezone: null,
1900
+ domain: selectedWorkspace.domain
1901
+ };
1902
+ } else {
1903
+ workspace = defaultWorkspace;
1904
+ }
1905
+ p.log.info(`Selected workspace: ${import_chalk.default.cyan(workspace.name)}`);
1906
+ } else {
1907
+ workspace = defaultWorkspace;
1908
+ validateSpinner.stop(`Workspace: ${import_chalk.default.cyan(workspace.name)}`);
1909
+ }
1910
+ } catch (error) {
1911
+ validateSpinner.stop(import_chalk.default.red("Invalid API key"));
1912
+ const errorMessage = error instanceof Error ? error.message : "Failed to validate API key";
1913
+ p.log.error(errorMessage);
1914
+ apiKey = void 0;
1915
+ if (keyAttempts < maxAttempts) {
1916
+ const action = await p.select({
1917
+ message: "What would you like to do?",
1918
+ options: [
1919
+ { value: "retry", label: "Try a different API key" },
1920
+ { value: "signup", label: "Create a free account", hint: "Opens browser" },
1921
+ { value: "exit", label: "Exit wizard" }
1922
+ ]
1923
+ });
1924
+ if (p.isCancel(action) || action === "exit") {
1925
+ p.cancel("Wizard cancelled");
1926
+ return { success: false, error: "User cancelled" };
1927
+ }
1928
+ if (action === "signup") {
1929
+ const signupUrl = "https://app.datalyr.com/signup?ref=wizard";
1930
+ p.log.info(`Opening ${import_chalk.default.cyan(signupUrl)} in your browser...`);
1931
+ try {
1932
+ const { exec: exec2 } = await import("child_process");
1933
+ const { promisify: promisify2 } = await import("util");
1934
+ const execAsync2 = promisify2(exec2);
1935
+ const platform = process.platform;
1936
+ const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
1937
+ await execAsync2(`${cmd} "${signupUrl}"`);
1938
+ p.log.success("Browser opened! Create your account and come back with your API key.");
1939
+ await p.text({
1940
+ message: "Press Enter when you have your API key ready..."
1941
+ });
1942
+ } catch {
1943
+ p.log.warn(`Please visit: ${import_chalk.default.cyan(signupUrl)}`);
1944
+ }
1945
+ }
1946
+ }
1947
+ }
1948
+ }
1949
+ if (!workspace || !apiKey) {
1950
+ p.log.error(`Failed to validate API key after ${maxAttempts} attempts.`);
1951
+ p.log.info(`Get help at ${import_chalk.default.cyan("https://docs.datalyr.com/getting-started")}`);
1952
+ return { success: false, error: "API key validation failed" };
1953
+ }
1954
+ const validatedApiKey = apiKey;
1955
+ const detectSpinner = p.spinner();
1956
+ detectSpinner.start("Analyzing your project...");
1957
+ let detection = await detectFramework(cwd);
1958
+ let framework = detection.framework;
1959
+ if (framework === "unknown" && !options.framework) {
1960
+ detectSpinner.stop("Could not auto-detect framework");
1961
+ const frameworkChoice = await p.select({
1962
+ message: "Select your framework:",
1963
+ options: [
1964
+ { value: "nextjs", label: "Next.js" },
1965
+ { value: "react", label: "React" },
1966
+ { value: "react-vite", label: "React (Vite)" },
1967
+ { value: "svelte", label: "Svelte" },
1968
+ { value: "sveltekit", label: "SvelteKit" },
1969
+ { value: "react-native", label: "React Native" },
1970
+ { value: "expo", label: "Expo" },
1971
+ { value: "ios", label: "iOS (Swift)" },
1972
+ { value: "node", label: "Node.js" }
1973
+ ]
1974
+ });
1975
+ if (p.isCancel(frameworkChoice)) {
1976
+ p.cancel("Wizard cancelled");
1977
+ return { success: false, error: "User cancelled" };
1978
+ }
1979
+ framework = frameworkChoice;
1980
+ } else if (options.framework) {
1981
+ framework = options.framework;
1982
+ }
1983
+ detectSpinner.stop(`Detected: ${import_chalk.default.cyan(getFrameworkDisplayName(framework))}`);
1984
+ if (detection.framework !== framework) {
1985
+ detection = { ...detection, framework, sdks: getSdksForFramework(framework) };
1986
+ }
1987
+ const isMobileFramework = ["react-native", "expo", "ios"].includes(framework);
1988
+ const isWebFramework = ["nextjs", "react", "react-vite", "svelte", "sveltekit"].includes(framework);
1989
+ let platformType = "web";
1990
+ if (isMobileFramework) {
1991
+ platformType = "mobile";
1992
+ } else if (!isMobileFramework && !isWebFramework) {
1993
+ const platformChoice = await p.select({
1994
+ message: "What platforms are you targeting?",
1995
+ options: PLATFORM_TYPES.map((type) => ({
1996
+ value: type.id,
1997
+ label: type.label,
1998
+ hint: type.hint
1999
+ }))
2000
+ });
2001
+ if (p.isCancel(platformChoice)) {
2002
+ p.cancel("Wizard cancelled");
2003
+ return { success: false, error: "User cancelled" };
2004
+ }
2005
+ platformType = platformChoice;
2006
+ }
2007
+ const runningAds = await p.confirm({
2008
+ message: "Are you running paid ads (Meta, Google, TikTok)?",
2009
+ initialValue: false
2010
+ });
2011
+ let adPlatforms = [];
2012
+ let platformConfig = {
2013
+ platformType,
2014
+ adPlatforms: [],
2015
+ enableDeepLinking: false,
2016
+ enableServerSideConversions: false
2017
+ };
2018
+ if (!p.isCancel(runningAds) && runningAds) {
2019
+ const adPlatformChoices = await p.multiselect({
2020
+ message: "Select your ad platforms:",
2021
+ options: AD_PLATFORMS.filter((p2) => p2.id !== "none").map((platform) => ({
2022
+ value: platform.id,
2023
+ label: platform.label,
2024
+ hint: platform.description
2025
+ })),
2026
+ required: false
2027
+ });
2028
+ if (!p.isCancel(adPlatformChoices)) {
2029
+ adPlatforms = adPlatformChoices;
2030
+ if (adPlatforms.length > 0) {
2031
+ const serverSide = await p.confirm({
2032
+ message: "Enable server-side conversion tracking (CAPI)?",
2033
+ initialValue: true
2034
+ });
2035
+ platformConfig.enableServerSideConversions = !p.isCancel(serverSide) && serverSide;
2036
+ }
2037
+ }
2038
+ }
2039
+ if (platformType === "mobile" || platformType === "both") {
2040
+ const deepLinking = await p.confirm({
2041
+ message: "Set up deep linking / deferred deep links?",
2042
+ initialValue: adPlatforms.length > 0
2043
+ });
2044
+ platformConfig.enableDeepLinking = !p.isCancel(deepLinking) && deepLinking;
2045
+ }
2046
+ platformConfig.adPlatforms = adPlatforms;
2047
+ let enableContainer = options.enableContainer;
2048
+ if (enableContainer === void 0 && platformType !== "mobile") {
2049
+ const containerChoice = await p.confirm({
2050
+ message: "Manage third-party pixels through Datalyr dashboard?",
2051
+ initialValue: adPlatforms.length > 0
2052
+ // Default yes if using ad platforms
2053
+ });
2054
+ if (!p.isCancel(containerChoice)) {
2055
+ enableContainer = containerChoice;
2056
+ if (containerChoice) {
2057
+ p.note(
2058
+ `Container scripts let you:
2059
+ ${import_chalk.default.green("\u2022")} Add/remove Meta, Google, TikTok pixels without code
2060
+ ${import_chalk.default.green("\u2022")} Auto-fire datalyr.track() events to all pixels
2061
+ ${import_chalk.default.green("\u2022")} Inject custom scripts (Hotjar, Intercom, etc.)`,
2062
+ "Container Scripts"
2063
+ );
2064
+ }
2065
+ }
2066
+ }
2067
+ const containerEnabled = enableContainer !== false;
2068
+ const businessTypeChoice = await p.select({
2069
+ message: "What type of app are you building?",
2070
+ options: BUSINESS_TYPES.map((type) => ({
2071
+ value: type.id,
2072
+ label: type.label,
2073
+ hint: type.hint
2074
+ }))
2075
+ });
2076
+ if (p.isCancel(businessTypeChoice)) {
2077
+ p.cancel("Wizard cancelled");
2078
+ return { success: false, error: "User cancelled" };
2079
+ }
2080
+ const selectedBusinessType = businessTypeChoice;
2081
+ const suggestedEvents = getEventSuggestions(selectedBusinessType);
2082
+ const attributionEvents = getAttributionEvents(platformConfig);
2083
+ const autoTrackedEvents = platformType === "mobile" || platformType === "both" ? ["page_view / screen_view", "session_start", "app_install (mobile)", "attribution data"] : ["page_view", "session_start", "referrer", "UTM parameters"];
2084
+ p.note(
2085
+ `${import_chalk.default.bold("Auto-tracked (no code needed):")}
2086
+ ` + autoTrackedEvents.map((e) => ` ${import_chalk.default.dim("\u2022")} ${e}`).join("\n") + `
2087
+
2088
+ ${import_chalk.default.bold("High-value events")} are the key actions that matter for your business:
2089
+ ${import_chalk.default.dim("\u2022")} Conversions (signups, purchases, leads)
2090
+ ${import_chalk.default.dim("\u2022")} Engagement (feature usage, key interactions)
2091
+ ${import_chalk.default.dim("\u2022")} Revenue (transactions, subscriptions)`,
2092
+ "Event Tracking"
2093
+ );
2094
+ const allEvents = [...suggestedEvents, ...attributionEvents.map((e) => ({
2095
+ name: e.name,
2096
+ description: e.description,
2097
+ properties: e.properties,
2098
+ priority: "high"
2099
+ }))];
2100
+ const uniqueEvents = allEvents.filter(
2101
+ (event, index, self) => index === self.findIndex((e) => e.name === event.name)
2102
+ );
2103
+ const eventOptions = buildEventSelectOptions(uniqueEvents, true);
2104
+ const businessTypeLabel = BUSINESS_TYPES.find((b) => b.id === selectedBusinessType)?.label || selectedBusinessType;
2105
+ p.note(
2106
+ `Based on your ${import_chalk.default.cyan(businessTypeLabel)} app, we suggest these events:
2107
+
2108
+ ` + uniqueEvents.filter((e) => e.priority === "high").map((e) => ` ${import_chalk.default.green("\u2022")} ${import_chalk.default.bold(e.name)}: ${formatEventDescription(e)}`).join("\n"),
2109
+ "Suggested Events"
2110
+ );
2111
+ const selectedEventNames = await p.multiselect({
2112
+ message: "Select events to track:",
2113
+ options: eventOptions,
2114
+ initialValues: ["__all_recommended__"],
2115
+ required: false
2116
+ });
2117
+ let selectedEvents = p.isCancel(selectedEventNames) ? uniqueEvents.filter((e) => e.priority === "high") : resolveSelectedEvents(selectedEventNames, uniqueEvents);
2118
+ const addCustom = await p.confirm({
2119
+ message: "Want to add any custom events specific to your app?",
2120
+ initialValue: false
2121
+ });
2122
+ if (!p.isCancel(addCustom) && addCustom) {
2123
+ const customEventInput = await p.text({
2124
+ message: "Enter custom event names (comma-separated):",
2125
+ placeholder: "checkout_completed, feature_clicked, ..."
2126
+ });
2127
+ if (!p.isCancel(customEventInput) && customEventInput) {
2128
+ const customEvents = customEventInput.split(",").map((e) => e.trim()).filter((e) => e.length > 0).map((name) => ({
2129
+ name,
2130
+ description: "Custom event",
2131
+ properties: ["value", "context"],
2132
+ priority: "high"
2133
+ }));
2134
+ selectedEvents = [...selectedEvents, ...customEvents];
2135
+ }
2136
+ }
2137
+ if (selectedEvents.length > 0) {
2138
+ const eventList = selectedEvents.slice(0, 6).map((e) => ` ${import_chalk.default.green("\u2022")} ${import_chalk.default.bold(e.name)}`).join("\n");
2139
+ const moreCount = selectedEvents.length > 6 ? ` (+${selectedEvents.length - 6} more)` : "";
2140
+ p.note(
2141
+ `Events you'll track:
2142
+
2143
+ ${eventList}${moreCount}`,
2144
+ "Your Events"
2145
+ );
2146
+ }
2147
+ const platformSdks = getSdksForPlatform(platformType, adPlatforms);
2148
+ const allSdks = [.../* @__PURE__ */ new Set([...detection.sdks, ...platformSdks])];
2149
+ const platformSteps = getPlatformPostInstallSteps(platformConfig);
2150
+ p.note(
2151
+ `The wizard will:
2152
+ ${import_chalk.default.green("\u2022")} Install ${import_chalk.default.cyan(allSdks.join(", "))}
2153
+ ${import_chalk.default.green("\u2022")} Create initialization code
2154
+ ${import_chalk.default.green("\u2022")} Configure environment variables
2155
+ ${import_chalk.default.green("\u2022")} Set up workspace: ${import_chalk.default.cyan(workspace.name)}
2156
+ ${adPlatforms.length > 0 ? `${import_chalk.default.green("\u2022")} Configure attribution for: ${import_chalk.default.cyan(adPlatforms.join(", "))}` : ""}
2157
+ ${containerEnabled && platformType !== "mobile" ? `${import_chalk.default.green("\u2022")} Enable container scripts for pixel management` : ""}`,
2158
+ "Installation Plan"
2159
+ );
2160
+ const proceed = await p.confirm({
2161
+ message: "Proceed with installation?",
2162
+ initialValue: true
2163
+ });
2164
+ if (p.isCancel(proceed) || !proceed) {
2165
+ p.cancel("Wizard cancelled");
2166
+ return { success: false, error: "User cancelled" };
2167
+ }
2168
+ const agentSpinner = p.spinner();
2169
+ agentSpinner.start("AI agent is working...");
2170
+ try {
2171
+ const result = await executeAgent({
2172
+ framework,
2173
+ apiKey: validatedApiKey,
2174
+ cwd,
2175
+ docs: getFrameworkDocs(framework, validatedApiKey),
2176
+ debug: options.debug,
2177
+ enableContainer: containerEnabled,
2178
+ workspaceId: workspace.id
2179
+ });
2180
+ if (result.success) {
2181
+ agentSpinner.stop(import_chalk.default.green("Installation complete!"));
2182
+ if (!options.skipVerification) {
2183
+ const verifyResult = await verifyInstallation(validatedApiKey, workspace.id);
2184
+ if (verifyResult.success) {
2185
+ p.log.success("SDK verified and ready to track events!");
2186
+ } else {
2187
+ p.log.warn("Could not verify SDK installation. This may be normal if events haven't been sent yet.");
2188
+ p.log.info(`Check your dashboard at: ${import_chalk.default.cyan(`https://app.datalyr.com/dashboard/${workspace.id}/events`)}`);
2189
+ }
2190
+ }
2191
+ try {
2192
+ const aiContextDoc = generateAIContextDoc({
2193
+ workspaceName: workspace.name,
2194
+ workspaceId: workspace.id,
2195
+ framework,
2196
+ platformType,
2197
+ adPlatforms,
2198
+ businessType: selectedBusinessType,
2199
+ selectedEvents,
2200
+ sdks: allSdks,
2201
+ enableServerSideConversions: platformConfig.enableServerSideConversions,
2202
+ enableContainer: containerEnabled
2203
+ });
2204
+ await (0, import_promises.writeFile)((0, import_path3.join)(cwd, ".datalyr.md"), aiContextDoc);
2205
+ p.log.success("Created .datalyr.md for AI coding assistants");
2206
+ } catch {
2207
+ }
2208
+ const eventExamples = selectedEvents.slice(0, 3).map(
2209
+ (e) => `datalyr.track('${e.name}', { ${e.properties.slice(0, 2).map((p2) => `${p2}: '...'`).join(", ")} })`
2210
+ ).join("\n ");
2211
+ const stepsText = platformSteps.length > 0 ? `
2212
+
2213
+ ${import_chalk.default.bold("Next steps:")}
2214
+ ${platformSteps.map((s) => ` ${import_chalk.default.yellow("\u2192")} ${s}`).join("\n")}` : "";
2215
+ p.note(
2216
+ `Your workspace "${workspace.name}" is ready!
2217
+
2218
+ ${import_chalk.default.bold("Start tracking events:")}
2219
+ ${eventExamples || "datalyr.track('event_name', { property: 'value' })"}
2220
+
2221
+ ${import_chalk.default.bold("View your data:")}
2222
+ ${import_chalk.default.cyan(`https://app.datalyr.com/dashboard/${workspace.id}/events`)}${stepsText}`,
2223
+ "Success!"
2224
+ );
2225
+ p.outro(import_chalk.default.green("Datalyr is ready to use!"));
2226
+ } else {
2227
+ agentSpinner.stop(import_chalk.default.red("Installation failed"));
2228
+ p.log.error(result.error || "Unknown error");
2229
+ }
2230
+ return result;
2231
+ } catch (error) {
2232
+ agentSpinner.stop(import_chalk.default.red("Agent error"));
2233
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2234
+ p.log.error(errorMessage);
2235
+ return { success: false, error: errorMessage };
2236
+ }
2237
+ }
2238
+ async function executeAgent(params) {
2239
+ const { framework, apiKey, cwd, docs, debug, enableContainer = true, workspaceId } = params;
2240
+ const initialMessage = buildIntegrationPrompt(framework, apiKey, docs, enableContainer, workspaceId);
2241
+ const messages = [
2242
+ { role: "user", content: initialMessage }
2243
+ ];
2244
+ const filesModified = [];
2245
+ const maxIterations = 20;
2246
+ let iterations = 0;
2247
+ while (iterations < maxIterations) {
2248
+ iterations++;
2249
+ if (debug) {
2250
+ console.log(`[DEBUG] Agent iteration ${iterations}`);
2251
+ }
2252
+ try {
2253
+ const controller = new AbortController();
2254
+ const timeoutId = setTimeout(() => controller.abort(), 6e4);
2255
+ const response = await fetch(`${LLM_GATEWAY_URL}/agent`, {
2256
+ method: "POST",
2257
+ headers: {
2258
+ "Content-Type": "application/json"
2259
+ },
2260
+ body: JSON.stringify({
2261
+ messages,
2262
+ system: buildSystemPrompt()
2263
+ }),
2264
+ signal: controller.signal
2265
+ });
2266
+ clearTimeout(timeoutId);
2267
+ if (!response.ok) {
2268
+ const error = await response.text();
2269
+ return { success: false, error: `Gateway error: ${error}` };
2270
+ }
2271
+ const claudeResponse = await response.json();
2272
+ if (debug) {
2273
+ console.log("[DEBUG] Response:", JSON.stringify(claudeResponse, null, 2));
2274
+ }
2275
+ messages.push({ role: "assistant", content: claudeResponse.content });
2276
+ if (claudeResponse.stop_reason === "end_turn") {
2277
+ const taskComplete = claudeResponse.content.find(
2278
+ (block) => block.type === "tool_use" && block.name === "task_complete"
2279
+ );
2280
+ if (taskComplete) {
2281
+ const input = taskComplete.input;
2282
+ return {
2283
+ success: input.success,
2284
+ message: input.summary,
2285
+ filesModified: input.files_modified || filesModified
2286
+ };
2287
+ }
2288
+ if (filesModified.length > 0) {
2289
+ return {
2290
+ success: false,
2291
+ message: "Agent stopped unexpectedly. Files were modified but installation may be incomplete.",
2292
+ error: "Agent did not call task_complete. Please verify the installation manually.",
2293
+ filesModified
2294
+ };
2295
+ }
2296
+ return {
2297
+ success: false,
2298
+ error: "Agent stopped without completing the installation task."
2299
+ };
2300
+ }
2301
+ if (claudeResponse.stop_reason === "tool_use") {
2302
+ const toolUseBlocks = claudeResponse.content.filter(
2303
+ (block) => block.type === "tool_use"
2304
+ );
2305
+ const toolResults = [];
2306
+ for (const toolUse of toolUseBlocks) {
2307
+ if (toolUse.name === "task_complete") {
2308
+ const input = toolUse.input;
2309
+ return {
2310
+ success: input.success,
2311
+ message: input.summary,
2312
+ filesModified: input.files_modified || filesModified
2313
+ };
2314
+ }
2315
+ const result = await executeTool(toolUse, cwd, debug);
2316
+ if (toolUse.name === "write_file" && !result.is_error) {
2317
+ const path3 = toolUse.input.path;
2318
+ if (!filesModified.includes(path3)) {
2319
+ filesModified.push(path3);
2320
+ }
2321
+ }
2322
+ toolResults.push({
2323
+ type: "tool_result",
2324
+ tool_use_id: toolUse.id,
2325
+ content: result.content,
2326
+ is_error: result.is_error
2327
+ });
2328
+ }
2329
+ messages.push({ role: "user", content: toolResults });
2330
+ }
2331
+ } catch (error) {
2332
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2333
+ if (debug) {
2334
+ console.error("[DEBUG] Agent error:", error);
2335
+ }
2336
+ return { success: false, error: `Agent error: ${errorMessage}` };
2337
+ }
2338
+ }
2339
+ return { success: false, error: "Agent exceeded maximum iterations" };
2340
+ }
2341
+ function isPathSafe(basePath, targetPath) {
2342
+ const resolvedBase = (0, import_path3.resolve)(basePath);
2343
+ const resolvedTarget = (0, import_path3.resolve)(basePath, targetPath);
2344
+ return resolvedTarget.startsWith(resolvedBase + "/") || resolvedTarget === resolvedBase;
2345
+ }
2346
+ async function executeTool(toolUse, cwd, debug) {
2347
+ const { name, input } = toolUse;
2348
+ if (debug) {
2349
+ console.log(`[DEBUG] Executing tool: ${name}`, input);
2350
+ }
2351
+ try {
2352
+ switch (name) {
2353
+ case "read_file": {
2354
+ if (typeof input.path !== "string" || !input.path.trim()) {
2355
+ return { content: "Error: path must be a non-empty string", is_error: true };
2356
+ }
2357
+ const path3 = input.path.trim();
2358
+ if (!isPathSafe(cwd, path3)) {
2359
+ return { content: "Error: path traversal detected - access denied", is_error: true };
2360
+ }
2361
+ const fullPath = (0, import_path3.join)(cwd, path3);
2362
+ const content = await (0, import_promises.readFile)(fullPath, "utf-8");
2363
+ return { content };
2364
+ }
2365
+ case "write_file": {
2366
+ if (typeof input.path !== "string" || !input.path.trim()) {
2367
+ return { content: "Error: path must be a non-empty string", is_error: true };
2368
+ }
2369
+ if (typeof input.content !== "string") {
2370
+ return { content: "Error: content must be a string", is_error: true };
2371
+ }
2372
+ const path3 = input.path.trim();
2373
+ const content = input.content;
2374
+ if (!isPathSafe(cwd, path3)) {
2375
+ return { content: "Error: path traversal detected - access denied", is_error: true };
2376
+ }
2377
+ const fullPath = (0, import_path3.join)(cwd, path3);
2378
+ const dir = (0, import_path3.dirname)(fullPath);
2379
+ if (!(0, import_fs2.existsSync)(dir)) {
2380
+ await (0, import_promises.mkdir)(dir, { recursive: true });
2381
+ }
2382
+ await (0, import_promises.writeFile)(fullPath, content, "utf-8");
2383
+ return { content: `Successfully wrote ${path3}` };
2384
+ }
2385
+ case "run_command": {
2386
+ if (typeof input.command !== "string" || !input.command.trim()) {
2387
+ return { content: "Error: command must be a non-empty string", is_error: true };
2388
+ }
2389
+ const command = input.command.trim();
2390
+ const validation = validateBashCommand(command);
2391
+ if (!validation.allowed) {
2392
+ return {
2393
+ content: `Command blocked: ${validation.reason}`,
2394
+ is_error: true
2395
+ };
2396
+ }
2397
+ const { stdout, stderr } = await execAsync(command, { cwd, timeout: 12e4 });
2398
+ return { content: stdout || stderr || "Command completed successfully" };
2399
+ }
2400
+ case "list_files": {
2401
+ if (typeof input.path !== "string") {
2402
+ return { content: "Error: path must be a string", is_error: true };
2403
+ }
2404
+ const path3 = input.path.trim() || ".";
2405
+ const pattern = typeof input.pattern === "string" ? input.pattern : "*";
2406
+ if (!isPathSafe(cwd, path3)) {
2407
+ return { content: "Error: path traversal detected - access denied", is_error: true };
2408
+ }
2409
+ const fullPath = (0, import_path3.join)(cwd, path3);
2410
+ const files = await (0, import_glob2.glob)(pattern, {
2411
+ cwd: fullPath,
2412
+ nodir: false,
2413
+ maxDepth: 3
2414
+ });
2415
+ return { content: files.slice(0, 100).join("\n") };
2416
+ }
2417
+ default:
2418
+ return { content: `Unknown tool: ${name}`, is_error: true };
2419
+ }
2420
+ } catch (error) {
2421
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2422
+ return { content: `Error: ${errorMessage}`, is_error: true };
2423
+ }
2424
+ }
2425
+ function buildIntegrationPrompt(framework, apiKey, docs, enableContainer, workspaceId) {
2426
+ const containerConfig = enableContainer ? " enableContainer: true, // Load third-party pixels from Datalyr dashboard" : " enableContainer: false, // Container scripts disabled";
2427
+ 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";
2428
+ return `Install Datalyr analytics into this ${getFrameworkDisplayName(framework)} project.
2429
+
2430
+ ## Workspace ID
2431
+ ${workspaceId}
2432
+
2433
+ ## API Key (for server-side only)
2434
+ ${apiKey}
2435
+
2436
+ ## Integration Documentation
2437
+ ${docs}
2438
+
2439
+ ## SDK Configuration
2440
+ When initializing the SDK, use these options:
2441
+ \`\`\`typescript
2442
+ datalyr.init({
2443
+ workspaceId: process.env.${envVarName},
2444
+ ${containerConfig}
2445
+ });
2446
+ \`\`\`
2447
+
2448
+ ## Environment Variable
2449
+ Add to .env.local (or .env):
2450
+ ${envVarName}=${workspaceId}
2451
+
2452
+ ## Your Task
2453
+
2454
+ 1. Read package.json to understand the project
2455
+ 2. Install the SDK: run the appropriate install command
2456
+ 3. Create initialization code following the documentation
2457
+ 4. Add the environment variable to .env.local or .env
2458
+ 5. Call task_complete when done
2459
+
2460
+ Start by reading package.json.`;
2461
+ }
2462
+ async function validateApiKey(apiKey) {
2463
+ const controller = new AbortController();
2464
+ const timeoutId = setTimeout(() => controller.abort(), 3e4);
2465
+ const response = await fetch(`${LLM_GATEWAY_URL}/validate-key`, {
2466
+ method: "POST",
2467
+ headers: {
2468
+ "Content-Type": "application/json"
2469
+ },
2470
+ body: JSON.stringify({ apiKey }),
2471
+ signal: controller.signal
2472
+ });
2473
+ clearTimeout(timeoutId);
2474
+ if (!response.ok) {
2475
+ const error = await response.json();
2476
+ throw new Error(error.error || "Failed to validate API key");
2477
+ }
2478
+ const result = await response.json();
2479
+ if (!result.success || !result.workspace) {
2480
+ throw new Error(result.error || "Invalid API key");
2481
+ }
2482
+ return result.workspace;
2483
+ }
2484
+ async function fetchWorkspaces(apiKey) {
2485
+ const controller = new AbortController();
2486
+ const timeoutId = setTimeout(() => controller.abort(), 3e4);
2487
+ const response = await fetch(`${LLM_GATEWAY_URL}/workspaces`, {
2488
+ method: "POST",
2489
+ headers: {
2490
+ "Content-Type": "application/json"
2491
+ },
2492
+ body: JSON.stringify({ apiKey }),
2493
+ signal: controller.signal
2494
+ });
2495
+ clearTimeout(timeoutId);
2496
+ if (!response.ok) {
2497
+ return [];
2498
+ }
2499
+ const result = await response.json();
2500
+ if (!result.success || !result.workspaces) {
2501
+ return [];
2502
+ }
2503
+ return result.workspaces;
2504
+ }
2505
+ async function verifyInstallation(apiKey, workspaceId) {
2506
+ try {
2507
+ const controller = new AbortController();
2508
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
2509
+ const response = await fetch(`${LLM_GATEWAY_URL}/verify`, {
2510
+ method: "POST",
2511
+ headers: {
2512
+ "Content-Type": "application/json"
2513
+ },
2514
+ body: JSON.stringify({ apiKey, workspaceId }),
2515
+ signal: controller.signal
2516
+ });
2517
+ clearTimeout(timeoutId);
2518
+ if (!response.ok) {
2519
+ return { success: false };
2520
+ }
2521
+ const result = await response.json();
2522
+ return result;
2523
+ } catch {
2524
+ return { success: false };
2525
+ }
2526
+ }
2527
+
2528
+ // bin/wizard.ts
2529
+ var validFrameworks = [
2530
+ "nextjs",
2531
+ "react",
2532
+ "react-vite",
2533
+ "svelte",
2534
+ "sveltekit",
2535
+ "react-native",
2536
+ "expo",
2537
+ "ios",
2538
+ "node"
2539
+ ];
2540
+ async function main() {
2541
+ const argv = await (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).scriptName("datalyr-wizard").usage("$0 [options]").option("api-key", {
2542
+ alias: "k",
2543
+ type: "string",
2544
+ description: "Your Datalyr API key (starts with dk_)"
2545
+ }).option("framework", {
2546
+ alias: "f",
2547
+ type: "string",
2548
+ choices: validFrameworks,
2549
+ description: "Framework to configure"
2550
+ }).option("skip-verification", {
2551
+ type: "boolean",
2552
+ default: false,
2553
+ description: "Skip post-install verification"
2554
+ }).option("enable-container", {
2555
+ type: "boolean",
2556
+ description: "Enable/disable container scripts for pixel management"
2557
+ }).option("debug", {
2558
+ type: "boolean",
2559
+ default: false,
2560
+ description: "Enable debug output"
2561
+ }).option("cwd", {
2562
+ type: "string",
2563
+ description: "Working directory (defaults to current)"
2564
+ }).example("$0", "Run interactive wizard").example("$0 --api-key dk_xxx", "Pre-fill API key").example("$0 --framework nextjs", "Force Next.js configuration").help().alias("help", "h").version().alias("version", "v").parse();
2565
+ const options = {
2566
+ apiKey: argv["api-key"],
2567
+ framework: argv.framework,
2568
+ skipVerification: argv["skip-verification"],
2569
+ enableContainer: argv["enable-container"],
2570
+ debug: argv.debug,
2571
+ cwd: argv.cwd
2572
+ };
2573
+ const result = await runAgentWizard(NEXTJS_CONFIG, options);
2574
+ process.exit(result.success ? 0 : 1);
2575
+ }
2576
+ main().catch((error) => {
2577
+ console.error("Fatal error:", error);
2578
+ process.exit(1);
2579
+ });
2580
+ //# sourceMappingURL=wizard.js.map