@scope-analytics/browser 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # Scope Analytics
2
+
3
+ Zero-code analytics with AI-powered insights. Install analytics in under 5 minutes without modifying your application code.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Run after building your application
9
+ npx @scope-analytics/browser install --key YOUR_PUBLIC_KEY
10
+
11
+ # Or use environment variable
12
+ export SCOPE_PUBLIC_KEY=your_public_key
13
+ npx @scope-analytics/browser install
14
+ ```
15
+
16
+ ## How it Works
17
+
18
+ Scope Analytics modifies your built HTML files to inject our lightweight SDK. This approach:
19
+ - ✅ Works with any framework or build tool
20
+ - ✅ No code changes required
21
+ - ✅ Automatic tracking of all user interactions
22
+ - ✅ Compatible with all major hosting platforms
23
+
24
+ ## Platform-Specific Setup
25
+
26
+ ### Vercel
27
+ ```json
28
+ // vercel.json
29
+ {
30
+ "buildCommand": "npm run build && npx @scope-analytics/browser install"
31
+ }
32
+ ```
33
+
34
+ ### Netlify
35
+ ```toml
36
+ # netlify.toml
37
+ [build]
38
+ command = "npm run build && npx @scope-analytics/browser install"
39
+ ```
40
+
41
+ ### Render
42
+ Set build command: `npm run build && npx @scope-analytics/browser install`
43
+
44
+ ### Next.js
45
+ ```json
46
+ // package.json
47
+ {
48
+ "scripts": {
49
+ "build": "next build && npx @scope-analytics/browser install --dir .next"
50
+ }
51
+ }
52
+ ```
53
+
54
+ ## Commands
55
+
56
+ ### Install
57
+ ```bash
58
+ npx @scope-analytics/browser install [options]
59
+
60
+ Options:
61
+ -k, --key <apiKey> Your Scope Analytics public key
62
+ -d, --dir <directory> Build output directory (default: dist)
63
+ -e, --endpoint <url> Custom API endpoint
64
+ --no-api-tracking Disable API call tracking
65
+ --debug Enable debug mode
66
+ ```
67
+
68
+ ### Uninstall
69
+ ```bash
70
+ npx @scope-analytics/browser uninstall [options]
71
+
72
+ Options:
73
+ -d, --dir <directory> Build output directory (default: dist)
74
+ ```
75
+
76
+ ### Validate
77
+ ```bash
78
+ npx @scope-analytics/browser validate [options]
79
+
80
+ Options:
81
+ -d, --dir <directory> Build output directory (default: dist)
82
+ ```
83
+
84
+ ## What Gets Tracked
85
+
86
+ - 📊 Page views and navigation
87
+ - 🖱️ Clicks and interactions
88
+ - 📜 Scroll behavior and reading time
89
+ - ⏱️ Performance metrics
90
+ - 🔗 API calls (optional)
91
+ - ❌ Errors and exceptions
92
+
93
+ ## Features
94
+
95
+ - **AI-Powered Insights**: Ask questions in natural language
96
+ - **Automatic Tracking**: No manual event setup required
97
+ - **Privacy-First**: No personal data collection
98
+ - **Lightweight**: < 30KB gzipped
99
+ - **Real-Time**: See events as they happen
100
+
101
+ ## Support
102
+
103
+ - Documentation: Coming soon at https://scopeai.dev
104
+ - Email: Contact via https://scopeai.dev
105
+ - Issues: Please report issues via our website
106
+
107
+ ## License
108
+
109
+ MIT
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import { install } from '../src/install.js';
6
+ import { uninstall } from '../src/uninstall.js';
7
+ import { validate } from '../src/validate.js';
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('scope-analytics')
13
+ .description('Scope Analytics - Build-time integration for zero-code analytics')
14
+ .version('0.0.4');
15
+
16
+ program
17
+ .command('install')
18
+ .description('Install Scope Analytics SDK into your built application')
19
+ .option('-k, --key <apiKey>', 'Your Scope Analytics public key (or use SCOPE_PUBLIC_KEY env var)')
20
+ .option('-d, --dir <directory>', 'Build output directory', 'dist')
21
+ .option('-e, --endpoint <url>', 'Custom API endpoint', 'https://api.scopeai.dev')
22
+ .option('--no-api-tracking', 'Disable API call tracking')
23
+ .option('--channel <channel>', 'SDK channel (v1, beta, stable)', 'v1')
24
+ .option('--sdk-url <url>', 'Custom SDK URL (overrides channel)')
25
+ .option('--debug', 'Enable debug mode')
26
+ .action(async (options) => {
27
+ try {
28
+ const apiKey = options.key || process.env.SCOPE_PUBLIC_KEY;
29
+
30
+ if (!apiKey) {
31
+ console.error(chalk.red('Error: API key is required'));
32
+ console.error(chalk.yellow('Please provide it via --key flag or SCOPE_PUBLIC_KEY environment variable'));
33
+ process.exit(1);
34
+ }
35
+
36
+ console.log(chalk.cyan('🚀 Installing Scope Analytics...'));
37
+
38
+ await install({
39
+ apiKey,
40
+ buildDir: options.dir,
41
+ endpoint: options.endpoint,
42
+ trackApiCalls: options.apiTracking,
43
+ channel: options.channel,
44
+ sdkUrl: options.sdkUrl,
45
+ debug: options.debug
46
+ });
47
+
48
+ console.log(chalk.green('✅ Scope Analytics installed successfully!'));
49
+ console.log(chalk.gray('Your application is now tracking analytics.'));
50
+ } catch (error) {
51
+ console.error(chalk.red('Installation failed:'), error.message);
52
+ if (options.debug) {
53
+ console.error(error);
54
+ }
55
+ process.exit(1);
56
+ }
57
+ });
58
+
59
+ program
60
+ .command('uninstall')
61
+ .description('Remove Scope Analytics SDK from your application')
62
+ .option('-d, --dir <directory>', 'Build output directory', 'dist')
63
+ .action(async (options) => {
64
+ try {
65
+ console.log(chalk.cyan('🗑️ Uninstalling Scope Analytics...'));
66
+
67
+ await uninstall({
68
+ buildDir: options.dir
69
+ });
70
+
71
+ console.log(chalk.green('✅ Scope Analytics uninstalled successfully'));
72
+ } catch (error) {
73
+ console.error(chalk.red('Uninstall failed:'), error.message);
74
+ process.exit(1);
75
+ }
76
+ });
77
+
78
+ program
79
+ .command('validate')
80
+ .description('Validate that Scope Analytics is properly installed')
81
+ .option('-d, --dir <directory>', 'Build output directory', 'dist')
82
+ .action(async (options) => {
83
+ try {
84
+ console.log(chalk.cyan('🔍 Validating Scope Analytics installation...'));
85
+
86
+ const result = await validate({
87
+ buildDir: options.dir
88
+ });
89
+
90
+ if (result.valid) {
91
+ console.log(chalk.green('✅ Scope Analytics is properly installed'));
92
+ console.log(chalk.gray(`Found in ${result.files.length} HTML files`));
93
+ } else {
94
+ console.log(chalk.yellow('⚠️ Scope Analytics is not installed'));
95
+ console.log(chalk.gray('Run "npx @scope-analytics/browser install" to add analytics to your application'));
96
+ }
97
+ } catch (error) {
98
+ console.error(chalk.red('Validation failed:'), error.message);
99
+ process.exit(1);
100
+ }
101
+ });
102
+
103
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@scope-analytics/browser",
3
+ "version": "0.0.4",
4
+ "description": "Build-time integration for Scope Analytics - Zero-code analytics with AI insights",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "scope-analytics": "bin/scope-analytics.js"
9
+ },
10
+ "scripts": {},
11
+ "keywords": [
12
+ "analytics",
13
+ "scope",
14
+ "tracking",
15
+ "ai",
16
+ "build-tool"
17
+ ],
18
+ "author": "Scope AI",
19
+ "license": "MIT",
20
+ "homepage": "https://scopeai.dev",
21
+ "dependencies": {
22
+ "chalk": "^5.3.0",
23
+ "commander": "^11.1.0",
24
+ "glob": "^10.3.10"
25
+ },
26
+ "engines": {
27
+ "node": ">=14.0.0"
28
+ },
29
+ "files": [
30
+ "bin/",
31
+ "src/",
32
+ "README.md"
33
+ ]
34
+ }
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { install } from './install.js';
2
+ export { uninstall } from './uninstall.js';
3
+ export { validate } from './validate.js';
4
+
5
+ // Platform-specific helpers
6
+ export { detectPlatform } from './platforms/detect.js';
7
+ export { getInstallInstructions } from './platforms/instructions.js';
package/src/install.js ADDED
@@ -0,0 +1,158 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { glob } from 'glob';
4
+ import chalk from 'chalk';
5
+
6
+ // Determine SDK URL based on environment
7
+ function getSDKUrl(options) {
8
+ // Allow explicit SDK URL override
9
+ if (options.sdkUrl) {
10
+ return options.sdkUrl;
11
+ }
12
+
13
+ // Check for channel preference
14
+ const channel = options.channel || process.env.SCOPE_SDK_CHANNEL || 'v1';
15
+ const validChannels = ['v1', 'beta', 'stable'];
16
+
17
+ if (validChannels.includes(channel)) {
18
+ return `https://cdn.scopeai.dev/${channel}/sdk.js`;
19
+ }
20
+
21
+ // Default to stable (v1) with cache-busting version
22
+ const version = '0.0.2'; // Update this with each SDK release
23
+ return `https://cdn.scopeai.dev/v1/sdk.js?v=${version}`;
24
+ }
25
+
26
+ export async function install(options) {
27
+ const {
28
+ apiKey,
29
+ buildDir = 'dist',
30
+ endpoint = 'https://api.scopeai.dev',
31
+ trackApiCalls = true,
32
+ debug = false
33
+ } = options;
34
+
35
+ // Verify build directory exists
36
+ try {
37
+ await fs.access(buildDir);
38
+ } catch (error) {
39
+ throw new Error(`Build directory "${buildDir}" does not exist. Please build your application first.`);
40
+ }
41
+
42
+ // Find all HTML files
43
+ const htmlFiles = await glob(`${buildDir}/**/*.html`);
44
+
45
+ if (htmlFiles.length === 0) {
46
+ throw new Error(`No HTML files found in "${buildDir}". Is this the correct build directory?`);
47
+ }
48
+
49
+ if (debug) {
50
+ console.log(chalk.gray(`Found ${htmlFiles.length} HTML files to process`));
51
+ }
52
+
53
+ let modifiedCount = 0;
54
+
55
+ // Process each HTML file
56
+ for (const filePath of htmlFiles) {
57
+ try {
58
+ let content = await fs.readFile(filePath, 'utf8');
59
+
60
+ // Skip if already installed
61
+ if (content.includes('SCOPE_CONFIG') || content.includes('cdn.scopeai.dev/sdk.js')) {
62
+ if (debug) {
63
+ console.log(chalk.gray(`Skipping ${path.relative(buildDir, filePath)} (already installed)`));
64
+ }
65
+ continue;
66
+ }
67
+
68
+ // Create the injection script
69
+ const sdkUrl = getSDKUrl(options);
70
+ const injectionScript = createInjectionScript({
71
+ apiKey,
72
+ endpoint,
73
+ trackApiCalls,
74
+ sdkUrl
75
+ });
76
+
77
+ // Find the best injection point
78
+ const injectionPoint = findInjectionPoint(content);
79
+
80
+ if (!injectionPoint) {
81
+ console.warn(chalk.yellow(`Warning: Could not find </body> or </html> tag in ${path.relative(buildDir, filePath)}`));
82
+ continue;
83
+ }
84
+
85
+ // Inject the script
86
+ content = content.slice(0, injectionPoint.index) +
87
+ injectionScript +
88
+ content.slice(injectionPoint.index);
89
+
90
+ // Write the modified content back
91
+ await fs.writeFile(filePath, content, 'utf8');
92
+ modifiedCount++;
93
+
94
+ if (debug) {
95
+ console.log(chalk.gray(`✓ Modified ${path.relative(buildDir, filePath)}`));
96
+ }
97
+ } catch (error) {
98
+ console.error(chalk.red(`Error processing ${filePath}:`), error.message);
99
+ throw error;
100
+ }
101
+ }
102
+
103
+ if (modifiedCount === 0) {
104
+ console.log(chalk.yellow('⚠️ No files were modified. Scope Analytics may already be installed.'));
105
+ } else {
106
+ console.log(chalk.green(`✓ Modified ${modifiedCount} HTML file${modifiedCount === 1 ? '' : 's'}`));
107
+ }
108
+
109
+ // Copy the SDK file if we're using a local version (for development)
110
+ if (process.env.SCOPE_DEV_MODE === 'true') {
111
+ await copyLocalSDK(buildDir);
112
+ }
113
+ }
114
+
115
+ function createInjectionScript({ apiKey, endpoint, trackApiCalls, sdkUrl }) {
116
+ // For the one-line install, we'll pass the public key as a URL parameter
117
+ const sdkUrlWithKey = `${sdkUrl}${sdkUrl.includes('?') ? '&' : '?'}token=${apiKey}`;
118
+
119
+ return `
120
+ <!-- Scope Analytics -->
121
+ <script src="${sdkUrlWithKey}" async></script>
122
+ <!-- End Scope Analytics -->
123
+ `;
124
+ }
125
+
126
+ function findInjectionPoint(html) {
127
+ // Try to find </body> first
128
+ const bodyMatch = html.match(/<\/body>/i);
129
+ if (bodyMatch) {
130
+ return {
131
+ index: bodyMatch.index,
132
+ tag: '</body>'
133
+ };
134
+ }
135
+
136
+ // Fallback to </html>
137
+ const htmlMatch = html.match(/<\/html>/i);
138
+ if (htmlMatch) {
139
+ return {
140
+ index: htmlMatch.index,
141
+ tag: '</html>'
142
+ };
143
+ }
144
+
145
+ return null;
146
+ }
147
+
148
+ async function copyLocalSDK(buildDir) {
149
+ try {
150
+ const sdkSource = path.join(process.cwd(), '../../sdk-new/core/dist/scope-sdk.js');
151
+ const sdkDest = path.join(buildDir, 'scope-sdk.js');
152
+
153
+ await fs.copyFile(sdkSource, sdkDest);
154
+ console.log(chalk.gray('✓ Copied local SDK for development'));
155
+ } catch (error) {
156
+ console.warn(chalk.yellow('Could not copy local SDK:', error.message));
157
+ }
158
+ }
@@ -0,0 +1,48 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ export async function detectPlatform() {
5
+ const checks = [
6
+ { file: 'vercel.json', platform: 'vercel' },
7
+ { file: 'netlify.toml', platform: 'netlify' },
8
+ { file: 'render.yaml', platform: 'render' },
9
+ { file: 'railway.json', platform: 'railway' },
10
+ { file: '.dockerignore', platform: 'docker' },
11
+ { file: 'app.json', platform: 'heroku' }
12
+ ];
13
+
14
+ // Check for platform-specific files
15
+ for (const check of checks) {
16
+ try {
17
+ await fs.access(check.file);
18
+ return check.platform;
19
+ } catch {
20
+ // File doesn't exist, continue
21
+ }
22
+ }
23
+
24
+ // Check package.json for framework clues
25
+ try {
26
+ const packageJson = JSON.parse(await fs.readFile('package.json', 'utf8'));
27
+
28
+ if (packageJson.dependencies) {
29
+ if (packageJson.dependencies.next) return 'nextjs';
30
+ if (packageJson.dependencies.gatsby) return 'gatsby';
31
+ if (packageJson.dependencies.nuxt) return 'nuxt';
32
+ if (packageJson.dependencies['@angular/core']) return 'angular';
33
+ if (packageJson.dependencies.vue) return 'vue';
34
+ if (packageJson.dependencies.react) return 'react';
35
+ }
36
+
37
+ // Check scripts for build tools
38
+ if (packageJson.scripts) {
39
+ if (packageJson.scripts.build?.includes('vite')) return 'vite';
40
+ if (packageJson.scripts.build?.includes('webpack')) return 'webpack';
41
+ if (packageJson.scripts.build?.includes('parcel')) return 'parcel';
42
+ }
43
+ } catch {
44
+ // No package.json or error reading it
45
+ }
46
+
47
+ return 'generic';
48
+ }
@@ -0,0 +1,71 @@
1
+ export function getInstallInstructions(platform, apiKey) {
2
+ const instructions = {
3
+ vercel: {
4
+ name: 'Vercel',
5
+ steps: [
6
+ '1. Add SCOPE_PUBLIC_KEY to your environment variables in Vercel dashboard',
7
+ '2. Update your build command in vercel.json or dashboard:',
8
+ ' "buildCommand": "npm run build && npx @scope-analytics/browser install"',
9
+ '3. Deploy your application'
10
+ ]
11
+ },
12
+ netlify: {
13
+ name: 'Netlify',
14
+ steps: [
15
+ '1. Add SCOPE_PUBLIC_KEY to your environment variables in Netlify dashboard',
16
+ '2. Update your netlify.toml:',
17
+ ' [build]',
18
+ ' command = "npm run build && npx @scope-analytics/browser install"',
19
+ '3. Deploy your application'
20
+ ]
21
+ },
22
+ render: {
23
+ name: 'Render',
24
+ steps: [
25
+ '1. Add SCOPE_PUBLIC_KEY to your environment variables in Render dashboard',
26
+ '2. Update your build command:',
27
+ ' Build Command: npm run build && npx @scope-analytics/browser install',
28
+ '3. Deploy your application'
29
+ ]
30
+ },
31
+ railway: {
32
+ name: 'Railway',
33
+ steps: [
34
+ '1. Add SCOPE_PUBLIC_KEY to your environment variables in Railway dashboard',
35
+ '2. Update your build command in railway.json:',
36
+ ' "build": "npm run build && npx @scope-analytics/browser install"',
37
+ '3. Deploy your application'
38
+ ]
39
+ },
40
+ nextjs: {
41
+ name: 'Next.js',
42
+ steps: [
43
+ '1. Add SCOPE_PUBLIC_KEY to your .env.local or deployment environment',
44
+ '2. Update your package.json scripts:',
45
+ ' "build": "next build && npx @scope-analytics/browser install --dir .next"',
46
+ '3. Deploy as usual'
47
+ ]
48
+ },
49
+ vite: {
50
+ name: 'Vite',
51
+ steps: [
52
+ '1. Add SCOPE_PUBLIC_KEY to your environment',
53
+ '2. Update your package.json scripts:',
54
+ ' "build": "vite build && npx @scope-analytics/browser install"',
55
+ '3. Deploy the dist folder'
56
+ ]
57
+ },
58
+ generic: {
59
+ name: 'Generic Setup',
60
+ steps: [
61
+ '1. Set your public key:',
62
+ ` export SCOPE_PUBLIC_KEY="${apiKey || 'your-public-key'}"`,
63
+ '2. After building your app, run:',
64
+ ' npx @scope-analytics/browser install',
65
+ '3. Deploy your built files'
66
+ ]
67
+ }
68
+ };
69
+
70
+ return instructions[platform] || instructions.generic;
71
+ }
@@ -0,0 +1,68 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { glob } from 'glob';
4
+ import chalk from 'chalk';
5
+
6
+ export async function uninstall(options) {
7
+ const { buildDir = 'dist' } = options;
8
+
9
+ // Verify build directory exists
10
+ try {
11
+ await fs.access(buildDir);
12
+ } catch (error) {
13
+ throw new Error(`Build directory "${buildDir}" does not exist.`);
14
+ }
15
+
16
+ // Find all HTML files
17
+ const htmlFiles = await glob(`${buildDir}/**/*.html`);
18
+
19
+ if (htmlFiles.length === 0) {
20
+ console.log(chalk.yellow('No HTML files found to uninstall from'));
21
+ return;
22
+ }
23
+
24
+ let modifiedCount = 0;
25
+
26
+ // Process each HTML file
27
+ for (const filePath of htmlFiles) {
28
+ try {
29
+ let content = await fs.readFile(filePath, 'utf8');
30
+ const originalLength = content.length;
31
+
32
+ // Remove Scope Analytics scripts
33
+ content = removeScopeAnalytics(content);
34
+
35
+ // Only write if content changed
36
+ if (content.length !== originalLength) {
37
+ await fs.writeFile(filePath, content, 'utf8');
38
+ modifiedCount++;
39
+ }
40
+ } catch (error) {
41
+ console.error(chalk.red(`Error processing ${filePath}:`), error.message);
42
+ throw error;
43
+ }
44
+ }
45
+
46
+ console.log(chalk.green(`✓ Removed from ${modifiedCount} HTML file${modifiedCount === 1 ? '' : 's'}`));
47
+
48
+ // Remove local SDK file if exists
49
+ try {
50
+ const localSDK = path.join(buildDir, 'scope-sdk.js');
51
+ await fs.unlink(localSDK);
52
+ console.log(chalk.gray('✓ Removed local SDK file'));
53
+ } catch (error) {
54
+ // Ignore if file doesn't exist
55
+ }
56
+ }
57
+
58
+ function removeScopeAnalytics(html) {
59
+ // Remove the entire Scope Analytics section
60
+ const scopePattern = /<!-- Scope Analytics -->[\s\S]*?<!-- End Scope Analytics -->\n?/g;
61
+ html = html.replace(scopePattern, '');
62
+
63
+ // Also remove any standalone references (in case the comments were removed)
64
+ html = html.replace(/<script[^>]*>[\s\S]*?SCOPE_CONFIG[\s\S]*?<\/script>\n?/g, '');
65
+ html = html.replace(/<script[^>]*src="[^"]*cdn\.scopeai\.dev\/sdk\.js"[^>]*><\/script>\n?/g, '');
66
+
67
+ return html;
68
+ }
@@ -0,0 +1,48 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { glob } from 'glob';
4
+
5
+ export async function validate(options) {
6
+ const { buildDir = 'dist' } = options;
7
+
8
+ // Verify build directory exists
9
+ try {
10
+ await fs.access(buildDir);
11
+ } catch (error) {
12
+ throw new Error(`Build directory "${buildDir}" does not exist.`);
13
+ }
14
+
15
+ // Find all HTML files
16
+ const htmlFiles = await glob(`${buildDir}/**/*.html`);
17
+
18
+ if (htmlFiles.length === 0) {
19
+ return {
20
+ valid: false,
21
+ files: [],
22
+ message: 'No HTML files found in build directory'
23
+ };
24
+ }
25
+
26
+ const installedFiles = [];
27
+
28
+ // Check each HTML file
29
+ for (const filePath of htmlFiles) {
30
+ try {
31
+ const content = await fs.readFile(filePath, 'utf8');
32
+
33
+ // Check if Scope Analytics is installed
34
+ if (content.includes('SCOPE_CONFIG') && content.includes('cdn.scopeai.dev/sdk.js')) {
35
+ installedFiles.push(path.relative(buildDir, filePath));
36
+ }
37
+ } catch (error) {
38
+ console.error(`Error reading ${filePath}:`, error.message);
39
+ }
40
+ }
41
+
42
+ return {
43
+ valid: installedFiles.length > 0,
44
+ files: installedFiles,
45
+ totalFiles: htmlFiles.length,
46
+ coverage: (installedFiles.length / htmlFiles.length) * 100
47
+ };
48
+ }