@merlean/analyzer 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,93 +48,101 @@ 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 --name <name>
55
+ npx @merlean/analyzer <path> --name <name>
66
56
 
67
57
  Arguments:
68
- <path> Path to codebase to analyze
58
+ <path> Path to codebase (default: current directory)
69
59
 
70
60
  Options:
71
61
  --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
62
+ --backend, -b <url> Backend URL (default: ${DEFAULT_BACKEND})
63
+ --output, -o <file> Save site map locally (optional)
75
64
  --help, -h Show this help
76
65
 
77
66
  Examples:
78
- # Analyze and upload to production
79
- ai-bot-analyze ./my-app --name "My App" --upload https://ai-bot-backend.fly.dev
67
+ # Analyze current directory
68
+ npx @merlean/analyzer --name "My App"
80
69
 
81
- # Analyze with auth token
82
- ai-bot-analyze ./my-app --name "My App" --upload https://backend.fly.dev --token secret123
70
+ # Analyze specific path
71
+ npx @merlean/analyzer ./my-app --name "My App"
83
72
 
84
- # Just generate local file
85
- ai-bot-analyze ./my-app --name "My App" --output ./map.json
73
+ # Use custom backend (for local dev)
74
+ npx @merlean/analyzer --name "My App" --backend http://localhost:3004
86
75
  `);
87
76
  }
88
77
 
89
78
  async function main() {
90
- console.log('\nšŸ” AI Bot Analyzer\n');
79
+ console.log('\nšŸ” Merlean Analyzer\n');
91
80
 
92
81
  const options = parseArgs();
93
82
 
94
83
  // Validate required args
95
- if (!options.path) {
96
- console.error('āŒ Error: Path to codebase is required');
97
- console.log(' Run with --help for usage');
98
- process.exit(1);
99
- }
100
-
101
84
  if (!options.name) {
102
85
  console.error('āŒ Error: --name is required');
86
+ console.log(' Run with --help for usage');
103
87
  process.exit(1);
104
88
  }
105
89
 
106
- const codebasePath = path.resolve(options.path);
90
+ // Default to current directory if no path provided
91
+ const codebasePath = path.resolve(options.path || '.');
107
92
 
108
93
  if (!fs.existsSync(codebasePath)) {
109
94
  console.error(`āŒ Error: Path does not exist: ${codebasePath}`);
110
95
  process.exit(1);
111
96
  }
112
97
 
113
- console.log(`šŸ“ Analyzing: ${codebasePath}`);
98
+ console.log(`šŸ“ Scanning: ${codebasePath}`);
114
99
  console.log(`šŸ“› Site name: ${options.name}`);
115
100
 
116
101
  try {
117
- // Analyze codebase
118
- const siteMap = await analyzeCodebase(codebasePath, options.name);
102
+ // Scan codebase locally
103
+ const fileContents = await scanCodebase(codebasePath);
119
104
 
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.');
105
+ console.log(`\nšŸ“¤ Uploading to backend for analysis...`);
106
+
107
+ // Send to backend for LLM analysis
108
+ const response = await fetch(`${options.backend}/api/analyze`, {
109
+ method: 'POST',
110
+ headers: { 'Content-Type': 'application/json' },
111
+ body: JSON.stringify({
112
+ siteName: options.name,
113
+ files: fileContents
114
+ })
115
+ });
116
+
117
+ if (!response.ok) {
118
+ const error = await response.text();
119
+ throw new Error(`Backend error: ${response.status} ${error}`);
147
120
  }
148
121
 
122
+ const result = await response.json();
123
+
124
+ console.log('\nāœ… Analysis complete!');
125
+ console.log('\nšŸ“Š Summary:');
126
+ console.log(` Site ID: ${result.siteId}`);
127
+ console.log(` Framework: ${result.framework || 'Unknown'}`);
128
+ console.log(` Routes: ${result.routes?.length || 0}`);
129
+ console.log(` Forms: ${result.forms?.length || 0}`);
130
+ console.log(` Actions: ${result.actions?.length || 0}`);
131
+
132
+ // Save locally if requested
133
+ if (options.output) {
134
+ const outputPath = path.resolve(options.output);
135
+ fs.writeFileSync(outputPath, JSON.stringify(result, null, 2));
136
+ console.log(`\nšŸ’¾ Saved to: ${outputPath}`);
137
+ }
138
+
139
+ // Show integration instructions
140
+ console.log('\n' + '─'.repeat(50));
141
+ console.log('\nšŸŽ‰ Integration Ready!\n');
142
+ console.log('Add this to your website:\n');
143
+ console.log(`<script src="${options.backend}/bot.js" data-site-id="${result.siteId}"></script>`);
144
+ console.log('\n' + '─'.repeat(50) + '\n');
145
+
149
146
  } catch (error) {
150
147
  console.error(`\nāŒ Error: ${error.message}`);
151
148
  process.exit(1);
@@ -153,4 +150,3 @@ async function main() {
153
150
  }
154
151
 
155
152
  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.1",
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
-