@merlean/analyzer 2.3.0 ā 3.0.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/README.md +75 -0
- package/bin/cli.js +51 -171
- package/package.json +10 -24
- package/bin/wrapper.js +0 -51
- package/lib/analyzer.js +0 -1344
- package/scripts/postinstall.js +0 -156
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# @merlean/analyzer
|
|
2
|
+
|
|
3
|
+
AI-powered codebase analyzer that generates site maps for the Merlean AI assistant widget.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @merlean/analyzer
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Analyze current directory
|
|
15
|
+
merleanalyze --name "My App"
|
|
16
|
+
|
|
17
|
+
# Analyze specific path
|
|
18
|
+
merleanalyze ./my-project --name "My App"
|
|
19
|
+
|
|
20
|
+
# Merge into existing site map
|
|
21
|
+
merleanalyze ./admin-panel --merge-with site_abc123
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or use with npx (no installation required):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx @merlean/analyzer ./my-project --name "My App"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Options
|
|
31
|
+
|
|
32
|
+
| Option | Description |
|
|
33
|
+
|--------|-------------|
|
|
34
|
+
| `--name, -n <name>` | Site name (required for new analysis) |
|
|
35
|
+
| `--merge-with, -m <siteId>` | Merge into existing site map |
|
|
36
|
+
| `--backend, -b <url>` | Custom backend URL |
|
|
37
|
+
| `--output, -o <file>` | Save site map locally |
|
|
38
|
+
| `--help, -h` | Show help |
|
|
39
|
+
|
|
40
|
+
## Supported Frameworks
|
|
41
|
+
|
|
42
|
+
- **JavaScript/TypeScript**: Express, Fastify, NestJS, React, Vue, Angular
|
|
43
|
+
- **PHP**: CodeIgniter, Laravel, Symfony
|
|
44
|
+
- **Python**: Flask, FastAPI, Django
|
|
45
|
+
- **Ruby**: Rails, Sinatra
|
|
46
|
+
- **Go**: Gin, Echo, Chi
|
|
47
|
+
- **Java/Kotlin**: Spring Boot
|
|
48
|
+
|
|
49
|
+
## How It Works
|
|
50
|
+
|
|
51
|
+
1. Scans your codebase for API patterns
|
|
52
|
+
2. Extracts route definitions and request schemas
|
|
53
|
+
3. Uploads to Merlean backend for AI analysis
|
|
54
|
+
4. Returns a site ID for widget integration
|
|
55
|
+
|
|
56
|
+
## Integration
|
|
57
|
+
|
|
58
|
+
After analysis, add the widget to your site:
|
|
59
|
+
|
|
60
|
+
```html
|
|
61
|
+
<script src="https://ai-bot-backend.fly.dev/bot.js" data-site-id="YOUR_SITE_ID"></script>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Platform Support
|
|
65
|
+
|
|
66
|
+
Pre-compiled binaries are available for:
|
|
67
|
+
- macOS (Apple Silicon / ARM64)
|
|
68
|
+
- macOS (Intel / x64)
|
|
69
|
+
- Linux (x64)
|
|
70
|
+
- Windows (x64)
|
|
71
|
+
|
|
72
|
+
## Links
|
|
73
|
+
|
|
74
|
+
- [Documentation](https://github.com/zmaren/merlean#readme)
|
|
75
|
+
- [Report Issues](https://github.com/zmaren/merlean/issues)
|
package/bin/cli.js
CHANGED
|
@@ -1,187 +1,67 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Merlean Analyzer
|
|
4
|
+
* Merlean Analyzer - Platform Binary Loader
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* npx @merlean/analyzer ./my-project --name "My App"
|
|
6
|
+
* This wrapper loads the platform-specific binary for your OS/architecture.
|
|
7
|
+
* The actual analysis logic is compiled into native binaries.
|
|
11
8
|
*/
|
|
12
9
|
|
|
13
|
-
const {
|
|
10
|
+
const { spawn } = require('child_process');
|
|
14
11
|
const path = require('path');
|
|
15
12
|
const fs = require('fs');
|
|
16
13
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (arg === '--name' || arg === '-n') {
|
|
34
|
-
options.name = args[++i];
|
|
35
|
-
} else if (arg === '--backend' || arg === '-b') {
|
|
36
|
-
options.backend = args[++i];
|
|
37
|
-
} else if (arg === '--output' || arg === '-o') {
|
|
38
|
-
options.output = args[++i];
|
|
39
|
-
} else if (arg === '--merge-with' || arg === '-m') {
|
|
40
|
-
options.mergeWith = args[++i];
|
|
41
|
-
} else if (arg === '--help' || arg === '-h') {
|
|
42
|
-
printHelp();
|
|
43
|
-
process.exit(0);
|
|
44
|
-
} else if (!arg.startsWith('-') && !options.path) {
|
|
45
|
-
options.path = arg;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return options;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function printHelp() {
|
|
53
|
-
console.log(`
|
|
54
|
-
Merlean Analyzer - AI-powered codebase analysis
|
|
55
|
-
|
|
56
|
-
Usage:
|
|
57
|
-
npx @merlean/analyzer --name <name>
|
|
58
|
-
npx @merlean/analyzer <path> --name <name>
|
|
59
|
-
|
|
60
|
-
Arguments:
|
|
61
|
-
<path> Path to codebase (default: current directory)
|
|
62
|
-
|
|
63
|
-
Options:
|
|
64
|
-
--name, -n <name> Site name (required for new analysis)
|
|
65
|
-
--merge-with, -m <siteId> Merge into existing site map (appends routes/forms/actions)
|
|
66
|
-
--backend, -b <url> Backend URL (default: ${DEFAULT_BACKEND})
|
|
67
|
-
--output, -o <file> Save site map locally (optional)
|
|
68
|
-
--help, -h Show this help
|
|
69
|
-
|
|
70
|
-
Examples:
|
|
71
|
-
# Analyze current directory
|
|
72
|
-
npx @merlean/analyzer --name "My App"
|
|
73
|
-
|
|
74
|
-
# Analyze specific path
|
|
75
|
-
npx @merlean/analyzer ./my-app --name "My App"
|
|
76
|
-
|
|
77
|
-
# Merge another frontend into existing site map
|
|
78
|
-
npx @merlean/analyzer ./admin-panel --merge-with site_abc123
|
|
79
|
-
|
|
80
|
-
# Use custom backend (for local dev)
|
|
81
|
-
npx @merlean/analyzer --name "My App" --backend http://localhost:3004
|
|
82
|
-
`);
|
|
14
|
+
const PLATFORMS = {
|
|
15
|
+
'darwin-arm64': '@merlean/analyzer-darwin-arm64',
|
|
16
|
+
'darwin-x64': '@merlean/analyzer-darwin-x64',
|
|
17
|
+
'linux-x64': '@merlean/analyzer-linux-x64',
|
|
18
|
+
'win32-x64': '@merlean/analyzer-win32-x64',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const platformKey = `${process.platform}-${process.arch}`;
|
|
22
|
+
const packageName = PLATFORMS[platformKey];
|
|
23
|
+
|
|
24
|
+
if (!packageName) {
|
|
25
|
+
console.error(`\nā Unsupported platform: ${platformKey}`);
|
|
26
|
+
console.error(`\nSupported platforms:`);
|
|
27
|
+
Object.keys(PLATFORMS).forEach(p => console.error(` - ${p}`));
|
|
28
|
+
console.error(`\nPlease open an issue at https://github.com/zmaren/merlean/issues`);
|
|
29
|
+
process.exit(1);
|
|
83
30
|
}
|
|
84
31
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
console.error('ā Error: --name is required (or use --merge-with to merge into existing site)');
|
|
93
|
-
console.log(' Run with --help for usage');
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Default to current directory if no path provided
|
|
98
|
-
const codebasePath = path.resolve(options.path || '.');
|
|
32
|
+
let binaryPath;
|
|
33
|
+
try {
|
|
34
|
+
// Find the platform-specific package
|
|
35
|
+
const packagePath = require.resolve(`${packageName}/package.json`);
|
|
36
|
+
const packageDir = path.dirname(packagePath);
|
|
37
|
+
const binaryName = process.platform === 'win32' ? 'merleanalyze.exe' : 'merleanalyze';
|
|
38
|
+
binaryPath = path.join(packageDir, 'bin', binaryName);
|
|
99
39
|
|
|
100
|
-
if (!fs.existsSync(
|
|
101
|
-
|
|
102
|
-
process.exit(1);
|
|
40
|
+
if (!fs.existsSync(binaryPath)) {
|
|
41
|
+
throw new Error('Binary not found in package');
|
|
103
42
|
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.error(`\nā Failed to find binary for ${platformKey}`);
|
|
45
|
+
console.error(`\nPackage: ${packageName}`);
|
|
46
|
+
console.error(`Error: ${e.message}`);
|
|
47
|
+
console.error(`\nTry reinstalling:`);
|
|
48
|
+
console.error(` npm install -g @merlean/analyzer`);
|
|
49
|
+
console.error(`\nOr with npx (downloads fresh):`);
|
|
50
|
+
console.error(` npx @merlean/analyzer@latest --help`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
104
53
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
console.log(`š Site name: ${options.name}`);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
// Scan codebase locally
|
|
117
|
-
const scanResult = await scanCodebase(codebasePath);
|
|
118
|
-
// Handle both old format (array) and new format (object with files + preExtractedRoutes)
|
|
119
|
-
const fileContents = Array.isArray(scanResult) ? scanResult : scanResult.files;
|
|
120
|
-
const preExtractedRoutes = Array.isArray(scanResult) ? [] : (scanResult.preExtractedRoutes || []);
|
|
121
|
-
|
|
122
|
-
console.log(`\nš¤ Uploading to backend for analysis...`);
|
|
123
|
-
|
|
124
|
-
// Build request body
|
|
125
|
-
const requestBody = {
|
|
126
|
-
siteName: options.name,
|
|
127
|
-
files: fileContents,
|
|
128
|
-
preExtractedRoutes // Include convention-based routes
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// Add merge parameter if specified
|
|
132
|
-
if (options.mergeWith) {
|
|
133
|
-
requestBody.mergeWithSiteId = options.mergeWith;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Send to backend for LLM analysis
|
|
137
|
-
const response = await fetch(`${options.backend}/api/analyze`, {
|
|
138
|
-
method: 'POST',
|
|
139
|
-
headers: { 'Content-Type': 'application/json' },
|
|
140
|
-
body: JSON.stringify(requestBody)
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
if (!response.ok) {
|
|
144
|
-
const error = await response.text();
|
|
145
|
-
throw new Error(`Backend error: ${response.status} ${error}`);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const result = await response.json();
|
|
149
|
-
|
|
150
|
-
if (options.mergeWith) {
|
|
151
|
-
console.log('\nā
Merge complete!');
|
|
152
|
-
console.log('\nš Updated Summary:');
|
|
153
|
-
} else {
|
|
154
|
-
console.log('\nā
Analysis complete!');
|
|
155
|
-
console.log('\nš Summary:');
|
|
156
|
-
}
|
|
157
|
-
console.log(` Site ID: ${result.siteId}`);
|
|
158
|
-
console.log(` Framework: ${result.framework || 'Unknown'}`);
|
|
159
|
-
console.log(` Routes: ${result.routes?.length || 0}`);
|
|
160
|
-
console.log(` Forms: ${result.forms?.length || 0}`);
|
|
161
|
-
console.log(` Actions: ${result.actions?.length || 0}`);
|
|
162
|
-
|
|
163
|
-
if (result.merged) {
|
|
164
|
-
console.log(`\n š Merged from: ${result.merged.sourcesCount} source(s)`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Save locally if requested
|
|
168
|
-
if (options.output) {
|
|
169
|
-
const outputPath = path.resolve(options.output);
|
|
170
|
-
fs.writeFileSync(outputPath, JSON.stringify(result, null, 2));
|
|
171
|
-
console.log(`\nš¾ Saved to: ${outputPath}`);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Show integration instructions
|
|
175
|
-
console.log('\n' + 'ā'.repeat(50));
|
|
176
|
-
console.log('\nš Integration Ready!\n');
|
|
177
|
-
console.log('Add this to your website:\n');
|
|
178
|
-
console.log(`<script src="${options.backend}/bot.js" data-site-id="${result.siteId}"></script>`);
|
|
179
|
-
console.log('\n' + 'ā'.repeat(50) + '\n');
|
|
54
|
+
// Run the binary with all arguments
|
|
55
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
56
|
+
stdio: 'inherit',
|
|
57
|
+
env: process.env
|
|
58
|
+
});
|
|
180
59
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
60
|
+
child.on('error', (err) => {
|
|
61
|
+
console.error(`\nā Failed to execute binary: ${err.message}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
186
64
|
|
|
187
|
-
|
|
65
|
+
child.on('exit', (code) => {
|
|
66
|
+
process.exit(code || 0);
|
|
67
|
+
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@merlean/analyzer",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "AI
|
|
5
|
-
"keywords": ["ai", "bot", "analyzer", "claude", "anthropic", "widget"],
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"description": "AI-powered codebase analyzer - generates site maps for AI assistant integration",
|
|
5
|
+
"keywords": ["ai", "bot", "analyzer", "claude", "anthropic", "widget", "merlean"],
|
|
6
6
|
"author": "zmaren",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -15,32 +15,18 @@
|
|
|
15
15
|
"access": "public"
|
|
16
16
|
},
|
|
17
17
|
"bin": {
|
|
18
|
-
"
|
|
18
|
+
"merleanalyze": "./bin/cli.js"
|
|
19
19
|
},
|
|
20
|
-
"main": "lib/analyzer.js",
|
|
21
20
|
"files": [
|
|
22
|
-
"bin/"
|
|
23
|
-
"lib/",
|
|
24
|
-
"scripts/"
|
|
21
|
+
"bin/"
|
|
25
22
|
],
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
"pkg": {
|
|
32
|
-
"scripts": ["lib/**/*.js", "bin/cli.js"],
|
|
33
|
-
"assets": [],
|
|
34
|
-
"targets": ["node18-linux-x64", "node18-macos-x64", "node18-macos-arm64", "node18-win-x64"],
|
|
35
|
-
"outputPath": "dist"
|
|
23
|
+
"optionalDependencies": {
|
|
24
|
+
"@merlean/analyzer-darwin-arm64": "3.0.1",
|
|
25
|
+
"@merlean/analyzer-darwin-x64": "3.0.1",
|
|
26
|
+
"@merlean/analyzer-linux-x64": "3.0.1",
|
|
27
|
+
"@merlean/analyzer-win32-x64": "3.0.1"
|
|
36
28
|
},
|
|
37
29
|
"engines": {
|
|
38
30
|
"node": ">=18.0.0"
|
|
39
|
-
},
|
|
40
|
-
"dependencies": {
|
|
41
|
-
"glob": "^10.3.10"
|
|
42
|
-
},
|
|
43
|
-
"devDependencies": {
|
|
44
|
-
"pkg": "^5.8.1"
|
|
45
31
|
}
|
|
46
32
|
}
|
package/bin/wrapper.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* AI Bot Analyzer - Wrapper Script
|
|
5
|
-
*
|
|
6
|
-
* Attempts to run the compiled binary, falls back to source if not available.
|
|
7
|
-
* This allows the package to work even if binary download failed.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const { spawn, execFileSync } = require('child_process');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const fs = require('fs');
|
|
13
|
-
|
|
14
|
-
const binDir = __dirname;
|
|
15
|
-
const binaryName = process.platform === 'win32' ? 'ai-bot-analyze.exe' : 'ai-bot-analyze';
|
|
16
|
-
const binaryPath = path.join(binDir, binaryName);
|
|
17
|
-
const sourcePath = path.join(binDir, 'cli.js');
|
|
18
|
-
const sourceModeFlagPath = path.join(binDir, '..', '.source-mode');
|
|
19
|
-
|
|
20
|
-
// Check if we should use source mode
|
|
21
|
-
const useSourceMode = fs.existsSync(sourceModeFlagPath) || !fs.existsSync(binaryPath);
|
|
22
|
-
|
|
23
|
-
if (useSourceMode) {
|
|
24
|
-
// Run source directly with Node.js
|
|
25
|
-
require('./cli.js');
|
|
26
|
-
} else {
|
|
27
|
-
// Run compiled binary
|
|
28
|
-
const args = process.argv.slice(2);
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
const result = spawn(binaryPath, args, {
|
|
32
|
-
stdio: 'inherit',
|
|
33
|
-
env: process.env
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
result.on('error', (err) => {
|
|
37
|
-
// If binary fails, fall back to source
|
|
38
|
-
console.error('Binary execution failed, falling back to source mode...');
|
|
39
|
-
require('./cli.js');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
result.on('exit', (code) => {
|
|
43
|
-
process.exit(code || 0);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
} catch (err) {
|
|
47
|
-
// Fall back to source mode
|
|
48
|
-
require('./cli.js');
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|