@merlean/analyzer 1.0.0 → 1.1.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.
package/bin/cli.js CHANGED
@@ -1,38 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * AI Bot Analyzer CLI (LLM-powered)
4
+ * Merlean Analyzer CLI
5
5
  *
6
- * Usage:
7
- * npx @ai-bot/analyzer /path/to/codebase --name "My App" --upload https://backend.fly.dev
8
- *
9
- * Options:
10
- * --name, -n Site name (required)
11
- * --upload, -u Backend URL to upload map (required)
12
- * --output, -o Output file path (optional, default: ./site-map.json)
13
- * --token, -t Auth token for upload (optional)
6
+ * Scans your codebase and uploads to Merlean backend for AI analysis.
7
+ * No API keys required - analysis happens on our servers.
14
8
  *
15
- * Requires ANTHROPIC_API_KEY environment variable
9
+ * Usage:
10
+ * npx @merlean/analyzer ./my-project --name "My App"
16
11
  */
17
12
 
18
- // Load env from parent directory (.env in bot-smart/)
19
- require('dotenv').config({ path: require('path').join(__dirname, '../../.env') });
20
- require('dotenv').config();
21
-
22
- const { analyzeCodebase } = require('../lib/analyzer');
23
- const { uploadMap } = require('../lib/uploader');
13
+ const { scanCodebase } = require('../lib/analyzer');
24
14
  const path = require('path');
25
15
  const fs = require('fs');
26
16
 
17
+ const DEFAULT_BACKEND = 'https://ai-bot-backend.fly.dev';
18
+
27
19
  // Parse CLI arguments
28
20
  function parseArgs() {
29
21
  const args = process.argv.slice(2);
30
22
  const options = {
31
23
  path: null,
32
24
  name: null,
33
- upload: null,
34
- output: './site-map.json',
35
- token: null
25
+ backend: DEFAULT_BACKEND,
26
+ output: null
36
27
  };
37
28
 
38
29
  for (let i = 0; i < args.length; i++) {
@@ -40,12 +31,10 @@ function parseArgs() {
40
31
 
41
32
  if (arg === '--name' || arg === '-n') {
42
33
  options.name = args[++i];
43
- } else if (arg === '--upload' || arg === '-u') {
44
- options.upload = args[++i];
34
+ } else if (arg === '--backend' || arg === '-b') {
35
+ options.backend = args[++i];
45
36
  } else if (arg === '--output' || arg === '-o') {
46
37
  options.output = args[++i];
47
- } else if (arg === '--token' || arg === '-t') {
48
- options.token = args[++i];
49
38
  } else if (arg === '--help' || arg === '-h') {
50
39
  printHelp();
51
40
  process.exit(0);
@@ -59,35 +48,34 @@ function parseArgs() {
59
48
 
60
49
  function printHelp() {
61
50
  console.log(`
62
- AI Bot Analyzer - Generate site maps for AI Bot integration
51
+ Merlean Analyzer - AI-powered codebase analysis
63
52
 
64
53
  Usage:
65
- ai-bot-analyze <path> --name <name> --upload <url>
54
+ npx @merlean/analyzer <path> --name <name>
66
55
 
67
56
  Arguments:
68
57
  <path> Path to codebase to analyze
69
58
 
70
59
  Options:
71
60
  --name, -n <name> Site name (required)
72
- --upload, -u <url> Backend URL to upload map (required)
73
- --output, -o <file> Output file (default: ./site-map.json)
74
- --token, -t <token> Auth token for upload
61
+ --backend, -b <url> Backend URL (default: ${DEFAULT_BACKEND})
62
+ --output, -o <file> Save site map locally (optional)
75
63
  --help, -h Show this help
76
64
 
77
65
  Examples:
78
- # Analyze and upload to production
79
- ai-bot-analyze ./my-app --name "My App" --upload https://ai-bot-backend.fly.dev
66
+ # Analyze your project
67
+ npx @merlean/analyzer ./my-app --name "My App"
80
68
 
81
- # Analyze with auth token
82
- ai-bot-analyze ./my-app --name "My App" --upload https://backend.fly.dev --token secret123
69
+ # Use custom backend
70
+ npx @merlean/analyzer ./my-app --name "My App" --backend http://localhost:3004
83
71
 
84
- # Just generate local file
85
- ai-bot-analyze ./my-app --name "My App" --output ./map.json
72
+ # Also save map locally
73
+ npx @merlean/analyzer ./my-app --name "My App" --output ./site-map.json
86
74
  `);
87
75
  }
88
76
 
89
77
  async function main() {
90
- console.log('\nšŸ” AI Bot Analyzer\n');
78
+ console.log('\nšŸ” Merlean Analyzer\n');
91
79
 
92
80
  const options = parseArgs();
93
81
 
@@ -110,42 +98,54 @@ async function main() {
110
98
  process.exit(1);
111
99
  }
112
100
 
113
- console.log(`šŸ“ Analyzing: ${codebasePath}`);
101
+ console.log(`šŸ“ Scanning: ${codebasePath}`);
114
102
  console.log(`šŸ“› Site name: ${options.name}`);
115
103
 
116
104
  try {
117
- // Analyze codebase
118
- const siteMap = await analyzeCodebase(codebasePath, options.name);
105
+ // Scan codebase locally
106
+ const fileContents = await scanCodebase(codebasePath);
119
107
 
120
- console.log('\nšŸ“Š Analysis Summary:');
121
- console.log(` Routes: ${siteMap.routes?.length || 0}`);
122
- console.log(` Forms: ${siteMap.forms?.length || 0}`);
123
- console.log(` Actions: ${siteMap.actions?.length || 0}`);
124
- console.log(` Site ID: ${siteMap.siteId}`);
125
-
126
- // Save locally
127
- const outputPath = path.resolve(options.output);
128
- fs.writeFileSync(outputPath, JSON.stringify(siteMap, null, 2));
129
- console.log(`\nšŸ’¾ Saved to: ${outputPath}`);
130
-
131
- // Upload if URL provided
132
- if (options.upload) {
133
- console.log(`\nšŸ“¤ Uploading to: ${options.upload}`);
134
- const result = await uploadMap(options.upload, siteMap, options.token);
135
-
136
- if (result.success) {
137
- console.log('āœ… Upload successful!');
138
- console.log(`\nšŸŽ‰ Integration Ready!\n`);
139
- console.log(` Add this to your website:\n`);
140
- console.log(` <script src="${options.upload}/bot.js" data-site-id="${siteMap.siteId}"></script>\n`);
141
- } else {
142
- console.error(`āŒ Upload failed: ${result.error}`);
143
- process.exit(1);
144
- }
145
- } else {
146
- console.log('\nāš ļø No --upload URL provided. Map saved locally only.');
108
+ console.log(`\nšŸ“¤ Uploading to backend for analysis...`);
109
+
110
+ // Send to backend for LLM analysis
111
+ const response = await fetch(`${options.backend}/api/analyze`, {
112
+ method: 'POST',
113
+ headers: { 'Content-Type': 'application/json' },
114
+ body: JSON.stringify({
115
+ siteName: options.name,
116
+ files: fileContents
117
+ })
118
+ });
119
+
120
+ if (!response.ok) {
121
+ const error = await response.text();
122
+ throw new Error(`Backend error: ${response.status} ${error}`);
123
+ }
124
+
125
+ const result = await response.json();
126
+
127
+ console.log('\nāœ… Analysis complete!');
128
+ console.log('\nšŸ“Š Summary:');
129
+ console.log(` Site ID: ${result.siteId}`);
130
+ console.log(` Framework: ${result.framework || 'Unknown'}`);
131
+ console.log(` Routes: ${result.routes?.length || 0}`);
132
+ console.log(` Forms: ${result.forms?.length || 0}`);
133
+ console.log(` Actions: ${result.actions?.length || 0}`);
134
+
135
+ // Save locally if requested
136
+ if (options.output) {
137
+ const outputPath = path.resolve(options.output);
138
+ fs.writeFileSync(outputPath, JSON.stringify(result, null, 2));
139
+ console.log(`\nšŸ’¾ Saved to: ${outputPath}`);
147
140
  }
148
141
 
142
+ // Show integration instructions
143
+ console.log('\n' + '─'.repeat(50));
144
+ console.log('\nšŸŽ‰ Integration Ready!\n');
145
+ console.log('Add this to your website:\n');
146
+ console.log(`<script src="${options.backend}/bot.js" data-site-id="${result.siteId}"></script>`);
147
+ console.log('\n' + '─'.repeat(50) + '\n');
148
+
149
149
  } catch (error) {
150
150
  console.error(`\nāŒ Error: ${error.message}`);
151
151
  process.exit(1);
@@ -153,4 +153,3 @@ async function main() {
153
153
  }
154
154
 
155
155
  main();
156
-
package/lib/analyzer.js CHANGED
@@ -1,25 +1,13 @@
1
1
  /**
2
- * LLM-based Codebase Analyzer
2
+ * Codebase Scanner
3
3
  *
4
- * Uses Claude to analyze codebase and extract:
5
- * - API routes/endpoints
6
- * - Forms and their fields
7
- * - Actions/mutations
4
+ * Scans codebase and prepares file contents for backend analysis.
5
+ * NO LLM calls here - that happens on the backend.
8
6
  */
9
7
 
10
- require('dotenv').config({ path: require('path').join(__dirname, '../../.env') });
11
- require('dotenv').config();
12
-
13
- const Anthropic = require('@anthropic-ai/sdk').default;
14
8
  const fs = require('fs');
15
9
  const path = require('path');
16
10
  const { glob } = require('glob');
17
- const crypto = require('crypto');
18
-
19
- // Initialize Anthropic
20
- const anthropic = new Anthropic({
21
- apiKey: process.env.ANTHROPIC_API_KEY
22
- });
23
11
 
24
12
  // File patterns to scan
25
13
  const SCAN_PATTERNS = [
@@ -52,11 +40,9 @@ const PRIORITY_KEYWORDS = [
52
40
  ];
53
41
 
54
42
  /**
55
- * Analyze a codebase using LLM
43
+ * Scan codebase and collect file contents
56
44
  */
57
- async function analyzeCodebase(codebasePath, siteName) {
58
- const siteId = generateSiteId();
59
-
45
+ async function scanCodebase(codebasePath) {
60
46
  console.log(' Scanning files...');
61
47
 
62
48
  // Get files to scan
@@ -70,9 +56,9 @@ async function analyzeCodebase(codebasePath, siteName) {
70
56
 
71
57
  // Prioritize and limit files
72
58
  const prioritizedFiles = prioritizeFiles(files, codebasePath);
73
- const filesToAnalyze = prioritizedFiles.slice(0, 50); // Limit to avoid rate limits
59
+ const filesToAnalyze = prioritizedFiles.slice(0, 50); // Limit for performance
74
60
 
75
- console.log(` Analyzing ${filesToAnalyze.length} priority files...`);
61
+ console.log(` Preparing ${filesToAnalyze.length} priority files...`);
76
62
 
77
63
  // Read and prepare file contents
78
64
  const fileContents = [];
@@ -81,7 +67,7 @@ async function analyzeCodebase(codebasePath, siteName) {
81
67
  const content = fs.readFileSync(file, 'utf-8');
82
68
  const relativePath = path.relative(codebasePath, file);
83
69
 
84
- // Limit content per file to avoid token limits
70
+ // Limit content per file
85
71
  const truncatedContent = content.slice(0, 3000);
86
72
 
87
73
  fileContents.push({
@@ -93,19 +79,7 @@ async function analyzeCodebase(codebasePath, siteName) {
93
79
  }
94
80
  }
95
81
 
96
- // Send to LLM for analysis
97
- console.log(' Calling LLM for analysis...');
98
- const analysis = await analyzeWithLLM(fileContents, siteName);
99
-
100
- return {
101
- siteId,
102
- siteName,
103
- analyzedAt: new Date().toISOString(),
104
- framework: analysis.framework,
105
- routes: analysis.routes || [],
106
- forms: analysis.forms || [],
107
- actions: analysis.actions || []
108
- };
82
+ return fileContents;
109
83
  }
110
84
 
111
85
  /**
@@ -125,71 +99,4 @@ function prioritizeFiles(files, basePath) {
125
99
  });
126
100
  }
127
101
 
128
- /**
129
- * Analyze files using Claude
130
- */
131
- async function analyzeWithLLM(fileContents, siteName) {
132
- const filesText = fileContents.map(f =>
133
- `=== ${f.path} ===\n${f.content}`
134
- ).join('\n\n');
135
-
136
- const prompt = `Analyze this codebase and extract API information.
137
-
138
- CODEBASE FILES:
139
- ${filesText}
140
-
141
- Extract and return as JSON:
142
- 1. framework: detected framework (express, laravel, django, fastapi, nextjs, wordpress, etc.) or null
143
- 2. routes: array of {method, path, description} - API endpoints found
144
- 3. forms: array of {action, method, fields: [{name, type}]} - forms found
145
- 4. actions: array of {name, endpoint, method, description} - API calls/actions found
146
-
147
- Focus on:
148
- - REST API routes
149
- - Form submissions
150
- - AJAX/fetch calls
151
- - Controller methods
152
-
153
- Return ONLY valid JSON, no markdown or explanation:
154
- {"framework": "...", "routes": [...], "forms": [...], "actions": [...]}`;
155
-
156
- try {
157
- const response = await anthropic.messages.create({
158
- model: 'claude-sonnet-4-20250514',
159
- max_tokens: 4096,
160
- messages: [{
161
- role: 'user',
162
- content: prompt
163
- }]
164
- });
165
-
166
- const text = response.content[0].type === 'text' ? response.content[0].text : '';
167
-
168
- // Extract JSON from response
169
- const jsonMatch = text.match(/\{[\s\S]*\}/);
170
- if (jsonMatch) {
171
- return JSON.parse(jsonMatch[0]);
172
- }
173
-
174
- return { framework: null, routes: [], forms: [], actions: [] };
175
-
176
- } catch (error) {
177
- if (error.status === 429) {
178
- console.log(' āš ļø Rate limited, waiting 30s...');
179
- await new Promise(r => setTimeout(r, 30000));
180
- return analyzeWithLLM(fileContents, siteName);
181
- }
182
- console.error(' LLM error:', error.message);
183
- return { framework: null, routes: [], forms: [], actions: [] };
184
- }
185
- }
186
-
187
- /**
188
- * Generate a unique site ID
189
- */
190
- function generateSiteId() {
191
- const random = crypto.randomBytes(4).toString('hex');
192
- return `site_${random}`;
193
- }
194
-
195
- module.exports = { analyzeCodebase };
102
+ module.exports = { scanCodebase };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merlean/analyzer",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "AI Bot codebase analyzer - generates site maps for AI assistant integration",
5
5
  "keywords": ["ai", "bot", "analyzer", "claude", "anthropic", "widget"],
6
6
  "author": "zmaren",
@@ -38,8 +38,6 @@
38
38
  "node": ">=18.0.0"
39
39
  },
40
40
  "dependencies": {
41
- "@anthropic-ai/sdk": "^0.39.0",
42
- "dotenv": "^16.3.1",
43
41
  "glob": "^10.3.10"
44
42
  },
45
43
  "devDependencies": {
package/lib/uploader.js DELETED
@@ -1,37 +0,0 @@
1
- /**
2
- * Upload site map to AI Bot backend
3
- */
4
-
5
- async function uploadMap(backendUrl, siteMap, token) {
6
- try {
7
- const url = `${backendUrl.replace(/\/$/, '')}/api/sites`;
8
-
9
- const headers = {
10
- 'Content-Type': 'application/json'
11
- };
12
-
13
- if (token) {
14
- headers['Authorization'] = `Bearer ${token}`;
15
- }
16
-
17
- const response = await fetch(url, {
18
- method: 'POST',
19
- headers,
20
- body: JSON.stringify(siteMap)
21
- });
22
-
23
- if (!response.ok) {
24
- const error = await response.text();
25
- return { success: false, error: `HTTP ${response.status}: ${error}` };
26
- }
27
-
28
- const data = await response.json();
29
- return { success: true, data };
30
-
31
- } catch (error) {
32
- return { success: false, error: error.message };
33
- }
34
- }
35
-
36
- module.exports = { uploadMap };
37
-