UNPKG

humanbehavior-js

Version:

SDK for HumanBehavior session and event recording

1 lines 194 kB
{"version":3,"file":"index.mjs","sources":["../src/core/install-wizard.ts","../src/ai/ai-install-wizard.ts","../src/services/remote-ai-service.ts","../src/ai/manual-framework-wizard.ts","../src/cli/ai-auto-install.ts","../src/cli/auto-install.ts","../src/services/centralized-ai-service.ts"],"sourcesContent":["/**\n * HumanBehavior SDK Auto-Installation Wizard\n * \n * This wizard automatically detects the user's framework and modifies their codebase\n * to integrate the SDK with minimal user intervention.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { execSync } from 'child_process';\n\nexport interface FrameworkInfo {\n name: string;\n type: 'react' | 'vue' | 'angular' | 'svelte' | 'nextjs' | 'nuxt' | 'remix' | 'vanilla' | 'astro' | 'gatsby' | 'node' | 'auto';\n bundler?: 'vite' | 'webpack' | 'esbuild' | 'rollup';\n packageManager?: 'npm' | 'yarn' | 'pnpm';\n hasTypeScript?: boolean;\n hasRouter?: boolean;\n projectRoot?: string;\n version?: string;\n majorVersion?: number;\n features?: {\n hasReact18?: boolean;\n hasVue3?: boolean;\n hasNuxt3?: boolean;\n hasAngularStandalone?: boolean;\n hasNextAppRouter?: boolean;\n hasSvelteKit?: boolean;\n };\n}\n\nexport interface CodeModification {\n filePath: string;\n action: 'create' | 'modify' | 'append';\n content: string;\n description: string;\n}\n\nexport interface InstallationResult {\n success: boolean;\n framework: FrameworkInfo;\n modifications: CodeModification[];\n errors: string[];\n nextSteps: string[];\n}\n\nexport class AutoInstallationWizard {\n protected apiKey: string;\n protected projectRoot: string;\n protected framework: FrameworkInfo | null = null;\n private manualNotes: string[] = [];\n\n constructor(apiKey: string, projectRoot: string = process.cwd()) {\n this.apiKey = apiKey;\n this.projectRoot = projectRoot;\n }\n\n /**\n * Simple version comparison utility\n */\n private compareVersions(version1: string, version2: string): number {\n const v1Parts = version1.split('.').map(Number);\n const v2Parts = version2.split('.').map(Number);\n \n for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {\n const v1 = v1Parts[i] || 0;\n const v2 = v2Parts[i] || 0;\n if (v1 > v2) return 1;\n if (v1 < v2) return -1;\n }\n return 0;\n }\n\n private isVersionGte(version: string, target: string): boolean {\n return this.compareVersions(version, target) >= 0;\n }\n\n private getMajorVersion(version: string): number {\n return parseInt(version.split('.')[0]) || 0;\n }\n\n /**\n * Main installation method - detects framework and auto-installs\n */\n async install(): Promise<InstallationResult> {\n try {\n // Step 1: Detect framework\n this.framework = await this.detectFramework();\n \n // Step 2: Install package\n await this.installPackage();\n \n // Step 3: Generate and apply code modifications\n const modifications = await this.generateModifications();\n await this.applyModifications(modifications);\n \n // Step 4: Generate next steps\n const nextSteps = this.generateNextSteps();\n \n return {\n success: true,\n framework: this.framework,\n modifications,\n errors: [],\n nextSteps\n };\n } catch (error) {\n return {\n success: false,\n framework: this.framework || { name: 'unknown', type: 'vanilla' },\n modifications: [],\n errors: [error instanceof Error ? error.message : 'Unknown error'],\n nextSteps: []\n };\n }\n }\n\n /**\n * Detect the current framework and project setup\n */\n public async detectFramework(): Promise<FrameworkInfo> {\n const packageJsonPath = path.join(this.projectRoot, 'package.json');\n \n if (!fs.existsSync(packageJsonPath)) {\n return {\n name: 'vanilla',\n type: 'vanilla',\n projectRoot: this.projectRoot\n };\n }\n\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));\n const dependencies = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies\n };\n\n // Detect framework with version information\n let framework: FrameworkInfo = {\n name: 'vanilla',\n type: 'vanilla',\n projectRoot: this.projectRoot,\n features: {}\n };\n\n if (dependencies.nuxt) {\n const nuxtVersion = dependencies.nuxt;\n const isNuxt3 = this.isVersionGte(nuxtVersion, '3.0.0');\n \n framework = {\n name: 'nuxt',\n type: 'nuxt',\n version: nuxtVersion,\n majorVersion: this.getMajorVersion(nuxtVersion),\n hasTypeScript: !!dependencies.typescript,\n hasRouter: true,\n projectRoot: this.projectRoot,\n features: {\n hasNuxt3: isNuxt3\n }\n };\n } else if (dependencies.next) {\n const nextVersion = dependencies.next;\n const isNext13 = this.isVersionGte(nextVersion, '13.0.0');\n \n framework = {\n name: 'nextjs',\n type: 'nextjs',\n version: nextVersion,\n majorVersion: this.getMajorVersion(nextVersion),\n hasTypeScript: !!dependencies.typescript || !!dependencies['@types/node'],\n hasRouter: true,\n projectRoot: this.projectRoot,\n features: {\n hasNextAppRouter: isNext13\n }\n };\n } else if (dependencies['@remix-run/react'] || dependencies['@remix-run/dev']) {\n const remixVersion = dependencies['@remix-run/react'] || dependencies['@remix-run/dev'];\n framework = {\n name: 'remix',\n type: 'remix',\n version: remixVersion,\n majorVersion: this.getMajorVersion(remixVersion),\n hasTypeScript: !!dependencies.typescript || !!dependencies['@types/react'],\n hasRouter: true,\n projectRoot: this.projectRoot,\n features: {}\n };\n } else if (dependencies.react) {\n const reactVersion = dependencies.react;\n const isReact18 = this.isVersionGte(reactVersion, '18.0.0');\n \n framework = {\n name: 'react',\n type: 'react',\n version: reactVersion,\n majorVersion: this.getMajorVersion(reactVersion),\n hasTypeScript: !!dependencies.typescript || !!dependencies['@types/react'],\n hasRouter: !!dependencies['react-router-dom'] || !!dependencies['react-router'],\n projectRoot: this.projectRoot,\n features: {\n hasReact18: isReact18\n }\n };\n } else if (dependencies.vue) {\n const vueVersion = dependencies.vue;\n const isVue3 = this.isVersionGte(vueVersion, '3.0.0');\n \n framework = {\n name: 'vue',\n type: 'vue',\n version: vueVersion,\n majorVersion: this.getMajorVersion(vueVersion),\n hasTypeScript: !!dependencies.typescript || !!dependencies['@vue/cli-service'],\n hasRouter: !!dependencies['vue-router'],\n projectRoot: this.projectRoot,\n features: {\n hasVue3: isVue3\n }\n };\n } else if (dependencies['@angular/core']) {\n const angularVersion = dependencies['@angular/core'];\n const isAngular17 = this.isVersionGte(angularVersion, '17.0.0');\n \n framework = {\n name: 'angular',\n type: 'angular',\n version: angularVersion,\n majorVersion: this.getMajorVersion(angularVersion),\n hasTypeScript: true,\n hasRouter: true,\n projectRoot: this.projectRoot,\n features: {\n hasAngularStandalone: isAngular17\n }\n };\n } else if (dependencies.svelte) {\n const svelteVersion = dependencies.svelte;\n const isSvelteKit = !!dependencies['@sveltejs/kit'];\n \n framework = {\n name: 'svelte',\n type: 'svelte',\n version: svelteVersion,\n majorVersion: this.getMajorVersion(svelteVersion),\n hasTypeScript: !!dependencies.typescript || !!dependencies['svelte-check'],\n hasRouter: !!dependencies['svelte-routing'] || !!dependencies['@sveltejs/kit'],\n projectRoot: this.projectRoot,\n features: {\n hasSvelteKit: isSvelteKit\n }\n };\n } else if (dependencies.astro) {\n const astroVersion = dependencies.astro;\n framework = {\n name: 'astro',\n type: 'astro',\n version: astroVersion,\n majorVersion: this.getMajorVersion(astroVersion),\n hasTypeScript: !!dependencies.typescript || !!dependencies['@astrojs/ts-plugin'],\n hasRouter: true,\n projectRoot: this.projectRoot,\n features: {}\n };\n } else if (dependencies.gatsby) {\n const gatsbyVersion = dependencies.gatsby;\n framework = {\n name: 'gatsby',\n type: 'gatsby',\n version: gatsbyVersion,\n majorVersion: this.getMajorVersion(gatsbyVersion),\n hasTypeScript: !!dependencies.typescript || !!dependencies['@types/react'],\n hasRouter: true,\n projectRoot: this.projectRoot,\n features: {}\n };\n }\n\n // Detect bundler\n if (dependencies.vite) {\n framework.bundler = 'vite';\n } else if (dependencies.webpack) {\n framework.bundler = 'webpack';\n } else if (dependencies.esbuild) {\n framework.bundler = 'esbuild';\n } else if (dependencies.rollup) {\n framework.bundler = 'rollup';\n }\n\n // Detect package manager\n if (fs.existsSync(path.join(this.projectRoot, 'yarn.lock'))) {\n framework.packageManager = 'yarn';\n } else if (fs.existsSync(path.join(this.projectRoot, 'pnpm-lock.yaml'))) {\n framework.packageManager = 'pnpm';\n } else {\n framework.packageManager = 'npm';\n }\n\n return framework;\n }\n\n /**\n * Install the SDK package with latest version range\n */\n protected async installPackage(): Promise<void> {\n \n // Build base command with latest version range\n let command = this.framework?.packageManager === 'yarn' \n ? 'yarn add humanbehavior-js@latest'\n : this.framework?.packageManager === 'pnpm'\n ? 'pnpm add humanbehavior-js@latest'\n : 'npm install humanbehavior-js@latest';\n \n // Add legacy peer deps flag for npm to handle dependency conflicts\n if (this.framework?.packageManager !== 'yarn' && this.framework?.packageManager !== 'pnpm') {\n command += ' --legacy-peer-deps';\n }\n\n try {\n execSync(command, { cwd: this.projectRoot, stdio: 'inherit' });\n } catch (error) {\n throw new Error(`Failed to install humanbehavior-js: ${error}`);\n }\n }\n\n /**\n * Generate code modifications based on framework\n */\n protected async generateModifications(): Promise<CodeModification[]> {\n const modifications: CodeModification[] = [];\n\n switch (this.framework?.type) {\n case 'react':\n modifications.push(...await this.generateReactModifications());\n break;\n case 'nextjs':\n modifications.push(...await this.generateNextJSModifications());\n break;\n case 'nuxt':\n modifications.push(...await this.generateNuxtModifications());\n break;\n case 'astro':\n modifications.push(...await this.generateAstroModifications());\n break;\n case 'gatsby':\n modifications.push(...await this.generateGatsbyModifications());\n break;\n case 'remix':\n modifications.push(...await this.generateRemixModifications());\n break;\n case 'vue':\n modifications.push(...await this.generateVueModifications());\n break;\n case 'angular':\n modifications.push(...await this.generateAngularModifications());\n break;\n case 'svelte':\n modifications.push(...await this.generateSvelteModifications());\n break;\n default:\n modifications.push(...await this.generateVanillaModifications());\n }\n\n return modifications;\n }\n\n /**\n * Generate React-specific modifications\n */\n private async generateReactModifications(): Promise<CodeModification[]> {\n const modifications: CodeModification[] = [];\n \n // Find main App component or index file\n const appFile = this.findReactAppFile();\n if (appFile) {\n const content = fs.readFileSync(appFile, 'utf8');\n const modifiedContent = this.injectReactProvider(content, appFile);\n \n modifications.push({\n filePath: appFile,\n action: 'modify',\n content: modifiedContent,\n description: 'Added HumanBehaviorProvider to React app'\n });\n }\n\n // Create or append to environment file\n modifications.push(this.createEnvironmentModification(this.framework!));\n\n return modifications;\n }\n\n /**\n * Generate Next.js-specific modifications\n */\n private async generateNextJSModifications(): Promise<CodeModification[]> {\n const modifications: CodeModification[] = [];\n \n // Check for App Router - try both with and without src directory\n const appLayoutFileWithSrc = path.join(this.projectRoot, 'src', 'app', 'layout.tsx');\n const appLayoutFile = path.join(this.projectRoot, 'app', 'layout.tsx');\n const pagesLayoutFileWithSrc = path.join(this.projectRoot, 'src', 'pages', '_app.tsx');\n const pagesLayoutFile = path.join(this.projectRoot, 'pages', '_app.tsx');\n \n // Determine which layout file exists and set paths accordingly\n let actualAppLayoutFile: string | null = null;\n let providersFilePath: string | null = null;\n \n if (fs.existsSync(appLayoutFileWithSrc)) {\n actualAppLayoutFile = appLayoutFileWithSrc;\n providersFilePath = path.join(this.projectRoot, 'src', 'app', 'providers.tsx');\n } else if (fs.existsSync(appLayoutFile)) {\n actualAppLayoutFile = appLayoutFile;\n providersFilePath = path.join(this.projectRoot, 'app', 'providers.tsx');\n }\n \n if (actualAppLayoutFile) {\n // Create providers.tsx file for App Router\n modifications.push({\n filePath: providersFilePath!,\n action: 'create',\n content: `'use client';\n\nimport { HumanBehaviorProvider } from 'humanbehavior-js/react';\n\nexport function Providers({ children }: { children: React.ReactNode }) {\n return (\n <HumanBehaviorProvider apiKey={process.env.NEXT_PUBLIC_HUMANBEHAVIOR_API_KEY}>\n {children}\n </HumanBehaviorProvider>\n );\n}`,\n description: 'Created providers.tsx file for Next.js App Router'\n });\n\n // Modify layout.tsx to use the provider\n const content = fs.readFileSync(actualAppLayoutFile, 'utf8');\n const modifiedContent = this.injectNextJSAppRouter(content);\n \n modifications.push({\n filePath: actualAppLayoutFile,\n action: 'modify',\n content: modifiedContent,\n description: 'Added Providers wrapper to Next.js App Router layout'\n });\n } else if (fs.existsSync(pagesLayoutFileWithSrc) || fs.existsSync(pagesLayoutFile)) {\n const actualPagesLayoutFile = fs.existsSync(pagesLayoutFileWithSrc) ? pagesLayoutFileWithSrc : pagesLayoutFile;\n const providersPath = fs.existsSync(pagesLayoutFileWithSrc) \n ? path.join(this.projectRoot, 'src', 'components', 'providers.tsx')\n : path.join(this.projectRoot, 'components', 'providers.tsx');\n const importPath = fs.existsSync(pagesLayoutFileWithSrc) \n ? '../components/providers'\n : './components/providers';\n \n // Create providers.tsx file for Pages Router\n modifications.push({\n filePath: providersPath,\n action: 'create',\n content: `'use client';\n\nimport { HumanBehaviorProvider } from 'humanbehavior-js/react';\n\nexport function Providers({ children }: { children: React.ReactNode }) {\n return (\n <HumanBehaviorProvider apiKey={process.env.NEXT_PUBLIC_HUMANBEHAVIOR_API_KEY}>\n {children}\n </HumanBehaviorProvider>\n );\n}`,\n description: 'Created providers.tsx file for Pages Router'\n });\n\n // Modify _app.tsx to use the provider\n const content = fs.readFileSync(actualPagesLayoutFile, 'utf8');\n const modifiedContent = this.injectNextJSPagesRouter(content, importPath);\n \n modifications.push({\n filePath: actualPagesLayoutFile,\n action: 'modify',\n content: modifiedContent,\n description: 'Added Providers wrapper to Next.js Pages Router'\n });\n }\n\n // Create or append to environment file\n modifications.push(this.createEnvironmentModification(this.framework!));\n\n return modifications;\n }\n\n /**\n * Generate Astro-specific modifications\n */\n private async generateAstroModifications(): Promise<CodeModification[]> {\n const modifications: CodeModification[] = [];\n\n // Create Astro component for HumanBehavior\n const astroComponentPath = path.join(this.projectRoot, 'src', 'components', 'HumanBehavior.astro');\n const astroComponentContent = `---\n// This component will only run on the client side\n---\n\n<script>\n import { HumanBehaviorTracker } from 'humanbehavior-js';\n const apiKey = import.meta.env.PUBLIC_HUMANBEHAVIOR_API_KEY;\n if (apiKey) {\n HumanBehaviorTracker.init(apiKey);\n }\n</script>`;\n\n modifications.push({\n filePath: astroComponentPath,\n action: 'create',\n content: astroComponentContent,\n description: 'Created Astro component for HumanBehavior SDK'\n });\n\n // Find and update layout file\n const layoutFiles = [\n path.join(this.projectRoot, 'src', 'layouts', 'Layout.astro'),\n path.join(this.projectRoot, 'src', 'layouts', 'layout.astro'),\n path.join(this.projectRoot, 'src', 'layouts', 'BaseLayout.astro')\n ];\n\n let layoutFile = null;\n for (const file of layoutFiles) {\n if (fs.existsSync(file)) {\n layoutFile = file;\n break;\n }\n }\n\n if (layoutFile) {\n const content = fs.readFileSync(layoutFile, 'utf8');\n const modifiedContent = this.injectAstroLayout(content);\n \n modifications.push({\n filePath: layoutFile,\n action: 'modify',\n content: modifiedContent,\n description: 'Added HumanBehavior component to Astro layout'\n });\n }\n\n // Add environment variable\n modifications.push(this.createEnvironmentModification(this.framework!));\n\n return modifications;\n }\n\n /**\n * Generate Nuxt-specific modifications\n */\n private async generateNuxtModifications(): Promise<CodeModification[]> {\n const modifications: CodeModification[] = [];\n \n // Create plugin file for Nuxt (in app directory)\n const pluginFile = path.join(this.projectRoot, 'app', 'plugins', 'humanbehavior.client.ts');\n modifications.push({\n filePath: pluginFile,\n action: 'create',\n content: `import { HumanBehaviorTracker } from 'humanbehavior-js';\n\nexport default defineNuxtPlugin(() => {\n const config = useRuntimeConfig();\n if (typeof window !== 'undefined') {\n const apiKey = config.public.humanBehaviorApiKey;\n if (apiKey) {\n HumanBehaviorTracker.init(apiKey);\n }\n }\n});`,\n description: 'Created Nuxt plugin for HumanBehavior SDK in app directory'\n });\n\n // Create environment configuration\n const nuxtConfigFile = path.join(this.projectRoot, 'nuxt.config.ts');\n {\n const mod = this.applyOrNotify(\n nuxtConfigFile,\n (c) => this.injectNuxtConfig(c),\n 'Added HumanBehavior runtime config to Nuxt config',\n 'Nuxt: Add inside defineNuxtConfig({ … }):\\nruntimeConfig: { public: { humanBehaviorApiKey: process.env.NUXT_PUBLIC_HUMANBEHAVIOR_API_KEY } },'\n );\n if (mod) modifications.push(mod);\n }\n\n // Create or append to environment file\n modifications.push(this.createEnvironmentModification(this.framework!));\n\n return modifications;\n }\n\n /**\n * Generate Remix-specific modifications\n */\n private async generateRemixModifications(): Promise<CodeModification[]> {\n const modifications: CodeModification[] = [];\n \n // Find root.tsx file\n const rootFile = path.join(this.projectRoot, 'app', 'root.tsx');\n if (fs.existsSync(rootFile)) {\n const content = fs.readFileSync(rootFile, 'utf8');\n const modifiedContent = this.injectRemixProvider(content);\n \n modifications.push({\n filePath: rootFile,\n action: 'modify',\n content: modifiedContent,\n description: 'Added HumanBehaviorProvider to Remix root component'\n });\n }\n\n // Create or append to environment file\n modifications.push(this.createEnvironmentModification(this.framework!));\n\n return modifications;\n }\n\n /**\n * Generate Vue-specific modifications\n */\n private async generateVueModifications(): Promise<CodeModification[]> {\n const modifications: CodeModification[] = [];\n \n // Find main.js or main.ts\n const mainFile = this.findVueMainFile();\n // Create Vue composable per docs (idempotent)\n const composableDir = path.join(this.projectRoot, 'src', 'composables');\n const composablePath = path.join(composableDir, 'useHumanBehavior.ts');\n const composableContent = `import { HumanBehaviorTracker } from 'humanbehavior-js'\n\nexport function useHumanBehavior() {\n const apiKey = import.meta.env.VITE_HUMANBEHAVIOR_API_KEY\n \n if (apiKey) {\n const tracker = HumanBehaviorTracker.init(apiKey);\n \n return { tracker }\n }\n \n return { tracker: null }\n}\n`;\n try {\n if (!fs.existsSync(composableDir)) {\n fs.mkdirSync(composableDir, { recursive: true });\n }\n if (!fs.existsSync(composablePath)) {\n modifications.push({\n filePath: composablePath,\n action: 'create',\n content: composableContent,\n description: 'Created Vue composable useHumanBehavior'\n });\n }\n } catch {}\n if (mainFile) {\n const content = fs.readFileSync(mainFile, 'utf8');\n const modifiedContent = this.injectVuePlugin(content);\n \n modifications.push({\n filePath: mainFile,\n action: 'modify',\n content: modifiedContent,\n description: 'Added HumanBehaviorPlugin to Vue app'\n });\n }\n\n // Create or append to environment file\n modifications.push(this.createEnvironmentModification(this.framework!));\n\n return modifications;\n }\n\n /**\n * Generate Angular-specific modifications\n */\n private async generateAngularModifications(): Promise<CodeModification[]> {\n const modifications: CodeModification[] = [];\n \n // Create Angular service (docs pattern)\n const serviceDir = path.join(this.projectRoot, 'src', 'app', 'services');\n const servicePath = path.join(serviceDir, 'hb.service.ts');\n const serviceContent = `import { Injectable, NgZone, Inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { HumanBehaviorTracker } from 'humanbehavior-js';\nimport { environment } from '../../environments/environment';\n\n@Injectable({ providedIn: 'root' })\nexport class HumanBehavior {\n private tracker: ReturnType<typeof HumanBehaviorTracker.init> | null = null;\n\n constructor(private ngZone: NgZone, @Inject(PLATFORM_ID) private platformId: Object) {\n if (isPlatformBrowser(this.platformId)) {\n this.ngZone.runOutsideAngular(() => {\n this.tracker = HumanBehaviorTracker.init(environment.humanBehaviorApiKey);\n });\n }\n }\n\n capture(event: string, props?: Record<string, any>) {\n this.tracker?.customEvent(event, props);\n }\n\n identify(user: Record<string, any>) {\n this.tracker?.identifyUser({ userProperties: user });\n }\n\n trackPageView(path?: string) {\n this.tracker?.trackPageView(path);\n }\n}\n`;\n\n if (!fs.existsSync(serviceDir)) {\n fs.mkdirSync(serviceDir, { recursive: true });\n }\n if (!fs.existsSync(servicePath)) {\n modifications.push({\n filePath: servicePath,\n action: 'create',\n content: serviceContent,\n description: 'Created Angular HumanBehavior service (singleton)'\n });\n }\n\n // Handle Angular environment files (proper Angular way)\n const envFile = path.join(this.projectRoot, 'src', 'environments', 'environment.ts');\n const envProdFile = path.join(this.projectRoot, 'src', 'environments', 'environment.prod.ts');\n \n // Create environments directory if it doesn't exist\n const envDir = path.dirname(envFile);\n if (!fs.existsSync(envDir)) {\n fs.mkdirSync(envDir, { recursive: true });\n }\n \n // Create or update development environment\n if (fs.existsSync(envFile)) {\n const content = fs.readFileSync(envFile, 'utf8');\n if (!content.includes('humanBehaviorApiKey')) {\n const modifiedContent = content.replace(\n /export const environment = {([\\s\\S]*?)};/,\n `export const environment = {\n $1,\n humanBehaviorApiKey: '${this.apiKey}'\n};`\n );\n modifications.push({\n filePath: envFile,\n action: 'modify',\n content: modifiedContent,\n description: 'Added API key to Angular development environment'\n });\n }\n } else {\n // Create new development environment file\n modifications.push({\n filePath: envFile,\n action: 'create',\n content: `export const environment = {\n production: false,\n humanBehaviorApiKey: '${this.apiKey}'\n};`,\n description: 'Created Angular development environment file'\n });\n }\n \n // Create or update production environment\n if (fs.existsSync(envProdFile)) {\n const content = fs.readFileSync(envProdFile, 'utf8');\n if (!content.includes('humanBehaviorApiKey')) {\n const modifiedContent = content.replace(\n /export const environment = {([\\s\\S]*?)};/,\n `export const environment = {\n $1,\n humanBehaviorApiKey: '${this.apiKey}'\n};`\n );\n modifications.push({\n filePath: envProdFile,\n action: 'modify',\n content: modifiedContent,\n description: 'Added API key to Angular production environment'\n });\n }\n } else {\n // Create new production environment file\n modifications.push({\n filePath: envProdFile,\n action: 'create',\n content: `export const environment = {\n production: true,\n humanBehaviorApiKey: '${this.apiKey}'\n};`,\n description: 'Created Angular production environment file'\n });\n }\n\n // For Angular, we don't need .env files since we use environment.ts\n // The environment files are already created above\n\n // Inject service into app component\n const appComponentPath = path.join(this.projectRoot, 'src', 'app', 'app.ts');\n if (fs.existsSync(appComponentPath)) {\n const appContent = fs.readFileSync(appComponentPath, 'utf8');\n \n // Check if already has HumanBehavior service\n if (!appContent.includes('HumanBehavior')) {\n let modifiedAppContent = appContent\n .replace(\n /import { Component } from '@angular\\/core';/,\n `import { Component } from '@angular/core';\nimport { HumanBehavior } from './services/hb.service';`\n )\n .replace(\n /export class App {/,\n `export class App {\n constructor(private readonly humanBehavior: HumanBehavior) {}`\n );\n \n // Do not modify standalone setting; leave component decorator unchanged\n \n modifications.push({\n action: 'modify',\n filePath: appComponentPath,\n content: modifiedAppContent,\n description: 'Injected HumanBehavior service into Angular app component'\n });\n }\n }\n\n return modifications;\n }\n\n /**\n * Generate Svelte-specific modifications\n */\n private async generateSvelteModifications(): Promise<CodeModification[]> {\n const modifications: CodeModification[] = [];\n \n // Check for SvelteKit\n const svelteConfigFile = path.join(this.projectRoot, 'svelte.config.js');\n const isSvelteKit = fs.existsSync(svelteConfigFile);\n \n if (isSvelteKit) {\n // SvelteKit - create layout file\n const layoutFile = path.join(this.projectRoot, 'src', 'routes', '+layout.svelte');\n if (fs.existsSync(layoutFile)) {\n const content = fs.readFileSync(layoutFile, 'utf8');\n const modifiedContent = this.injectSvelteKitLayout(content);\n \n modifications.push({\n filePath: layoutFile,\n action: 'modify',\n content: modifiedContent,\n description: 'Added HumanBehavior tracker init to SvelteKit layout'\n });\n }\n } else {\n // Regular Svelte - modify main file\n const mainFile = this.findSvelteMainFile();\n if (mainFile) {\n const content = fs.readFileSync(mainFile, 'utf8');\n const modifiedContent = this.injectSvelteStore(content);\n \n modifications.push({\n filePath: mainFile,\n action: 'modify',\n content: modifiedContent,\n description: 'Added HumanBehavior tracker init to Svelte app'\n });\n }\n }\n\n // Create or append to environment file\n modifications.push(this.createEnvironmentModification(this.framework!));\n\n return modifications;\n }\n\n /**\n * Generate vanilla JS/TS modifications\n */\n private async generateVanillaModifications(): Promise<CodeModification[]> {\n const modifications: CodeModification[] = [];\n \n // Find HTML file to inject script\n const htmlFile = this.findHTMLFile();\n if (htmlFile) {\n const content = fs.readFileSync(htmlFile, 'utf8');\n const modifiedContent = this.injectVanillaScript(content);\n \n modifications.push({\n filePath: htmlFile,\n action: 'modify',\n content: modifiedContent,\n description: 'Added HumanBehavior CDN script to HTML file'\n });\n }\n\n // Create or append to environment file\n modifications.push(this.createEnvironmentModification(this.framework!));\n\n return modifications;\n }\n\n /**\n * Generate Gatsby-specific modifications\n */\n private async generateGatsbyModifications(): Promise<CodeModification[]> {\n const modifications: CodeModification[] = [];\n \n // Modify or create gatsby-browser.js for Gatsby\n const gatsbyBrowserFile = path.join(this.projectRoot, 'gatsby-browser.js');\n \n if (fs.existsSync(gatsbyBrowserFile)) {\n const content = fs.readFileSync(gatsbyBrowserFile, 'utf8');\n const modifiedContent = this.injectGatsbyBrowser(content);\n \n modifications.push({\n filePath: gatsbyBrowserFile,\n action: 'modify',\n content: modifiedContent,\n description: 'Added HumanBehavior initialization to Gatsby browser'\n });\n } else {\n // Create gatsby-browser.js if it doesn't exist\n modifications.push({\n filePath: gatsbyBrowserFile,\n action: 'create',\n content: `import { HumanBehaviorTracker } from 'humanbehavior-js';\n\nexport const onClientEntry = () => {\n const apiKey = process.env.GATSBY_HUMANBEHAVIOR_API_KEY;\n if (apiKey) {\n HumanBehaviorTracker.init(apiKey);\n }\n};`,\n description: 'Created gatsby-browser.js with HumanBehavior initialization'\n });\n }\n\n // Create or append to environment file\n modifications.push(this.createEnvironmentModification(this.framework!));\n\n return modifications;\n }\n\n\n\n /**\n * Apply modifications to the codebase\n */\n protected async applyModifications(modifications: CodeModification[]): Promise<void> {\n for (const modification of modifications) {\n try {\n const dir = path.dirname(modification.filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n switch (modification.action) {\n case 'create':\n fs.writeFileSync(modification.filePath, modification.content);\n break;\n case 'modify':\n fs.writeFileSync(modification.filePath, modification.content);\n break;\n case 'append':\n fs.appendFileSync(modification.filePath, '\\n' + modification.content);\n break;\n }\n } catch (error) {\n throw new Error(`Failed to apply modification to ${modification.filePath}: ${error}`);\n }\n }\n }\n\n /**\n * Generate next steps for the user\n */\n private generateNextSteps(): string[] {\n const steps = [\n '✅ SDK installed and configured automatically!',\n '🚀 Your app is now tracking user behavior',\n '📊 View sessions in your HumanBehavior dashboard',\n '🔧 Customize tracking in your code as needed'\n ];\n\n if (this.framework?.type === 'react' || this.framework?.type === 'nextjs') {\n steps.push('💡 Use the useHumanBehavior() hook to track custom events');\n }\n\n // Append any manual notes gathered during transformation\n if (this.manualNotes.length) {\n steps.push(...this.manualNotes.map((n) => `⚠️ ${n}`));\n }\n\n return steps;\n }\n\n /**\n * Helper: apply a file transform or record a manual instruction if unchanged/missing\n */\n private applyOrNotify(\n filePath: string,\n transform: (content: string) => string,\n description: string,\n manualNote: string\n ): CodeModification | null {\n if (!fs.existsSync(filePath)) {\n this.manualNotes.push(`${manualNote} (file missing: ${path.relative(this.projectRoot, filePath)})`);\n return null;\n }\n const original = fs.readFileSync(filePath, 'utf8');\n const updated = transform(original);\n if (updated !== original) {\n return {\n filePath,\n action: 'modify',\n content: updated,\n description\n };\n }\n this.manualNotes.push(manualNote);\n return null;\n }\n\n // Helper methods for file detection and content injection\n private findReactAppFile(): string | null {\n const possibleFiles = [\n 'src/App.jsx', 'src/App.js', 'src/App.tsx', 'src/App.ts',\n 'src/index.js', 'src/index.tsx', 'src/main.js', 'src/main.tsx'\n ];\n\n for (const file of possibleFiles) {\n const fullPath = path.join(this.projectRoot, file);\n if (fs.existsSync(fullPath)) {\n return fullPath;\n }\n }\n return null;\n }\n\n private findVueMainFile(): string | null {\n const possibleFiles = [\n 'src/main.js', 'src/main.ts', 'src/main.jsx', 'src/main.tsx'\n ];\n\n for (const file of possibleFiles) {\n const fullPath = path.join(this.projectRoot, file);\n if (fs.existsSync(fullPath)) {\n return fullPath;\n }\n }\n return null;\n }\n\n private findSvelteMainFile(): string | null {\n const possibleFiles = [\n 'src/main.js', 'src/main.ts', 'src/main.svelte'\n ];\n\n for (const file of possibleFiles) {\n const fullPath = path.join(this.projectRoot, file);\n if (fs.existsSync(fullPath)) {\n return fullPath;\n }\n }\n return null;\n }\n\n private findHTMLFile(): string | null {\n const possibleFiles = ['index.html', 'public/index.html', 'dist/index.html'];\n\n for (const file of possibleFiles) {\n const fullPath = path.join(this.projectRoot, file);\n if (fs.existsSync(fullPath)) {\n return fullPath;\n }\n }\n return null;\n }\n\n private injectReactProvider(content: string, filePath: string): string {\n const isTypeScript = filePath.endsWith('.tsx') || filePath.endsWith('.ts');\n \n // Check if already has HumanBehaviorProvider\n if (content.includes('HumanBehaviorProvider')) {\n return content;\n }\n\n // Determine the correct environment variable syntax based on bundler\n const isVite = this.framework?.bundler === 'vite';\n const envVar = isVite \n ? 'import.meta.env.VITE_HUMANBEHAVIOR_API_KEY!' \n : 'process.env.REACT_APP_HUMANBEHAVIOR_API_KEY!';\n\n const importStatement = `import { HumanBehaviorProvider } from 'humanbehavior-js/react';`;\n\n // Enhanced parsing for React 18+ features\n const hasReact18 = this.framework?.features?.hasReact18;\n \n // Handle different React patterns\n if (content.includes('function App()') || content.includes('const App =')) {\n // Add import statement\n let modifiedContent = content.replace(\n /(import.*?from.*?['\"]react['\"];?)/,\n `$1\\n${importStatement}`\n );\n \n // If no React import found, add it at the top\n if (!modifiedContent.includes(importStatement)) {\n modifiedContent = `${importStatement}\\n\\n${modifiedContent}`;\n }\n \n // Wrap the App component return with HumanBehaviorProvider\n modifiedContent = modifiedContent.replace(\n /return\\s*\\(([\\s\\S]*?)\\)\\s*;/,\n `return (\n <HumanBehaviorProvider apiKey={${envVar}}>\n $1\n </HumanBehaviorProvider>\n );`\n );\n \n return modifiedContent;\n }\n \n // Handle React 18+ createRoot pattern\n if (hasReact18 && content.includes('createRoot')) {\n let modifiedContent = content.replace(\n /(import.*?from.*?['\"]react['\"];?)/,\n `$1\\n${importStatement}`\n );\n \n if (!modifiedContent.includes(importStatement)) {\n modifiedContent = `${importStatement}\\n\\n${modifiedContent}`;\n }\n \n // Wrap the root render with HumanBehaviorProvider\n modifiedContent = modifiedContent.replace(\n /(root\\.render\\s*\\([\\s\\S]*?\\)\\s*;)/,\n `root.render(\n <HumanBehaviorProvider apiKey={${envVar}}>\n $1\n </HumanBehaviorProvider>\n );`\n );\n \n return modifiedContent;\n }\n\n // Fallback: simple injection\n return `${importStatement}\\n\\n${content}`;\n }\n\n private injectNextJSAppRouter(content: string): string {\n if (content.includes('Providers')) {\n return content;\n }\n\n const importStatement = `import { Providers } from './providers';`;\n \n // First, add the import statement\n let modifiedContent = content.replace(\n /export default function RootLayout/,\n `${importStatement}\\n\\nexport default function RootLayout`\n );\n \n // Then wrap the body content with Providers\n // Use a more specific approach to handle the body content\n modifiedContent = modifiedContent.replace(\n /<body([^>]*)>([\\s\\S]*?)<\\/body>/,\n (match, bodyAttrs, bodyContent) => {\n // Trim whitespace and newlines from bodyContent\n const trimmedContent = bodyContent.trim();\n return `<body${bodyAttrs}>\n <Providers>\n ${trimmedContent}\n </Providers>\n </body>`;\n }\n );\n \n return modifiedContent;\n }\n\n private injectNextJSPagesRouter(content: string, importPath: string = '../components/providers'): string {\n if (content.includes('Providers')) {\n return content;\n }\n\n const importStatement = `import { Providers } from '${importPath}';`;\n \n return content.replace(\n /function MyApp/,\n `${importStatement}\\n\\nfunction MyApp`\n ).replace(\n /return \\(([\\s\\S]*?)\\);/,\n `return (\n <Providers>\n $1\n </Providers>\n );`\n );\n }\n\n private injectRemixProvider(content: string): string {\n if (content.includes('HumanBehaviorProvider')) {\n return content;\n }\n\n let modifiedContent = content;\n \n // Step 1: Add useLoaderData import\n if (!content.includes('useLoaderData')) {\n modifiedContent = modifiedContent.replace(\n /(} from ['\"]@remix-run\\/react['\"];?\\s*)/,\n `$1import { useLoaderData } from '@remix-run/react';\n`\n );\n }\n \n // Step 2: Add HumanBehaviorProvider import\n if (!content.includes('HumanBehaviorProvider')) {\n modifiedContent = modifiedContent.replace(\n /(} from ['\"]@remix-run\\/react['\"];?\\s*)/,\n `$1import { HumanBehaviorProvider } from 'humanbehavior-js/react';\n`\n );\n }\n \n // Step 3: Add LoaderFunctionArgs import\n if (!content.includes('LoaderFunctionArgs')) {\n modifiedContent = modifiedContent.replace(\n /(} from ['\"]@remix-run\\/node['\"];?\\s*)/,\n `$1import type { LoaderFunctionArgs } from '@remix-run/node';\n`\n );\n }\n\n // Step 4: Add loader function before Layout function\n if (!content.includes('export const loader')) {\n modifiedContent = modifiedContent.replace(\n /(export function Layout)/,\n `export const loader = async ({ request }: LoaderFunctionArgs) => {\n return {\n ENV: {\n HUMANBEHAVIOR_API_KEY: process.env.HUMANBEHAVIOR_API_KEY,\n },\n };\n};\n\n$1`\n );\n }\n\n // Step 5: Add useLoaderData call and wrap App function's return content with HumanBehaviorProvider\n if (!content.includes('const data = useLoaderData')) {\n modifiedContent = modifiedContent.replace(\n /(export default function App\\(\\) \\{\\s*)(return \\(\\s*<div[^>]*>[\\s\\S]*?<\\/div>\\s*\\);\\s*\\})/,\n `$1const data = useLoaderData<typeof loader>();\n \n return (\n <HumanBehaviorProvider apiKey={data.ENV.HUMANBEHAVIOR_API_KEY}>\n <div className=\"min-h-screen bg-gray-50\">\n <Navigation />\n <Outlet />\n </div>\n </HumanBehaviorProvider>\n );\n}`\n );\n }\n\n return modifiedContent;\n }\n\n private injectVuePlugin(content: string): string {\n // New: use composable/tracker pattern per docs; idempotent and migrates from plugin\n if (content.includes('useHumanBehavior')) {\n return content;\n }\n\n const hasVue3 = this.framework?.features?.hasVue3;\n const isVue3ByContent = content.includes('createApp') || content.includes('import { createApp }');\n \n let modifiedContent = content\n .replace(/import\\s*\\{\\s*HumanBehaviorPlugin\\s*\\}\\s*from\\s*['\\\"]humanbehavior-js\\/vue['\\\"];?/g, '')\n .replace(/app\\.use\\(\\s*HumanBehaviorPlugin[\\s\\S]*?\\);?/g, '');\n\n if (hasVue3 || isVue3ByContent) {\n const importComposable = `import { useHumanBehavior } from './composables/useHumanBehavior';`;\n if (!modifiedContent.includes(importComposable)) {\n const lastImportIndex = modifiedContent.lastIndexOf('import');\n if (lastImportIndex !== -1) {\n const nextLineIndex = modifiedContent.indexOf('\\n', lastImportIndex);\n if (nextLineIndex !== -1) {\n modifiedContent = modifiedContent.slice(0, nextLineIndex + 1) + importComposable + '\\n' + modifiedContent.slice(nextLineIndex + 1);\n } else {\n modifiedContent = modifiedContent + '\\n' + importComposable;\n }\n } else {\n modifiedContent = importComposable + '\\n' + modifiedContent;\n }\n }\n if (modifiedContent.includes('createApp')) {\n modifiedContent = modifiedContent.replace(\n /(const\\s+app\\s*=\\s*createApp\\([^)]*\\))/,\n `$1\\nconst { tracker } = useHumanBehavior();`\n );\n }\n return modifiedContent;\n } else {\n const trackerImport = `import { HumanBehaviorTracker } from 'humanbehavior-js';`;\n if (!modifiedContent.includes(trackerImport)) {\n modifiedContent = `${trackerImport}\\n${modifiedContent}`;\n }\n if (modifiedContent.includes('new Vue')) {\n modifiedContent = modifiedContent.replace(\n /(new\\s+Vue\\s*\\()/,\n `HumanBehaviorTracker.init(process.env.VUE_APP_HUMANBEHAVIOR_API_KEY || import.meta?.env?.VITE_HUMANBEHAVIOR_API_KEY);\\n$1`\n );\n }\n return modifiedContent;\n }\n }\n\n private injectAngularModule(content: string): string {\n if (content.includes('HumanBehaviorModule')) {\n return content;\n }\n\n const importStatement = `import { HumanBehaviorModule } from 'humanbehavior-js/angular';`;\n const environmentImport = `import { environment } from '../environments/environment';`;\n \n // Add environment import if not present\n let modifiedContent = content;\n if (!content.includes('environment')) {\n modifiedContent = content.replace(\n /import.*from.*['\"]@angular/,\n `${environmentImport}\\n$&`\n );\n }\n \n return modifiedContent.replace(\n /imports:\\s*\\[([\\s\\S]*?)\\]/,\n `imports: [\n $1,\n HumanBehaviorModule.forRoot({\n apiKey: environment.humanBehaviorApiKey\n })\n ]`\n ).replace(\n /import.*from.*['\"]@angular/,\n `$&\\n${importStatement}`\n );\n }\n\n private injectAngularStandaloneInit(content: string): string {\n if (content.includes('initializeHumanBehavior')) {\n return content;\n }\n\n const importStatement = `import { initializeHumanBehavior } from 'humanbehavior-js/angular';`;\n const environmentImport = `import { environment } from './environments/environment';`;\n \n // Add imports at the top\n let modifiedContent = content.replace(\n /import.*from.*['\"]@angular/,\n `${importStatement}\\n${environmentImport}\\n$&`\n );\n\n // Add initialization after bootstrapApplication\n modifiedContent = modifiedContent.replace(\n /(bootstrapApplication\\([^}]+\\}?\\)(?:\\s*\\.catch[^;]+;)?)/,\n `$1\n\n// Initialize HumanBehavior SDK (client-side only)\nif (typeof window !== 'undefined') {\n const tracker = initializeHumanBehavior(environment.humanBehaviorApiKey);\n}`\n );\n\n return modifiedContent;\n }\n\n private injectSvelteStore(content: string): string {\n // Direct tracker init for non-SSR Svelte\n if (content.includes('HumanBehaviorTracker.init')) {\n return content;\n }\n const importStatement = `import { HumanBehaviorTracker } from 'humanbehavior-js';`;\n const initCode = `// Initialize HumanBehavior SDK\\nHumanBehaviorTracker.init(import.meta.env?.VITE_HUMANBEHAVIOR_API_KEY || process.env.PUBLIC_HUMANBEHAVIOR_API_KEY || '');`;\n return `${importStatement}\\n${initCode}\\n\\n${content}`;\n }\n\n private injectSvelteKitLayout(content: string): string {\n // Direct tracker init with browser guard for SvelteKit\n if (content.includes('HumanBehaviorTracker.init')) {\n return content;\n }\n const envImport = `import { PUBLIC_HUMANBEHAVIOR_API_KEY } from '$env/static/public';`;\n const hbImport = `import { HumanBehaviorTracker } from 'humanbehavior-js';`;\n const browserImport = `import { browser } from '$app/environment';`;\n const initCode = `if (browser) {\\n const apiKey = PUBLIC_HUMANBEHAVIOR_API_KEY || import.meta.env.VITE_HUMANBEHAVIOR_API_KEY;\\n if (apiKey) {\\n HumanBehaviorTracker.init(apiKey);\\n }\\n}`;\n\n if (content.includes('<script lang=\"ts\">')) {\n return content.replace(\n /<script lang=\"ts\">/,\n `<script lang=\"ts\">\\n\\t${browserImport}\\n\\t${envImport}\\n\\t${hbImport}\\n\\t${initCode}`\n );\n } else if (content.includes('<script>')) {\n return content.replace(\n /<script>/,\n `<script>\\n\\t${browserImport}\\n\\t${envImport}\\n\\t${hbImport}\\n\\t${initCode}`\n );\n } else {\n return `<script lang=\"ts\">\\n${browserImport}\\n${envImport}\\n${hbImport}\\n${initCode}\\n</script>\\n\\n${content}`;\n }\n }\n\n private injectVanillaScript(content: string): string {\n if (content.includes('humanbehavior-js')) {\n return content;\n }\n\n const cdnScript = `<script src=\"https://unpkg.com/humanbehavior-js@latest/dist/index.min.js\"></script>`;\n const initScript = `<script>\n // Initialize HumanBehavior SDK\n // Note: For vanilla HTML, the API key must be hardcoded since env vars aren't available\n const tracker = HumanBehaviorTracker.init('${this.apiKey}');\n</script>`;\n \n return content.replace(\n /<\\/head>/,\n ` ${cdnScript}\\n ${initScript}\\n</head>`\n );\n }\n\n /**\n * Inject Astro layout with HumanBehavior component\n */\n private injectAstroLayout(content: string): string {\n // Check if HumanBehavior component is already imported\n if (content.includes('HumanBehavior') || content.includes('humanbehavior-js')) {\n return content; // Already has HumanBehavior\n }\n\n // Add import inside frontmatter if not present\n let modifiedContent = content;\n if (!content.includes('import HumanBehavior')) {\n const importStatement = 'import HumanBehavior from \\'../components/HumanBehavior.astro\\';';\n const frontmatterEndIndex = content.indexOf('---', 3);\n if (frontmatterEndIndex !== -1) {\n // Insert import inside frontmatter, before the closing ---\n modifiedContent = content.slice(0, frontmatterEndIndex) + '\\n' + importStatement + '\\n' + content.slice(frontmatterEndIndex);\n } else {\n // No frontmatter, add at the very beginning\n modifiedContent = '---\\n' + importStatement + '\\n---\\n\\n' + content;\n }\n }\n\n // Find the closing </body> tag and add HumanBehavior component before it\n const bodyCloseIndex = modifiedContent.lastIndexOf('</body>');\n if (bodyCloseIndex === -1) {\n // No body tag found, append to end\n return modifiedContent + '\\n\\n<HumanBehavior />';\n }\n\n // Add component before closing body tag\n return modifiedContent.slice(0, bodyCloseIndex) + ' <HumanBehavior />\\n' + modifiedContent.slice(bod