├── middleware
├── security.js
├── validateRequest.js
└── rateLimiter.js
├── utils
├── ipTracker.js
├── githubUtils.js
└── analysisUtils.js
├── github-actions-deploy.yml
├── github-actions-example.yml
├── .gitignore
├── public
├── images
│ └── image.jpg
├── index.html,public
│ └── analysis.html,public
│ │ └── history.html,public
│ │ ├── insights.html
│ │ └── insights.html,public
│ │ └── search.html
├── analysis.html,public
│ ├── explore.html
│ └── history.html,public
│ │ └── insights.html,public
│ │ └── search.html
├── index.html
├── css
│ ├── components
│ │ ├── buttons.css
│ │ ├── forms.css
│ │ ├── cards.css
│ │ └── header.css
│ ├── base
│ │ ├── reset.css
│ │ ├── typography.css
│ │ └── variables.css
│ ├── layouts
│ │ └── grid.css
│ ├── explore.css
│ ├── header.css
│ └── whitepaper.css
├── site.webmanifest
├── components
│ └── header.html
├── sitecheck.html
├── js
│ ├── explore.js
│ ├── whitepaper.js
│ ├── header.js
│ └── main.js
├── whitepaper.html
├── templates
│ └── header.html
└── search.html
├── services
├── queueService.js
├── scheduler.js
├── schedulerService.js
├── twitterConnector.js
├── historyManager.js
├── vectorStoreService.js
├── siteAnalyzer.js
├── analyzer.js
├── claudeTools.js
└── insightsService.js
├── server
├── models
│ └── Analysis.js
├── index.js
├── utils
│ └── repoChecker.js
├── routes
│ └── api.js
└── discord
│ └── bot.js
├── server.js
├── command
├── models
├── ApiKey.js
├── Repository.js
└── TweetHistory.js
├── bots
└── archive
│ └── styles.css
├── cleanup.js
├── app.js
├── scripts
├── cleanup.js
├── generate-favicon.js
├── testSiteAnalysis.js
├── quickTest.js
└── debugScores.js
├── config
├── claudeConfig.js
├── config.js
└── database.js
├── package.json
├── twitter-cookies.json
├── views
└── history.html
├── index.js
├── tests
└── claudeTest.js
└── README.md
/middleware/security.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/utils/ipTracker.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/github-actions-deploy.yml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/github-actions-example.yml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .env
3 | .DS_Store
--------------------------------------------------------------------------------
/public/images/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mentallyblue/ai0x/HEAD/public/images/image.jpg
--------------------------------------------------------------------------------
/services/queueService.js:
--------------------------------------------------------------------------------
1 | // This file can be deleted as we're not using the queue system anymore
2 | module.exports = {};
--------------------------------------------------------------------------------
/public/index.html,public/analysis.html,public/history.html,public/insights.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/analysis.html,public/explore.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/server/models/Analysis.js:
--------------------------------------------------------------------------------
1 | // Add indexes for commonly queried fields
2 | const AnalysisSchema = new Schema({
3 | // ... existing schema ...
4 | }, {
5 | timestamps: true
6 | });
7 |
8 | // Add compound index for sorting and filtering
9 | AnalysisSchema.index({ lastAnalyzed: -1, language: 1 });
10 | AnalysisSchema.index({ 'analysis.finalLegitimacyScore': -1 });
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const discordBot = require('./discord/bot');
2 |
3 | // Start Discord bot
4 | discordBot.start();
5 |
6 | // Add error handlers
7 | process.on('unhandledRejection', (error) => {
8 | console.error('Unhandled promise rejection:', error);
9 | });
10 |
11 | process.on('uncaughtException', (error) => {
12 | console.error('Uncaught exception:', error);
13 | });
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const limiter = require('./middleware/rateLimiter');
3 | const { validateGitHubUrl } = require('./middleware/validateRequest');
4 |
5 | // Apply rate limiter to all routes
6 | app.use('/api/', limiter);
7 |
8 | // Apply validation to analyze endpoint
9 | app.post('/api/analyze', validateGitHubUrl, async (req, res) => {
10 | // ... existing code
11 | });
--------------------------------------------------------------------------------
/command:
--------------------------------------------------------------------------------
1 | mkdir -p public/css/components
2 | mkdir -p public/css/layouts
3 | mkdir -p public/css/base
4 |
5 | # Create initial component files
6 | touch public/css/components/header.css
7 | touch public/css/components/cards.css
8 | touch public/css/components/buttons.css
9 | touch public/css/components/forms.css
10 | touch public/css/layouts/grid.css
11 | touch public/css/base/reset.css
12 | touch public/css/base/typography.css
--------------------------------------------------------------------------------
/models/ApiKey.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const apiKeySchema = new mongoose.Schema({
4 | key: { type: String, required: true, unique: true },
5 | owner: String,
6 | requestsToday: { type: Number, default: 0 },
7 | lastRequest: Date,
8 | dailyLimit: { type: Number, default: 50 },
9 | isActive: { type: Boolean, default: true }
10 | }, {
11 | timestamps: true
12 | });
13 |
14 | module.exports = mongoose.model('ApiKey', apiKeySchema);
--------------------------------------------------------------------------------
/bots/archive/styles.css:
--------------------------------------------------------------------------------
1 | /* Base */
2 | @import 'base/variables.css';
3 | @import 'base/reset.css';
4 | @import 'base/typography.css';
5 |
6 | /* Layouts */
7 | @import 'layouts/grid.css';
8 |
9 | /* Components */
10 | @import 'components/header.css';
11 | @import 'components/cards.css';
12 | @import 'components/buttons.css';
13 | @import 'components/forms.css';
14 |
15 | /* Keep all existing styles until we properly modularize them */
16 | /* Original styles.css content stays here */
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | AI0x | Redirecting...
7 |
8 |
11 |
12 |
13 | Redirecting to analysis page...
14 |
15 |
--------------------------------------------------------------------------------
/middleware/validateRequest.js:
--------------------------------------------------------------------------------
1 | const validateGitHubUrl = (req, res, next) => {
2 | const { repoUrl } = req.body;
3 |
4 | // Basic GitHub URL validation
5 | const githubUrlPattern = /^https?:\/\/github\.com\/[\w-]+\/[\w.-]+\/?$/;
6 |
7 | if (!repoUrl || !githubUrlPattern.test(repoUrl)) {
8 | return res.status(400).json({
9 | error: 'Invalid GitHub repository URL'
10 | });
11 | }
12 |
13 | next();
14 | };
15 |
16 | module.exports = { validateGitHubUrl };
--------------------------------------------------------------------------------
/public/css/components/buttons.css:
--------------------------------------------------------------------------------
1 | .button {
2 | padding: 0.5rem 1rem;
3 | border-radius: 4px;
4 | font-size: 0.75rem;
5 | font-weight: 500;
6 | cursor: pointer;
7 | transition: opacity 0.2s ease;
8 | }
9 |
10 | .button-primary {
11 | background: var(--accent);
12 | color: var(--bg-primary);
13 | }
14 |
15 | .button-secondary {
16 | background: var(--bg-secondary);
17 | color: var(--text-primary);
18 | border: 1px solid var(--border);
19 | }
20 |
21 | .button:hover {
22 | opacity: 0.9;
23 | }
--------------------------------------------------------------------------------
/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "AI0x",
3 | "short_name": "AI0x",
4 | "icons": [
5 | {
6 | "src": "/images/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/images/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#00ff9d",
17 | "background_color": "#0a0a0a",
18 | "display": "standalone"
19 | }
--------------------------------------------------------------------------------
/public/analysis.html,public/history.html,public/insights.html,public/search.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/css/base/reset.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | font-family: 'JetBrains Mono', monospace;
9 | background-color: var(--bg-primary);
10 | color: var(--text-primary);
11 | line-height: 1.6;
12 | min-height: 100vh;
13 | }
14 |
15 | a {
16 | text-decoration: none;
17 | color: inherit;
18 | }
19 |
20 | button {
21 | background: none;
22 | border: none;
23 | font: inherit;
24 | cursor: pointer;
25 | }
26 |
27 | input, select, textarea {
28 | font: inherit;
29 | border: none;
30 | background: none;
31 | outline: none;
32 | }
--------------------------------------------------------------------------------
/cleanup.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const mongoose = require('mongoose');
3 | const Repository = require('./models/Repository');
4 |
5 | async function cleanup() {
6 | try {
7 | await mongoose.connect(process.env.MONGODB_URI, {
8 | useNewUrlParser: true,
9 | useUnifiedTopology: true
10 | });
11 | console.log('Connected to MongoDB');
12 |
13 | const result = await Repository.deleteMany({});
14 | console.log(`Deleted ${result.deletedCount} repository records`);
15 |
16 | } catch (error) {
17 | console.error('Error during cleanup:', error);
18 | } finally {
19 | await mongoose.connection.close();
20 | console.log('Cleanup complete, connection closed');
21 | }
22 | }
23 |
24 | cleanup();
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const app = express();
3 | const server = require('http').createServer(app);
4 | const io = require('socket.io')(server);
5 |
6 | // Make io available globally
7 | global.io = io;
8 |
9 | // ... rest of your Express configuration ...
10 |
11 | server.listen(port, () => {
12 | console.log(`Server running on port ${port}`);
13 | });
14 |
15 | io.on('connection', (socket) => {
16 | // Send initial queue status
17 | socket.emit('queueUpdate', { count: getQueueCount() });
18 |
19 | // Handle analysis requests
20 | socket.on('analyzeRepository', async (data) => {
21 | // Your existing analysis logic
22 | });
23 | });
24 |
25 | // Update queue status whenever it changes
26 | function updateQueueStatus() {
27 | io.emit('queueUpdate', { count: getQueueCount() });
28 | }
--------------------------------------------------------------------------------
/scripts/cleanup.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const mongoose = require('mongoose');
3 | const Repository = require('../models/Repository');
4 |
5 | async function cleanup() {
6 | try {
7 | // Connect to MongoDB
8 | await mongoose.connect(process.env.MONGODB_URI);
9 | console.log('Connected to MongoDB');
10 |
11 | // Clear all repository analyses
12 | await Repository.deleteMany({});
13 | console.log('✅ Cleared all repository analyses');
14 |
15 | // Clear any in-memory queue data by restarting the server
16 | console.log('\n🔄 Now restart the server with:');
17 | console.log('npm start');
18 |
19 | } catch (error) {
20 | console.error('❌ Cleanup error:', error);
21 | } finally {
22 | await mongoose.disconnect();
23 | console.log('Disconnected from MongoDB');
24 | }
25 | }
26 |
27 | cleanup();
--------------------------------------------------------------------------------
/config/claudeConfig.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | tweetConfig: {
3 | maxLength: 180,
4 | linkLength: 23,
5 | spacing: 5,
6 | maxToolAttempts: 3,
7 | historySize: 50,
8 | compressConfig: {
9 | maxFeatures: 3,
10 | maxTechStack: 5,
11 | maxSummaryLength: 150
12 | }
13 | },
14 |
15 | analysisFields: {
16 | // Configure which fields get passed to Claude
17 | required: ['fullName', 'analysis.finalLegitimacyScore'],
18 | technical: [
19 | 'analysis.codeReview.techStack',
20 | 'analysis.codeReview.logicFlow',
21 | 'analysis.codeReview.aiAnalysis.score',
22 | 'analysis.detailedScores'
23 | ],
24 | context: [
25 | 'analysis.summary',
26 | 'analysis.codeReview.investmentRanking',
27 | 'stars',
28 | 'forks'
29 | ]
30 | }
31 | };
--------------------------------------------------------------------------------
/public/css/base/typography.css:
--------------------------------------------------------------------------------
1 | h1, h2, h3, h4, h5, h6 {
2 | font-weight: 700;
3 | line-height: 1.2;
4 | margin-bottom: 1rem;
5 | }
6 |
7 | h1 {
8 | font-size: 2rem;
9 | color: var(--accent);
10 | }
11 |
12 | h2 {
13 | font-size: 1.5rem;
14 | color: var(--text-primary);
15 | }
16 |
17 | h3 {
18 | font-size: 1.25rem;
19 | color: var(--text-primary);
20 | }
21 |
22 | p {
23 | margin-bottom: 1rem;
24 | color: var(--text-secondary);
25 | }
26 |
27 | code {
28 | font-family: 'JetBrains Mono', monospace;
29 | background: var(--bg-secondary);
30 | padding: 0.2em 0.4em;
31 | border-radius: 4px;
32 | font-size: 0.9em;
33 | }
34 |
35 | pre {
36 | background: var(--bg-secondary);
37 | padding: 1rem;
38 | border-radius: 8px;
39 | overflow-x: auto;
40 | margin: 1rem 0;
41 | border: 1px solid var(--border);
42 | }
43 |
44 | pre code {
45 | background: none;
46 | padding: 0;
47 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "github-repo-analyzer",
3 | "version": "1.0.0",
4 | "description": "GitHub repository analyzer using LLM",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node --no-deprecation index.js",
8 | "dev": "nodemon index.js"
9 | },
10 | "dependencies": {
11 | "@anthropic-ai/sdk": "^0.14.1",
12 | "@octokit/rest": "^19.0.13",
13 | "agent-twitter-client": "^0.0.18",
14 | "axios": "^1.7.9",
15 | "bull": "^4.16.5",
16 | "connect-mongo": "^5.1.0",
17 | "discord.js": "^14.17.2",
18 | "dotenv": "^16.4.1",
19 | "express": "^4.18.2",
20 | "express-rate-limit": "^7.1.5",
21 | "express-session": "^1.18.1",
22 | "ioredis": "^5.4.2",
23 | "marked": "^12.0.0",
24 | "mongoose": "^8.2.0",
25 | "node-cron": "^3.0.3",
26 | "rate-limit-redis": "^4.2.0",
27 | "scheduler": "^0.25.0",
28 | "socket.io": "^4.8.1",
29 | "telegraf": "^4.16.3"
30 | },
31 | "devDependencies": {
32 | "nodemon": "^3.0.3"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/scripts/generate-favicon.js:
--------------------------------------------------------------------------------
1 | const sharp = require('sharp');
2 | const toIco = require('to-ico');
3 | const fs = require('fs').promises;
4 | const path = require('path');
5 |
6 | async function generateFavicons() {
7 | const sourceImage = path.join(__dirname, '../public/images/image.jpg');
8 | const outputDir = path.join(__dirname, '../public/images');
9 |
10 | // Generate PNG favicons
11 | await sharp(sourceImage)
12 | .resize(32, 32)
13 | .toFile(path.join(outputDir, 'logo32.png'));
14 |
15 | await sharp(sourceImage)
16 | .resize(16, 16)
17 | .toFile(path.join(outputDir, 'logo16.png'));
18 |
19 | // Generate ICO file (contains both 16x16 and 32x32)
20 | const pngBuffers = await Promise.all([
21 | fs.readFile(path.join(outputDir, 'logo16.png')),
22 | fs.readFile(path.join(outputDir, 'logo32.png'))
23 | ]);
24 |
25 | const icoBuffer = await toIco(pngBuffers);
26 | await fs.writeFile(path.join(outputDir, 'favicon.ico'), icoBuffer);
27 | }
28 |
29 | generateFavicons().catch(console.error);
--------------------------------------------------------------------------------
/config/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | RATE_LIMITS: {
3 | FREE_TIER: {
4 | requests_per_day: 5,
5 | concurrent_requests: 2
6 | },
7 | PAID_TIER: {
8 | requests_per_day: 100,
9 | concurrent_requests: 10
10 | },
11 | API_KEY_TIER: {
12 | requests_per_day: 1000,
13 | concurrent_requests: 50
14 | }
15 | },
16 | QUEUE_CONFIG: {
17 | maxQueueSize: 1000, // Maximum items in queue
18 | jobTimeout: 5 * 60 * 1000, // 5 minutes
19 | cleanupInterval: 60 * 1000, // Cleanup every minute
20 | resultsTTL: 60 * 60 * 1000, // Keep results for 1 hour
21 | maxRetries: 3, // Maximum retries per job
22 | retryDelay: 5000, // 5 seconds between retries
23 | maxConcurrent: 10, // Maximum concurrent processing
24 | priorities: {
25 | default: 0,
26 | premium: 1,
27 | urgent: 2
28 | }
29 | },
30 | REDIS_CONFIG: {
31 | retryStrategy: (times) => Math.min(times * 50, 2000)
32 | }
33 | };
--------------------------------------------------------------------------------
/models/Repository.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const repositorySchema = new mongoose.Schema({
4 | fullName: String,
5 | owner: String,
6 | repoName: String,
7 | description: String,
8 | language: String,
9 | stars: Number,
10 | forks: Number,
11 | lastAnalyzed: Date,
12 | analysis: {
13 | detailedScores: {
14 | codeQuality: Number,
15 | projectStructure: Number,
16 | implementation: Number,
17 | documentation: Number
18 | },
19 | legitimacyScore: Number,
20 | trustScore: Number,
21 | finalLegitimacyScore: Number,
22 | codeReview: {
23 | // ... other code review fields
24 | },
25 | fullAnalysis: String,
26 | summary: String
27 | },
28 | summary: String
29 | });
30 |
31 | // Add index for faster cache lookups
32 | repositorySchema.index({ owner: 1, repoName: 1, lastAnalyzed: -1 });
33 |
34 | // Add compound index for common queries
35 | repositorySchema.index({ fullName: 1, lastAnalyzed: -1 });
36 |
37 | module.exports = mongoose.model('Repository', repositorySchema);
--------------------------------------------------------------------------------
/services/scheduler.js:
--------------------------------------------------------------------------------
1 | const cron = require('node-cron');
2 | const TwitterAgent = require('./twitterAgent');
3 |
4 | class Scheduler {
5 | constructor() {
6 | this.twitterAgent = null;
7 | this.init();
8 | }
9 |
10 | async init() {
11 | // Initialize TwitterAgent
12 | this.twitterAgent = new TwitterAgent();
13 | await this.twitterAgent.init();
14 |
15 | // Run immediately on startup
16 | await this.generateTweets();
17 |
18 | // Run every hour in both dev and prod
19 | console.log('🕒 Scheduling tweet generation every hour');
20 | cron.schedule('0 * * * *', () => {
21 | this.generateTweets();
22 | });
23 | }
24 |
25 | async generateTweets() {
26 | try {
27 | const timestamp = new Date().toISOString();
28 | console.log(`[${timestamp}] Generating tweets...`);
29 | await this.twitterAgent.processRecentAnalyses();
30 | } catch (error) {
31 | console.error('Error in tweet generation schedule:', error);
32 | }
33 | }
34 | }
35 |
36 | module.exports = Scheduler;
--------------------------------------------------------------------------------
/public/css/base/variables.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'JetBrains Mono';
3 | font-style: normal;
4 | font-weight: 400;
5 | font-display: swap;
6 | src: url(https://fonts.gstatic.com/s/jetbrainsmono/v13/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxTOlOV.woff2) format('woff2');
7 | }
8 |
9 | @font-face {
10 | font-family: 'JetBrains Mono';
11 | font-style: normal;
12 | font-weight: 700;
13 | font-display: swap;
14 | src: url(https://fonts.gstatic.com/s/jetbrainsmono/v13/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8L6tTOlOV.woff2) format('woff2');
15 | }
16 |
17 | :root {
18 | /* Colors */
19 | --bg-primary: #0d1117;
20 | --bg-secondary: #161b22;
21 | --bg-dark: #0d1117;
22 | --bg-hover: #1c2128;
23 |
24 | /* Text colors */
25 | --text-primary: #e6edf3;
26 | --text-secondary: #7d8590;
27 | --text-tertiary: #636e7b;
28 |
29 | /* Accent colors */
30 | --accent: #eab308;
31 | --border: #30363d;
32 | --error: #f87171;
33 | --success: #22c55e;
34 | --warning: #eab308;
35 |
36 | /* Status colors */
37 | --status-color: var(--text-primary);
38 | --status-bg: rgba(255, 255, 255, 0.05);
39 | --status-border: var(--border);
40 | }
--------------------------------------------------------------------------------
/config/database.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const { init: initTweetHistory } = require('../models/TweetHistory');
3 |
4 | const connectDB = async () => {
5 | try {
6 | // Main application database
7 | await mongoose.connect(process.env.MONGODB_URI, {
8 | useNewUrlParser: true,
9 | useUnifiedTopology: true
10 | });
11 | console.log('MongoDB connected successfully');
12 |
13 | // Create a separate connection for tweet history
14 | const tweetHistoryConnection = mongoose.createConnection(process.env.MONGODB_URI, {
15 | useNewUrlParser: true,
16 | useUnifiedTopology: true,
17 | dbName: 'ai0x_tweets'
18 | });
19 |
20 | // Initialize the TweetHistory model with the connection
21 | const TweetHistory = initTweetHistory(tweetHistoryConnection);
22 |
23 | // Make both available globally if needed
24 | global.tweetHistoryDB = tweetHistoryConnection;
25 | global.TweetHistory = TweetHistory;
26 |
27 | console.log('Tweet history database connected successfully');
28 |
29 | } catch (error) {
30 | console.error('MongoDB connection error:', error);
31 | process.exit(1);
32 | }
33 | };
34 |
35 | module.exports = connectDB;
--------------------------------------------------------------------------------
/middleware/rateLimiter.js:
--------------------------------------------------------------------------------
1 | const rateLimit = require('express-rate-limit');
2 |
3 | // Create a Map to track ongoing analyses per IP
4 | const ongoingAnalyses = new Map();
5 |
6 | // Middleware to check for ongoing analyses
7 | const oneAnalysisPerIP = (req, res, next) => {
8 | const clientIP = req.ip;
9 |
10 | if (ongoingAnalyses.has(clientIP)) {
11 | return res.status(429).json({
12 | error: 'You already have an ongoing analysis. Please wait for it to complete.'
13 | });
14 | }
15 |
16 | // Add IP to tracking
17 | ongoingAnalyses.set(clientIP, Date.now());
18 |
19 | // Remove IP from tracking after timeout (5 minutes max)
20 | setTimeout(() => {
21 | ongoingAnalyses.delete(clientIP);
22 | }, 5 * 60 * 1000);
23 |
24 | next();
25 | };
26 |
27 | // Regular rate limiting
28 | const queueLimiter = rateLimit({
29 | windowMs: 60 * 1000, // 1 minute
30 | max: 5, // 5 requests per minute
31 | message: {
32 | error: 'Too many requests. Please try again later.'
33 | }
34 | });
35 |
36 | // Export both middleware
37 | module.exports = {
38 | queueLimiter,
39 | oneAnalysisPerIP,
40 | // Helper to remove IP from tracking when analysis completes
41 | removeAnalysis: (ip) => ongoingAnalyses.delete(ip)
42 | };
--------------------------------------------------------------------------------
/services/schedulerService.js:
--------------------------------------------------------------------------------
1 | const cron = require('node-cron');
2 | const Repository = require('../models/Repository');
3 | const insightsService = require('./insightsService');
4 |
5 | class SchedulerService {
6 | start() {
7 | // Run every hour
8 | cron.schedule('0 * * * *', async () => {
9 | try {
10 | console.log('Running scheduled insights update...');
11 |
12 | // Get repositories that need updates
13 | const repos = await Repository.find({
14 | $or: [
15 | { lastInsightUpdate: { $lt: new Date(Date.now() - 3600000) } }, // Older than 1 hour
16 | { lastInsightUpdate: { $exists: false } }
17 | ]
18 | });
19 |
20 | for (const repo of repos) {
21 | try {
22 | await insightsService.generateInsights(repo.fullName);
23 | } catch (error) {
24 | console.error(`Error processing repo ${repo.fullName}:`, error);
25 | }
26 | }
27 | } catch (error) {
28 | console.error('Scheduler error:', error);
29 | }
30 | });
31 | }
32 | }
33 |
34 | module.exports = new SchedulerService();
--------------------------------------------------------------------------------
/models/TweetHistory.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const tweetHistorySchema = new mongoose.Schema({
4 | text: {
5 | type: String,
6 | required: true
7 | },
8 | repoName: {
9 | type: String,
10 | required: true,
11 | index: true
12 | },
13 | analysisIds: [{
14 | type: mongoose.Schema.Types.ObjectId,
15 | ref: 'Repository'
16 | }],
17 | analysisUrl: String,
18 | timestamp: {
19 | type: Date,
20 | default: Date.now,
21 | index: true
22 | },
23 | metrics: {
24 | impressions: { type: Number, default: 0 },
25 | engagements: { type: Number, default: 0 },
26 | clicks: { type: Number, default: 0 }
27 | }
28 | }, {
29 | collection: 'tweet_history'
30 | });
31 |
32 | // Index for querying recent tweets
33 | tweetHistorySchema.index({ timestamp: -1 });
34 |
35 | // TTL index to automatically delete old tweets after 30 days
36 | tweetHistorySchema.index({ timestamp: 1 }, { expireAfterSeconds: 30 * 24 * 60 * 60 });
37 |
38 | let TweetHistory;
39 |
40 | module.exports = {
41 | schema: tweetHistorySchema,
42 | init: (connection) => {
43 | if (!TweetHistory) {
44 | TweetHistory = connection.model('TweetHistory', tweetHistorySchema);
45 | }
46 | return TweetHistory;
47 | }
48 | };
--------------------------------------------------------------------------------
/public/css/layouts/grid.css:
--------------------------------------------------------------------------------
1 | .container {
2 | max-width: 1200px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | display: grid;
6 | grid-template-columns: 300px 1fr;
7 | gap: 2rem;
8 | align-items: start;
9 | }
10 |
11 | .sidebar {
12 | position: sticky;
13 | top: 5rem;
14 | display: flex;
15 | flex-direction: column;
16 | gap: 1rem;
17 | max-height: calc(100vh - 8rem);
18 | overflow-y: auto;
19 | }
20 |
21 | .sidebar::-webkit-scrollbar {
22 | width: 4px;
23 | }
24 |
25 | .sidebar::-webkit-scrollbar-thumb {
26 | background: var(--border);
27 | border-radius: 4px;
28 | }
29 |
30 | .results-section {
31 | min-height: calc(100vh - 8rem);
32 | }
33 |
34 | /* Loading state */
35 | .loading {
36 | color: var(--text-secondary);
37 | font-size: 0.875rem;
38 | padding: 1rem;
39 | text-align: center;
40 | opacity: 0.8;
41 | }
42 |
43 | /* Responsive layouts */
44 | @media (max-width: 1024px) {
45 | .container {
46 | grid-template-columns: 1fr;
47 | gap: 1.5rem;
48 | }
49 |
50 | .sidebar {
51 | position: static;
52 | max-height: none;
53 | overflow-y: visible;
54 | }
55 | }
56 |
57 | @media (max-width: 768px) {
58 | .container {
59 | padding: 1rem;
60 | }
61 | }
62 |
63 | @media (max-width: 480px) {
64 | .container {
65 | padding: 0.75rem;
66 | }
67 | }
--------------------------------------------------------------------------------
/public/components/header.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/services/twitterConnector.js:
--------------------------------------------------------------------------------
1 | const AgentTwitterClient = require('agent-twitter-client');
2 |
3 | class TwitterConnector {
4 | constructor(config) {
5 | this.config = config;
6 | this.client = null;
7 | this.isConnected = false;
8 | }
9 |
10 | async connect() {
11 | try {
12 | // Initialize the client directly
13 | this.client = new AgentTwitterClient({
14 | username: this.config.username,
15 | password: this.config.password,
16 | email: this.config.email,
17 | proxy: this.config.proxy || null
18 | });
19 |
20 | // Attempt to login
21 | await this.client.login();
22 |
23 | this.isConnected = true;
24 | console.log('Twitter connection established successfully');
25 | return true;
26 | } catch (error) {
27 | console.error('Twitter connection error:', error);
28 | this.isConnected = false;
29 | return false;
30 | }
31 | }
32 |
33 | async tweet(text) {
34 | if (!this.isConnected || !this.client) {
35 | throw new Error('Twitter client not connected');
36 | }
37 |
38 | try {
39 | const result = await this.client.tweet(text);
40 | return result;
41 | } catch (error) {
42 | console.error('Tweet posting error:', error);
43 | throw error;
44 | }
45 | }
46 |
47 | isLoggedIn() {
48 | return this.isConnected && this.client !== null;
49 | }
50 | }
51 |
52 | module.exports = TwitterConnector;
--------------------------------------------------------------------------------
/scripts/testSiteAnalysis.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const { analyzeSite } = require('../services/siteAnalyzer');
3 |
4 | async function runTest() {
5 | try {
6 | const url = 'https://bake.is';
7 | console.log('Starting site analysis for:', url);
8 |
9 | const result = await analyzeSite(url);
10 |
11 | console.log('\nAnalysis Results:');
12 | console.log('-'.repeat(50));
13 |
14 | // Framework Detection
15 | console.log('\nDetected Frameworks:');
16 | Object.entries(result.analysis.frameworkData)
17 | .filter(([_, data]) => data.found)
18 | .forEach(([name, data]) => {
19 | console.log(`- ${name} (${data.confidence}% confidence)`);
20 | data.evidence.forEach(e => console.log(` • ${e}`));
21 | });
22 |
23 | // Scores
24 | console.log('\nScores:');
25 | console.log(`Legitimacy: ${result.analysis.scores.legitimacyScore}`);
26 | console.log(`LARP: ${result.analysis.scores.larpScore}`);
27 | console.log(`Effort: ${result.analysis.scores.effortScore}`);
28 | console.log(`Risk: ${result.analysis.scores.riskScore}`);
29 |
30 | // Extracted Data
31 | console.log('\nExtracted Data:');
32 | console.log(result.extractedData);
33 |
34 | // Screenshot
35 | console.log('\nScreenshot URL:');
36 | console.log(result.screenshot);
37 |
38 | // Risk Factors
39 | console.log('\nKey Risks:');
40 | result.analysis.summary.keyRisks.forEach((risk, i) => {
41 | console.log(`${i + 1}. ${risk}`);
42 | });
43 |
44 | // Raw Analysis
45 | console.log('\nDetailed Analysis:');
46 | console.log(result.analysis.raw);
47 |
48 | } catch (error) {
49 | console.error('Test failed:', error);
50 | }
51 | }
52 |
53 | runTest();
--------------------------------------------------------------------------------
/public/css/components/forms.css:
--------------------------------------------------------------------------------
1 | .input-group {
2 | background: var(--bg-secondary);
3 | border: 1px solid var(--border);
4 | border-radius: 12px;
5 | padding: 1.5rem;
6 | margin-bottom: 2rem;
7 | transition: all 0.2s ease;
8 | }
9 |
10 | .input-group:focus-within {
11 | border-color: var(--accent);
12 | box-shadow: 0 0 0 2px var(--hover);
13 | }
14 |
15 | .input-group h2 {
16 | color: var(--text-primary);
17 | margin-bottom: 1rem;
18 | font-size: 1.25rem;
19 | }
20 |
21 | #repoUrl {
22 | width: 100%;
23 | padding: 1rem 1.25rem;
24 | background: var(--bg-primary);
25 | border: 2px solid var(--border);
26 | border-radius: 8px;
27 | color: var(--text-primary);
28 | font-family: 'JetBrains Mono', monospace;
29 | font-size: 1rem;
30 | transition: all 0.2s ease;
31 | margin-bottom: 1rem;
32 | }
33 |
34 | #repoUrl::placeholder {
35 | color: var(--text-secondary);
36 | opacity: 0.7;
37 | }
38 |
39 | #repoUrl:hover {
40 | border-color: var(--accent);
41 | box-shadow: 0 2px 8px rgba(234, 179, 8, 0.1);
42 | }
43 |
44 | #repoUrl:focus {
45 | outline: none;
46 | border-color: var(--accent);
47 | box-shadow: 0 0 0 3px var(--hover);
48 | }
49 |
50 | /* Pulsing animation for the input field */
51 | @keyframes subtle-pulse {
52 | 0% { transform: translateY(0); }
53 | 50% { transform: translateY(-1px); }
54 | 100% { transform: translateY(0); }
55 | }
56 |
57 | .input-group:not(:focus-within) #repoUrl {
58 | animation: subtle-pulse 2s ease-in-out infinite;
59 | }
60 |
61 | /* Style the analyze button to complement the input */
62 | .analyze-button {
63 | width: 100%;
64 | padding: 1rem;
65 | background: var(--accent);
66 | color: var(--bg-primary);
67 | border: none;
68 | border-radius: 8px;
69 | font-family: 'JetBrains Mono', monospace;
70 | font-weight: 500;
71 | cursor: pointer;
72 | transition: all 0.2s ease;
73 | }
74 |
75 | .analyze-button:hover {
76 | opacity: 0.9;
77 | transform: translateY(-1px);
78 | }
--------------------------------------------------------------------------------
/scripts/quickTest.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const { analyzeSite } = require('../services/siteAnalyzer');
3 |
4 | async function testSiteAnalysis() {
5 | const url = 'https://bake.is';
6 | console.log(`\nAnalyzing ${url}...`);
7 | console.log('='.repeat(50));
8 |
9 | try {
10 | const result = await analyzeSite(url);
11 |
12 | // Print Framework Detection
13 | if (result.analysis?.frameworkData) {
14 | console.log('\nFrameworks Detected:');
15 | Object.entries(result.analysis.frameworkData)
16 | .filter(([_, data]) => data.found)
17 | .forEach(([name, data]) => {
18 | console.log(`- ${name} (${data.confidence}% confident)`);
19 | if (data.evidence.length > 0) {
20 | console.log(' Evidence:');
21 | data.evidence.forEach(e => console.log(` • ${e}`));
22 | }
23 | });
24 | }
25 |
26 | // Print Scores
27 | if (result.analysis?.scores) {
28 | console.log('\nRisk Analysis:');
29 | const scores = result.analysis.scores;
30 | Object.entries(scores).forEach(([key, value]) => {
31 | console.log(`${key}: ${value}/100`);
32 | });
33 | }
34 |
35 | // Print Summary
36 | if (result.analysis?.summary) {
37 | console.log('\nSummary:');
38 | const summary = result.analysis.summary;
39 | console.log('Verdict:', summary.verdict);
40 | console.log('Risk Level:', summary.riskLevel);
41 | console.log('Trust Score:', summary.trustScore);
42 |
43 | if (summary.keyRisks?.length > 0) {
44 | console.log('\nKey Risks:');
45 | summary.keyRisks.forEach((risk, i) => {
46 | console.log(`${i + 1}. ${risk}`);
47 | });
48 | }
49 | }
50 |
51 | // Print Tech Stack
52 | if (result.analysis?.techStack?.length > 0) {
53 | console.log('\nDetected Technologies:');
54 | result.analysis.techStack.forEach((tech, i) => {
55 | console.log(`${i + 1}. ${tech}`);
56 | });
57 | }
58 |
59 | } catch (error) {
60 | console.error('Analysis failed:', error.message);
61 | if (error.stack) {
62 | console.error('\nStack trace:', error.stack);
63 | }
64 | }
65 | }
66 |
67 | testSiteAnalysis().catch(error => {
68 | console.error('Fatal error:', error);
69 | process.exit(1);
70 | });
--------------------------------------------------------------------------------
/public/sitecheck.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | AI0x | Site Check
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
32 |
33 |
34 |
35 |
36 |
Website Security & Tech Analysis
37 |
38 | Analyze any website for security vulnerabilities, technology stack, and performance metrics.
39 |
40 |
41 |
42 |
43 |
49 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
Recent Site Checks
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/public/js/explore.js:
--------------------------------------------------------------------------------
1 | async function loadRepositories() {
2 | try {
3 | const grid = document.getElementById('repositoriesGrid');
4 | grid.innerHTML = 'Loading repositories...
';
5 |
6 | const response = await fetch('/api/analyses');
7 | if (!response.ok) {
8 | const errorData = await response.json();
9 | throw new Error(errorData.details || 'Failed to fetch repositories');
10 | }
11 |
12 | const data = await response.json();
13 | if (!data || !Array.isArray(data)) {
14 | throw new Error('Invalid data received from server');
15 | }
16 |
17 | allRepositories = data;
18 | currentPage = 1;
19 | applyFilters();
20 |
21 | // Update stats
22 | document.getElementById('totalAnalyses').textContent = data.length;
23 | updateStats(data);
24 | } catch (error) {
25 | console.error('Load error:', error);
26 | document.getElementById('repositoriesGrid').innerHTML = `
27 |
28 |
Failed to load repositories
29 |
${error.message}
30 |
33 |
34 | `;
35 | }
36 | }
37 |
38 | function updateStats(data) {
39 | // Calculate average score
40 | const avgScore = data.reduce((acc, repo) =>
41 | acc + (repo.analysis?.legitimacyScore || 0), 0) / data.length;
42 | document.getElementById('avgScore').textContent =
43 | avgScore ? Math.round(avgScore) : 0;
44 |
45 | // Find most common language
46 | const languages = data.reduce((acc, repo) => {
47 | if (repo.language) {
48 | acc[repo.language] = (acc[repo.language] || 0) + 1;
49 | }
50 | return acc;
51 | }, {});
52 |
53 | const topLanguage = Object.entries(languages)
54 | .sort(([,a], [,b]) => b - a)[0];
55 |
56 | document.getElementById('topLanguage').textContent =
57 | topLanguage ? topLanguage[0] : 'N/A';
58 | }
59 |
60 | function showMobileAnalysis(repoFullName) {
61 | if (window.innerWidth <= 768) {
62 | const container = document.querySelector('.container');
63 | container.classList.add('mobile-fullscreen');
64 |
65 | // Add back button
66 | const backButton = document.createElement('button');
67 | backButton.className = 'back-button';
68 | backButton.innerHTML = `
69 |
72 | Close
73 | `;
74 |
75 | backButton.addEventListener('click', () => {
76 | container.classList.remove('mobile-fullscreen');
77 | backButton.remove();
78 | // Clear the analysis and show the repository list
79 | document.getElementById('result').innerHTML = '';
80 | loadRepositories();
81 | });
82 |
83 | document.body.appendChild(backButton);
84 | }
85 | }
86 |
87 | // Update the repository click handler
88 | function handleRepositoryClick(repoFullName) {
89 | showMobileAnalysis(repoFullName);
90 | loadAnalysis(repoFullName);
91 | // Update URL without page reload
92 | history.pushState({}, '', `/analysis.html?repo=${repoFullName}`);
93 | }
--------------------------------------------------------------------------------
/services/historyManager.js:
--------------------------------------------------------------------------------
1 | const Repository = require('../models/Repository');
2 |
3 | async function saveAnalysis(repoDetails, analysisData, scores, summary) {
4 | try {
5 | const analysis = {
6 | detailedScores: scores.detailedScores,
7 | legitimacyScore: scores.legitimacyScore,
8 | trustScore: analysisData.trustScore,
9 | finalLegitimacyScore: analysisData.finalLegitimacyScore,
10 | codeReview: analysisData.codeReview,
11 | fullAnalysis: analysisData.fullAnalysis,
12 | summary
13 | };
14 |
15 | const repositoryData = {
16 | fullName: repoDetails.full_name,
17 | owner: repoDetails.owner.login,
18 | repoName: repoDetails.name,
19 | description: summary,
20 | language: repoDetails.language,
21 | stars: repoDetails.stargazers_count,
22 | forks: repoDetails.forks_count,
23 | lastAnalyzed: new Date(),
24 | analysis,
25 | summary
26 | };
27 |
28 | // Upsert the repository data
29 | return await Repository.findOneAndUpdate(
30 | { fullName: repoDetails.full_name },
31 | repositoryData,
32 | { upsert: true, new: true }
33 | );
34 | } catch (error) {
35 | console.error('Error saving analysis:', error);
36 | throw error;
37 | }
38 | }
39 |
40 | async function getAnalysisHistory() {
41 | try {
42 | return await Repository.find()
43 | .select({
44 | fullName: 1,
45 | description: 1,
46 | language: 1,
47 | stars: 1,
48 | forks: 1,
49 | lastAnalyzed: 1,
50 | 'analysis.detailedScores': 1,
51 | 'analysis.legitimacyScore': 1,
52 | 'analysis.trustScore': 1,
53 | 'analysis.finalLegitimacyScore': 1,
54 | summary: 1
55 | })
56 | .sort({ lastAnalyzed: -1 })
57 | .limit(50);
58 | } catch (error) {
59 | console.error('Error fetching analysis history:', error);
60 | throw new Error('Failed to fetch analysis history');
61 | }
62 | }
63 |
64 | async function getRecentAnalyses() {
65 | try {
66 | const analyses = await Repository.find()
67 | .select({
68 | fullName: 1,
69 | analysis: 1,
70 | lastAnalyzed: 1,
71 | description: 1,
72 | language: 1,
73 | stars: 1,
74 | forks: 1
75 | })
76 | .sort({ lastAnalyzed: -1 })
77 | .limit(10)
78 | .lean();
79 |
80 | // Transform the data after fetching to match frontend expectations
81 | return analyses.map(analysis => ({
82 | repoFullName: analysis.fullName,
83 | analysis: analysis.analysis?.fullAnalysis || analysis.analysis, // Handle both formats
84 | timestamp: analysis.lastAnalyzed,
85 | description: analysis.description,
86 | language: analysis.language,
87 | stars: analysis.stars,
88 | forks: analysis.forks
89 | }));
90 | } catch (error) {
91 | console.error('Error fetching recent analyses:', error);
92 | throw new Error('Failed to fetch recent analyses');
93 | }
94 | }
95 |
96 | module.exports = { saveAnalysis, getAnalysisHistory, getRecentAnalyses };
--------------------------------------------------------------------------------
/server/utils/repoChecker.js:
--------------------------------------------------------------------------------
1 | const { Octokit } = require('@octokit/rest');
2 | const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
3 |
4 | async function checkRepositorySimilarity(repoFullName) {
5 | try {
6 | const [owner, repo] = repoFullName.split('/');
7 |
8 | // Get repository details
9 | const { data: repoData } = await octokit.repos.get({
10 | owner,
11 | repo
12 | });
13 |
14 | // Get creation date and last push
15 | const createdAt = new Date(repoData.created_at);
16 | const lastPush = new Date(repoData.pushed_at);
17 | const daysSinceCreation = (Date.now() - createdAt) / (1000 * 60 * 60 * 24);
18 |
19 | // Get commit history
20 | const { data: commits } = await octokit.repos.listCommits({
21 | owner,
22 | repo,
23 | per_page: 100
24 | });
25 |
26 | // Red flags
27 | const flags = [];
28 |
29 | // Check for suspicious patterns
30 | if (commits.length === 1) {
31 | flags.push("Single commit repository - possible direct copy");
32 | }
33 |
34 | if (daysSinceCreation < 7 && commits.length > 50) {
35 | flags.push("High commit count for new repository - possible bulk import");
36 | }
37 |
38 | // Check commit patterns
39 | const commitTimes = commits.map(c => new Date(c.commit.author.date).getTime());
40 | const timeDiffs = [];
41 | for (let i = 1; i < commitTimes.length; i++) {
42 | timeDiffs.push(commitTimes[i-1] - commitTimes[i]);
43 | }
44 |
45 | // Check for unnaturally consistent commit intervals
46 | const avgDiff = timeDiffs.reduce((a, b) => a + b, 0) / timeDiffs.length;
47 | const allSimilar = timeDiffs.every(diff => Math.abs(diff - avgDiff) < 1000 * 60 * 5); // 5 minutes
48 | if (allSimilar && commits.length > 10) {
49 | flags.push("Suspiciously consistent commit intervals");
50 | }
51 |
52 | // Check for bulk file additions
53 | const { data: contents } = await octokit.repos.getContent({
54 | owner,
55 | repo,
56 | path: ''
57 | });
58 |
59 | if (contents.length > 50 && commits.length < 5) {
60 | flags.push("Large number of files with few commits");
61 | }
62 |
63 | // Search for similar repositories
64 | const { data: similar } = await octokit.search.repos({
65 | q: `created:<${repoData.created_at} ${repoData.description || repo}`,
66 | sort: 'stars',
67 | per_page: 5
68 | });
69 |
70 | // Check for very similar repositories that are older
71 | const similarRepos = similar.items.filter(r =>
72 | r.full_name !== repoFullName &&
73 | r.stargazers_count > repoData.stargazers_count
74 | );
75 |
76 | if (similarRepos.length > 0) {
77 | flags.push(`Similar older repositories found: ${similarRepos.map(r => r.full_name).join(', ')}`);
78 | }
79 |
80 | return {
81 | flags,
82 | riskLevel: flags.length > 2 ? 'high' : flags.length > 0 ? 'medium' : 'low',
83 | similarRepos: similarRepos.map(r => ({
84 | name: r.full_name,
85 | stars: r.stargazers_count,
86 | created: r.created_at
87 | }))
88 | };
89 |
90 | } catch (error) {
91 | console.error('Error checking repository similarity:', error);
92 | return {
93 | flags: ['Error checking repository'],
94 | riskLevel: 'unknown',
95 | similarRepos: []
96 | };
97 | }
98 | }
99 |
100 | module.exports = { checkRepositorySimilarity };
--------------------------------------------------------------------------------
/twitter-cookies.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "key": "guest_id_marketing",
4 | "value": "v1%3A173602165492945019",
5 | "expires": "2027-01-04T20:14:14.000Z",
6 | "maxAge": 63072000,
7 | "domain": "twitter.com",
8 | "path": "/",
9 | "secure": true,
10 | "hostOnly": false,
11 | "creation": "2025-01-04T20:14:13.069Z",
12 | "lastAccessed": "2025-01-04T20:14:14.512Z",
13 | "sameSite": "none"
14 | },
15 | {
16 | "key": "guest_id_ads",
17 | "value": "v1%3A173602165492945019",
18 | "expires": "2027-01-04T20:14:14.000Z",
19 | "maxAge": 63072000,
20 | "domain": "twitter.com",
21 | "path": "/",
22 | "secure": true,
23 | "hostOnly": false,
24 | "creation": "2025-01-04T20:14:13.069Z",
25 | "lastAccessed": "2025-01-04T20:14:14.512Z",
26 | "sameSite": "none"
27 | },
28 | {
29 | "key": "personalization_id",
30 | "value": "\"v1_LNR2IFyBuWztw7urdpO9sw==\"",
31 | "expires": "2027-01-04T20:14:14.000Z",
32 | "maxAge": 63072000,
33 | "domain": "twitter.com",
34 | "path": "/",
35 | "secure": true,
36 | "hostOnly": false,
37 | "creation": "2025-01-04T20:14:13.069Z",
38 | "lastAccessed": "2025-01-04T20:14:14.512Z",
39 | "sameSite": "none"
40 | },
41 | {
42 | "key": "guest_id",
43 | "value": "v1%3A173602165492945019",
44 | "expires": "2027-01-04T20:14:14.000Z",
45 | "maxAge": 63072000,
46 | "domain": "twitter.com",
47 | "path": "/",
48 | "secure": true,
49 | "hostOnly": false,
50 | "creation": "2025-01-04T20:14:13.069Z",
51 | "lastAccessed": "2025-01-04T20:14:14.512Z",
52 | "sameSite": "none"
53 | },
54 | {
55 | "key": "kdt",
56 | "value": "lQdwqKu8R43GVO2zfo1HqCIhlTqvyLdlo3EGipBV",
57 | "expires": "2026-07-05T20:14:16.000Z",
58 | "maxAge": 47260800,
59 | "domain": "twitter.com",
60 | "path": "/",
61 | "secure": true,
62 | "httpOnly": true,
63 | "hostOnly": false,
64 | "creation": "2025-01-04T20:14:14.305Z",
65 | "lastAccessed": "2025-01-04T20:14:14.512Z"
66 | },
67 | {
68 | "key": "twid",
69 | "value": "\"u=1874910889062678528\"",
70 | "expires": "2030-01-03T20:14:16.000Z",
71 | "maxAge": 157680000,
72 | "domain": "twitter.com",
73 | "path": "/",
74 | "secure": true,
75 | "hostOnly": false,
76 | "creation": "2025-01-04T20:14:14.305Z",
77 | "lastAccessed": "2025-01-04T20:14:14.512Z",
78 | "sameSite": "none"
79 | },
80 | {
81 | "key": "ct0",
82 | "value": "bc1866c09adbde0604fb9bc7c836736c2353b50ab380a96ba0ce3e2e387ef8b7d677086afb866d5d1aab40a9f6dbd444804aba53a54762c83f98ff182be50326914314a4a5489ecb2163a97dcf97272b",
83 | "expires": "2030-01-03T20:14:16.000Z",
84 | "maxAge": 157680000,
85 | "domain": "twitter.com",
86 | "path": "/",
87 | "secure": true,
88 | "hostOnly": false,
89 | "creation": "2025-01-04T20:14:14.306Z",
90 | "lastAccessed": "2025-01-04T20:14:14.512Z",
91 | "sameSite": "lax"
92 | },
93 | {
94 | "key": "auth_token",
95 | "value": "59b8658e3632cefec9a1b51eb6bcc1b0df9cdcfb",
96 | "expires": "2030-01-03T20:14:16.000Z",
97 | "maxAge": 157680000,
98 | "domain": "twitter.com",
99 | "path": "/",
100 | "secure": true,
101 | "httpOnly": true,
102 | "hostOnly": false,
103 | "creation": "2025-01-04T20:14:14.306Z",
104 | "lastAccessed": "2025-01-04T20:14:14.512Z",
105 | "sameSite": "none"
106 | },
107 | {
108 | "key": "att",
109 | "value": "1-w5FChzsJoM0TlwLmCZL4gtHEfgE4sjaiA0jDIoxN",
110 | "expires": "2025-01-05T20:14:16.000Z",
111 | "maxAge": 86400,
112 | "domain": "twitter.com",
113 | "path": "/",
114 | "secure": true,
115 | "httpOnly": true,
116 | "hostOnly": false,
117 | "creation": "2025-01-04T20:14:14.511Z",
118 | "lastAccessed": "2025-01-04T20:14:14.512Z",
119 | "sameSite": "none"
120 | }
121 | ]
--------------------------------------------------------------------------------
/public/index.html,public/analysis.html,public/history.html,public/insights.html,public/search.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/whitepaper.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | AI0x | Technical Whitepaper
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
46 |
47 |
48 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/public/css/components/cards.css:
--------------------------------------------------------------------------------
1 | .recent-analyses {
2 | margin-top: 1rem;
3 | display: flex;
4 | flex-direction: column;
5 | gap: 0.5rem;
6 | }
7 |
8 | .recent-analyses::before {
9 | content: 'RECENT ANALYSES';
10 | display: block;
11 | color: var(--text-secondary);
12 | font-size: 0.75rem;
13 | margin-bottom: 0.5rem;
14 | opacity: 0.7;
15 | }
16 |
17 | .analysis-card {
18 | background: var(--bg-secondary);
19 | padding: 0.875rem;
20 | border-radius: 6px;
21 | cursor: pointer;
22 | border-left: 2px solid var(--border);
23 | transition: border-color 0.2s ease;
24 | }
25 |
26 | .analysis-card:hover {
27 | border-left-color: var(--accent);
28 | }
29 |
30 | .repo-name {
31 | color: var(--text-primary);
32 | font-size: 0.8125rem;
33 | font-weight: 500;
34 | margin-bottom: 0.5rem;
35 | white-space: nowrap;
36 | overflow: hidden;
37 | text-overflow: ellipsis;
38 | }
39 |
40 | .repo-description {
41 | color: var(--text-secondary);
42 | font-size: 0.75rem;
43 | line-height: 1.4;
44 | margin-bottom: 0.75rem;
45 | opacity: 0.8;
46 | display: -webkit-box;
47 | -webkit-line-clamp: 1;
48 | -webkit-box-orient: vertical;
49 | overflow: hidden;
50 | height: 1.4em;
51 | }
52 |
53 | .meta-info {
54 | display: flex;
55 | align-items: center;
56 | gap: 0.75rem;
57 | font-size: 0.6875rem;
58 | color: var(--text-secondary);
59 | padding-top: 0.5rem;
60 | border-top: 1px solid rgba(255, 255, 255, 0.1);
61 | }
62 |
63 | .language-info,
64 | .star-count {
65 | display: flex;
66 | align-items: center;
67 | gap: 0.25rem;
68 | opacity: 0.7;
69 | }
70 |
71 | .analysis-date {
72 | margin-left: auto;
73 | opacity: 0.5;
74 | }
75 |
76 | /* Mobile optimizations */
77 | @media (max-width: 1024px) {
78 | .recent-analyses {
79 | display: grid;
80 | grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
81 | gap: 0.75rem;
82 | }
83 | }
84 |
85 | @media (max-width: 640px) {
86 | .recent-analyses {
87 | grid-template-columns: 1fr;
88 | }
89 |
90 | .analysis-card {
91 | padding: 0.75rem;
92 | }
93 |
94 | .meta-info {
95 | flex-wrap: wrap;
96 | }
97 |
98 | .analysis-date {
99 | width: 100%;
100 | margin-left: 0;
101 | margin-top: 0.25rem;
102 | }
103 | }
104 |
105 | /* Processing state */
106 | .processing {
107 | color: var(--text-secondary);
108 | font-size: 0.875rem;
109 | padding: 1rem;
110 | text-align: center;
111 | opacity: 0.8;
112 | }
113 |
114 | /* Placeholder Analysis */
115 | .analysis-placeholder {
116 | background: var(--bg-secondary);
117 | padding: 1.5rem;
118 | border-radius: 8px;
119 | border-left: 2px solid var(--accent);
120 | }
121 |
122 | .analysis-placeholder h3 {
123 | color: var(--text-primary);
124 | font-size: 1rem;
125 | font-weight: 500;
126 | margin-bottom: 0.75rem;
127 | }
128 |
129 | .placeholder-text {
130 | color: var(--text-secondary);
131 | font-size: 0.875rem;
132 | line-height: 1.5;
133 | opacity: 0.8;
134 | margin-bottom: 1.5rem;
135 | }
136 |
137 | .placeholder-stats {
138 | display: grid;
139 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
140 | gap: 1rem;
141 | padding-top: 1rem;
142 | border-top: 1px solid rgba(255, 255, 255, 0.1);
143 | }
144 |
145 | .stat-item {
146 | display: flex;
147 | flex-direction: column;
148 | gap: 0.25rem;
149 | }
150 |
151 | .stat-label {
152 | color: var(--text-secondary);
153 | font-size: 0.75rem;
154 | opacity: 0.7;
155 | }
156 |
157 | .stat-value {
158 | color: var(--accent);
159 | font-size: 0.875rem;
160 | font-weight: 500;
161 | }
162 |
163 | .repository-card {
164 | background: var(--bg-secondary);
165 | border: 1px solid var(--border);
166 | border-radius: 12px;
167 | padding: 1.5rem;
168 | transition: all 0.2s ease;
169 | cursor: pointer;
170 | }
171 |
172 | .repository-card:hover {
173 | transform: translateY(-2px);
174 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
175 | border-color: var(--accent);
176 | }
177 |
178 | .repository-card:active {
179 | transform: translateY(0);
180 | }
181 |
182 | /* Make sure the entire card is clickable */
183 | .repository-card * {
184 | pointer-events: none;
185 | }
--------------------------------------------------------------------------------
/views/history.html:
--------------------------------------------------------------------------------
1 |
42 |
43 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const express = require('express');
3 | const app = express();
4 | const http = require('http').createServer(app);
5 | const io = require('socket.io')(http, {
6 | cors: {
7 | origin: "*",
8 | methods: ["GET", "POST"]
9 | },
10 | transports: ['websocket', 'polling']
11 | });
12 | const mongoose = require('mongoose');
13 | const connectDB = require('./config/database');
14 | const { analyzeRepo } = require('./services/analyzer');
15 | const { extractRepoInfo } = require('./utils/githubUtils');
16 | const Repository = require('./models/Repository');
17 | const ApiKey = require('./models/ApiKey');
18 | const apiRouter = require('./routes/api');
19 | const { Anthropic } = require('@anthropic-ai/sdk');
20 | const session = require('express-session');
21 | const { startBot: startDiscordBot } = require('./bots/discordBot');
22 | const { startBot: startTelegramBot } = require('./bots/telegramBot');
23 | const v8 = require('v8');
24 | const MongoStore = require('connect-mongo');
25 | const Scheduler = require('./services/scheduler');
26 |
27 | // Set up middleware
28 | app.use(express.json());
29 | app.use(express.static('public'));
30 |
31 | // Set up Socket.IO
32 | global.io = io;
33 |
34 | io.on('connection', (socket) => {
35 | console.log('Client connected:', socket.id);
36 |
37 | socket.on('error', (error) => {
38 | console.error('Socket error:', error);
39 | });
40 |
41 | socket.on('disconnect', (reason) => {
42 | console.log('Client disconnected:', socket.id, 'Reason:', reason);
43 | });
44 | });
45 |
46 | // Connect to MongoDB
47 | connectDB();
48 |
49 | // Add memory monitoring
50 | setInterval(() => {
51 | const heapStats = v8.getHeapStatistics();
52 | const heapUsed = (heapStats.used_heap_size / heapStats.heap_size_limit) * 100;
53 |
54 | console.log(`Memory usage: ${heapUsed.toFixed(2)}%`);
55 |
56 | if (heapUsed > 90) {
57 | console.warn('High memory usage detected!');
58 | global.gc && global.gc();
59 | }
60 | }, 300000);
61 |
62 | // Routes
63 | app.use('/api', apiRouter);
64 |
65 | // MongoDB connection
66 | mongoose.connect(process.env.MONGODB_URI, {
67 | useNewUrlParser: true,
68 | useUnifiedTopology: true,
69 | serverSelectionTimeoutMS: 5000,
70 | socketTimeoutMS: 45000,
71 | keepAlive: true,
72 | keepAliveInitialDelay: 300000
73 | })
74 | .then(() => console.log('MongoDB connected'))
75 | .catch(err => console.error('MongoDB connection error:', err));
76 |
77 | // Session configuration
78 | app.use(session({
79 | secret: process.env.SESSION_SECRET || 'your-secret-key',
80 | resave: false,
81 | saveUninitialized: false,
82 | store: MongoStore.create({
83 | mongoUrl: process.env.MONGODB_URI,
84 | ttl: 24 * 60 * 60
85 | })
86 | }));
87 |
88 | // Error handling middleware
89 | app.use((err, req, res, next) => {
90 | if (err.message === 'Failed to find request token in session') {
91 | console.error('Authentication error:', err);
92 | return res.redirect('/login?error=auth_failed');
93 | }
94 | next(err);
95 | });
96 |
97 | // Health check endpoint
98 | app.get('/health', (req, res) => {
99 | const isDbConnected = mongoose.connection.readyState === 1;
100 | if (isDbConnected) {
101 | res.status(200).json({ status: 'healthy', db: 'connected' });
102 | } else {
103 | res.status(503).json({ status: 'unhealthy', db: 'disconnected' });
104 | }
105 | });
106 |
107 | // Initialize bots
108 | const initializeBots = async () => {
109 | try {
110 | // Start Discord bot
111 | await startDiscordBot();
112 | console.log('Discord bot started successfully');
113 |
114 | // Start Telegram bot (remove development check)
115 | try {
116 | await startTelegramBot();
117 | console.log('Telegram bot started successfully');
118 | } catch (error) {
119 | console.error('Failed to initialize Telegram bot:', error);
120 | }
121 |
122 | console.log('Bot initialization complete');
123 | } catch (error) {
124 | console.error('Error initializing bots:', error);
125 | }
126 | };
127 |
128 | // Start the application
129 | const startApp = async () => {
130 | try {
131 | await connectDB();
132 | console.log('Connected to MongoDB');
133 |
134 | const port = process.env.PORT || 3000;
135 | http.listen(port, () => {
136 | console.log(`Server running on port ${port}`);
137 | });
138 |
139 | await initializeBots();
140 | } catch (error) {
141 | console.error('Application startup error:', error);
142 | process.exit(1);
143 | }
144 | };
145 |
146 | // Graceful shutdown
147 | process.on('SIGTERM', () => {
148 | console.log('SIGTERM received. Shutting down gracefully...');
149 | http.close(() => {
150 | console.log('HTTP server closed');
151 | process.exit(0);
152 | });
153 | });
154 |
155 | // This will start the tweet generation schedule
156 | const schedulerInstance = new Scheduler();
157 |
158 | startApp();
--------------------------------------------------------------------------------
/public/templates/header.html:
--------------------------------------------------------------------------------
1 |
53 |
54 |
--------------------------------------------------------------------------------
/public/css/components/header.css:
--------------------------------------------------------------------------------
1 | .header {
2 | background: var(--bg-secondary);
3 | border-bottom: 1px solid var(--border);
4 | padding: 1rem 0;
5 | position: sticky;
6 | top: 0;
7 | z-index: 100;
8 | }
9 |
10 | .header-content {
11 | max-width: 1400px;
12 | margin: 0 auto;
13 | padding: 0 2rem;
14 | display: flex;
15 | align-items: center;
16 | gap: 1.5rem;
17 | }
18 |
19 | .logo {
20 | display: flex;
21 | align-items: center;
22 | gap: 0.75rem;
23 | text-decoration: none;
24 | color: var(--text-primary);
25 | font-family: 'JetBrains Mono', monospace;
26 | font-weight: bold;
27 | font-size: 1.25rem;
28 | }
29 |
30 | .logo:hover {
31 | color: var(--accent);
32 | }
33 |
34 | .nav-links {
35 | display: flex;
36 | gap: 1.5rem;
37 | align-items: center;
38 | }
39 |
40 | .nav-link {
41 | color: var(--text-secondary);
42 | text-decoration: none;
43 | font-family: 'JetBrains Mono', monospace;
44 | font-size: 0.875rem;
45 | padding: 0.5rem;
46 | border-radius: 6px;
47 | transition: all 0.2s ease;
48 | }
49 |
50 | .nav-link:hover {
51 | color: var(--accent);
52 | background: var(--bg-hover);
53 | }
54 |
55 | .nav-link.active {
56 | color: var(--accent);
57 | background: rgba(234, 179, 8, 0.1);
58 | }
59 |
60 | .social-links {
61 | display: flex;
62 | align-items: center;
63 | gap: 1rem;
64 | padding-left: 1rem;
65 | border-left: 1px solid var(--border);
66 | }
67 |
68 | .social-link {
69 | color: var(--text-secondary);
70 | transition: all 0.2s ease;
71 | display: flex;
72 | align-items: center;
73 | padding: 0.5rem;
74 | border-radius: 6px;
75 | }
76 |
77 | .social-link:hover {
78 | color: var(--accent);
79 | background: var(--bg-hover);
80 | }
81 |
82 | .social-link svg {
83 | width: 20px;
84 | height: 20px;
85 | }
86 |
87 | .logo-img {
88 | width: 32px;
89 | height: 32px;
90 | border-radius: 50%;
91 | transition: transform 0.2s ease;
92 | }
93 |
94 | .logo:hover .logo-img {
95 | transform: scale(1.1);
96 | }
97 |
98 | .logo-text {
99 | font-size: 1.25rem;
100 | font-weight: bold;
101 | }
102 |
103 | .ca-display {
104 | display: flex;
105 | align-items: center;
106 | gap: 0.75rem;
107 | padding: 0.625rem 1rem;
108 | background: var(--bg-primary);
109 | border: 1px solid var(--border);
110 | border-radius: 6px;
111 | cursor: pointer;
112 | transition: all 0.2s ease;
113 | font-family: 'JetBrains Mono', monospace;
114 | font-size: 0.875rem;
115 | color: var(--text-secondary);
116 | white-space: nowrap;
117 | }
118 |
119 | .ca-display:hover {
120 | border-color: var(--accent);
121 | background: var(--bg-hover);
122 | transform: translateY(-1px);
123 | }
124 |
125 | .copy-icon {
126 | width: 18px;
127 | height: 18px;
128 | color: var(--accent);
129 | opacity: 0.8;
130 | transition: all 0.2s ease;
131 | padding: 2px;
132 | }
133 |
134 | .ca-display:hover .copy-icon {
135 | opacity: 1;
136 | transform: scale(1.1);
137 | }
138 |
139 | .copy-notification {
140 | position: fixed;
141 | top: 1rem;
142 | right: 1rem;
143 | background: var(--bg-secondary);
144 | color: var(--accent);
145 | padding: 0.75rem 1rem;
146 | border-radius: 6px;
147 | display: flex;
148 | align-items: center;
149 | gap: 0.75rem;
150 | border: 1px solid var(--accent);
151 | animation: slideIn 0.3s ease;
152 | z-index: 1000;
153 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
154 | }
155 |
156 | .copy-notification svg {
157 | width: 18px;
158 | height: 18px;
159 | }
160 |
161 | @keyframes slideIn {
162 | from {
163 | transform: translateY(-1rem);
164 | opacity: 0;
165 | }
166 | to {
167 | transform: translateY(0);
168 | opacity: 1;
169 | }
170 | }
171 |
172 | @media (max-width: 768px) {
173 | .header-content {
174 | padding: 0 1rem;
175 | flex-wrap: wrap;
176 | gap: 1rem;
177 | }
178 |
179 | .nav-links {
180 | order: 3;
181 | width: 100%;
182 | justify-content: space-between;
183 | padding-top: 0.5rem;
184 | border-top: 1px solid var(--border);
185 | }
186 |
187 | .ca-display {
188 | margin-left: auto;
189 | }
190 |
191 | .ca-text {
192 | display: none;
193 | }
194 |
195 | .ca-display:before {
196 | content: 'CA:';
197 | margin-right: 0.25rem;
198 | }
199 |
200 | .social-links {
201 | padding-left: 0.5rem;
202 | gap: 0.5rem;
203 | }
204 |
205 | .nav-link {
206 | font-size: 0.8125rem;
207 | padding: 0.375rem 0.5rem;
208 | }
209 | }
210 |
211 | @media (max-width: 480px) {
212 | .header-content {
213 | padding: 0 0.75rem;
214 | }
215 |
216 | .logo-text {
217 | font-size: 1.125rem;
218 | }
219 |
220 | .logo-img {
221 | width: 28px;
222 | height: 28px;
223 | }
224 |
225 | .social-links {
226 | border-left: none;
227 | }
228 | }
229 |
230 | @media (max-width: 640px) {
231 | .nav-links {
232 | overflow-x: auto;
233 | -webkit-overflow-scrolling: touch;
234 | scrollbar-width: none;
235 | -ms-overflow-style: none;
236 | }
237 |
238 | .nav-links::-webkit-scrollbar {
239 | display: none;
240 | }
241 | }
--------------------------------------------------------------------------------
/utils/githubUtils.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios');
2 |
3 | function extractRepoInfo(url) {
4 | const githubRegex = /github\.com\/([^\/]+)\/([^\/]+)/;
5 | const match = url.match(githubRegex);
6 |
7 | if (!match) return null;
8 |
9 | return {
10 | owner: match[1],
11 | repo: match[2]
12 | };
13 | }
14 |
15 | async function getRepoDetails({ owner, repo }) {
16 | if (!owner || !repo) {
17 | throw new Error('Invalid repository info provided');
18 | }
19 |
20 | try {
21 | const githubToken = process.env.GITHUB_TOKEN;
22 | console.log(`Making GitHub API request for: ${owner}/${repo}`);
23 |
24 | const response = await axios.get(`https://api.github.com/repos/${owner}/${repo}`, {
25 | headers: {
26 | Authorization: `Bearer ${githubToken}`,
27 | Accept: 'application/vnd.github.v3+json'
28 | }
29 | });
30 |
31 | return response.data;
32 | } catch (error) {
33 | if (error.response?.status === 404) {
34 | throw new Error(`Repository ${owner}/${repo} does not exist`);
35 | }
36 | throw error;
37 | }
38 | }
39 |
40 | async function getRepoContents(repoInfo, path = '', maxFiles = 50) {
41 | try {
42 | const files = [];
43 | const queue = [{ path }];
44 |
45 | while (queue.length > 0 && files.length < maxFiles) {
46 | const current = queue.shift();
47 | const response = await axios.get(
48 | `https://api.github.com/repos/${repoInfo.owner}/${repoInfo.repo}/contents/${current.path}`,
49 | {
50 | headers: {
51 | 'Authorization': `token ${process.env.GITHUB_TOKEN}`,
52 | 'Accept': 'application/vnd.github.v3+json'
53 | }
54 | }
55 | );
56 |
57 | for (const item of response.data) {
58 | if (item.type === 'file') {
59 | // Filter relevant file types
60 | if (isRelevantFile(item.name)) {
61 | const content = await getFileContent(item.download_url);
62 | files.push({
63 | path: item.path,
64 | content: content
65 | });
66 | }
67 | } else if (item.type === 'dir') {
68 | queue.push({ path: item.path });
69 | }
70 | }
71 | }
72 |
73 | return files;
74 | } catch (error) {
75 | console.error('Error fetching repo contents:', error);
76 | return [];
77 | }
78 | }
79 |
80 | function isRelevantFile(filename) {
81 | const relevantExtensions = [
82 | '.js', '.ts', '.py', '.java', '.go', '.rs', '.cpp', '.c',
83 | '.jsx', '.tsx', '.vue', '.php', '.rb', '.sol', '.cs',
84 | '.html', '.css', '.scss', '.md', '.json', '.yml', '.yaml'
85 | ];
86 |
87 | const excludedPaths = [
88 | 'node_modules/', 'vendor/', 'dist/', 'build/',
89 | 'test/', 'tests/', '__tests__/', '__pycache__/',
90 | '.git/', '.github/', '.vscode/', '.idea/'
91 | ];
92 |
93 | return relevantExtensions.some(ext => filename.endsWith(ext)) &&
94 | !excludedPaths.some(path => filename.includes(path));
95 | }
96 |
97 | function parseGitHubUrl(url) {
98 | try {
99 | // Clean the URL
100 | url = url.trim();
101 |
102 | // Remove trailing slashes
103 | url = url.replace(/\/+$/, '');
104 |
105 | console.log('Cleaning URL:', url);
106 |
107 | // Handle HTTPS URLs
108 | if (url.includes('github.com')) {
109 | // Remove any trailing .git
110 | url = url.replace(/\.git$/, '');
111 |
112 | // Updated regex to be more precise
113 | const match = url.match(/github\.com\/([^\/\s]+)\/([^\/\s]+)/);
114 |
115 | if (!match) {
116 | console.log('No match found for URL:', url);
117 | return null;
118 | }
119 |
120 | const [, owner, repo] = match;
121 |
122 | // Clean and validate the results
123 | const cleanOwner = owner.trim();
124 | const cleanRepo = repo.trim();
125 |
126 | console.log('Parsed result:', { owner: cleanOwner, repo: cleanRepo });
127 |
128 | // Validate the repository exists before returning
129 | return {
130 | owner: cleanOwner,
131 | repo: cleanRepo
132 | };
133 | }
134 | return null;
135 | } catch (error) {
136 | console.error('Error parsing GitHub URL:', error);
137 | return null;
138 | }
139 | }
140 |
141 | async function getFileContent(url) {
142 | try {
143 | const response = await axios.get(url, {
144 | headers: {
145 | 'Authorization': `token ${process.env.GITHUB_TOKEN}`,
146 | 'Accept': 'application/vnd.github.v3.raw'
147 | }
148 | });
149 | // Ensure we return a string
150 | return typeof response.data === 'string'
151 | ? response.data
152 | : JSON.stringify(response.data);
153 | } catch (error) {
154 | console.error(`Error fetching file content from ${url}:`, error.message);
155 | return '';
156 | }
157 | }
158 |
159 | module.exports = { extractRepoInfo, getRepoDetails, getRepoContents, parseGitHubUrl, getFileContent };
--------------------------------------------------------------------------------
/services/vectorStoreService.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const { Anthropic } = require('@anthropic-ai/sdk');
3 | const anthropic = new Anthropic();
4 |
5 | // Define a schema for vector storage
6 | const vectorSchema = new mongoose.Schema({
7 | repoFullName: String,
8 | vector: [Number], // Store embeddings as array of numbers
9 | metadata: {
10 | stars: Number,
11 | languages: [String],
12 | healthScore: Number,
13 | trendingScore: Number,
14 | timestamp: Date
15 | }
16 | });
17 |
18 | // Create vector model
19 | const VectorModel = mongoose.model('Vector', vectorSchema);
20 |
21 | class VectorStoreService {
22 | async storeRepoInsights(repoFullName, insights) {
23 | try {
24 | // Convert insights into embeddings using Claude
25 | const embedding = await this.generateEmbedding(insights);
26 |
27 | // Store in MongoDB
28 | await VectorModel.findOneAndUpdate(
29 | { repoFullName },
30 | {
31 | vector: embedding,
32 | metadata: {
33 | stars: insights.basic.stars,
34 | languages: Object.keys(insights.languages),
35 | healthScore: insights.healthScore,
36 | trendingScore: insights.trendingScore,
37 | timestamp: new Date()
38 | }
39 | },
40 | { upsert: true }
41 | );
42 |
43 | console.log(`Stored vectors for ${repoFullName}`);
44 | } catch (error) {
45 | console.error('Error storing repo insights:', error);
46 | throw error;
47 | }
48 | }
49 |
50 | async generateEmbedding(insights) {
51 | try {
52 | // Convert insights to a structured text representation
53 | const textRepresentation = this.insightsToText(insights);
54 |
55 | // Use Claude to generate a numerical representation
56 | const response = await anthropic.messages.create({
57 | model: "claude-3-sonnet-20240229",
58 | max_tokens: 1024,
59 | messages: [{
60 | role: "user",
61 | content: `Convert this repository data into a numerical vector representation with 384 dimensions, suitable for similarity search. Only return the array of numbers, nothing else: ${textRepresentation}`
62 | }]
63 | });
64 |
65 | // Parse the response into an array of numbers
66 | const vectorString = response.content.trim();
67 | const vector = JSON.parse(vectorString);
68 |
69 | return vector;
70 | } catch (error) {
71 | console.error('Error generating embedding:', error);
72 | throw error;
73 | }
74 | }
75 |
76 | insightsToText(insights) {
77 | return `
78 | Repository Stats:
79 | Stars: ${insights.basic.stars}
80 | Forks: ${insights.basic.forks}
81 | Languages: ${Object.keys(insights.languages).join(', ')}
82 | Recent Commits: ${insights.activity.recentCommits}
83 | Health Score: ${insights.healthScore}
84 | Trending Score: ${insights.trendingScore}
85 | Contributors: ${insights.activity.contributors}
86 | `;
87 | }
88 |
89 | async findSimilarRepos(repoFullName, limit = 5) {
90 | try {
91 | // Get the vector for the target repo
92 | const targetRepo = await VectorModel.findOne({ repoFullName });
93 | if (!targetRepo) {
94 | throw new Error('Repository not found in vector store');
95 | }
96 |
97 | // Find similar repos using vector similarity
98 | const similarRepos = await VectorModel.aggregate([
99 | {
100 | $search: {
101 | knnBeta: {
102 | vector: targetRepo.vector,
103 | path: "vector",
104 | k: limit
105 | }
106 | }
107 | },
108 | {
109 | $project: {
110 | repoFullName: 1,
111 | metadata: 1,
112 | score: { $meta: "searchScore" }
113 | }
114 | }
115 | ]);
116 |
117 | return similarRepos;
118 | } catch (error) {
119 | console.error('Error finding similar repos:', error);
120 | throw error;
121 | }
122 | }
123 |
124 | async analyzePatterns() {
125 | try {
126 | // Get recent repositories
127 | const recentRepos = await VectorModel.find()
128 | .sort({ 'metadata.timestamp': -1 })
129 | .limit(100);
130 |
131 | // Use Claude to analyze patterns
132 | const analysis = await anthropic.messages.create({
133 | model: "claude-3-sonnet-20240229",
134 | max_tokens: 2048,
135 | messages: [{
136 | role: "user",
137 | content: `Analyze these repositories and identify interesting patterns, trends, and notable insights: ${JSON.stringify(recentRepos)}`
138 | }]
139 | });
140 |
141 | return analysis.content;
142 | } catch (error) {
143 | console.error('Error analyzing patterns:', error);
144 | throw error;
145 | }
146 | }
147 | }
148 |
149 | module.exports = new VectorStoreService();
--------------------------------------------------------------------------------
/server/routes/api.js:
--------------------------------------------------------------------------------
1 | const Redis = require('ioredis');
2 | const { checkRepositorySimilarity } = require('../utils/repoChecker');
3 |
4 | const REDIS_URL = process.env.NODE_ENV === 'production'
5 | ? process.env.REDIS_URL_PROD
6 | : process.env.REDIS_URL_DEV;
7 |
8 | const redis = new Redis(REDIS_URL);
9 |
10 | // Cache keys
11 | const CACHE_KEYS = {
12 | ALL_ANALYSES: 'all_analyses',
13 | STATS: 'explore_stats'
14 | };
15 |
16 | // Add error handling for Redis connection
17 | redis.on('error', (error) => {
18 | console.error('Redis connection error:', error);
19 | });
20 |
21 | redis.on('connect', () => {
22 | console.log('Connected to Redis successfully');
23 | });
24 |
25 | // Update the GET /api/analyses endpoint
26 | router.get('/analyses', async (req, res) => {
27 | try {
28 | // Try cache first
29 | const cachedData = await redis.get(CACHE_KEYS.ALL_ANALYSES);
30 | if (cachedData) {
31 | console.log('Serving from cache');
32 | return res.json(JSON.parse(cachedData));
33 | }
34 |
35 | console.log('Cache miss, fetching from MongoDB');
36 |
37 | // If no cache, get from MongoDB with optimized query
38 | const analyses = await Analysis.find({}, {
39 | fullName: 1,
40 | stars: 1,
41 | forks: 1,
42 | language: 1,
43 | lastAnalyzed: 1,
44 | 'analysis.legitimacyScore': 1,
45 | 'analysis.trustScore': 1,
46 | description: 1,
47 | owner: 1,
48 | repoName: 1
49 | })
50 | .sort({ lastAnalyzed: -1 })
51 | .limit(100)
52 | .lean()
53 | .exec();
54 |
55 | if (!analyses) {
56 | throw new Error('No analyses found');
57 | }
58 |
59 | // Cache the results
60 | await redis.set(CACHE_KEYS.ALL_ANALYSES, JSON.stringify(analyses));
61 |
62 | console.log(`Sending ${analyses.length} analyses`);
63 | res.json(analyses);
64 | } catch (error) {
65 | console.error('Error in /api/analyses:', error);
66 | res.status(500).json({
67 | error: 'Failed to fetch analyses',
68 | details: error.message,
69 | stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
70 | });
71 | }
72 | });
73 |
74 | // Update stats endpoint with caching
75 | router.get('/stats', async (req, res) => {
76 | try {
77 | // Try cache first
78 | const cachedStats = await redis.get(CACHE_KEYS.STATS);
79 | if (cachedStats) {
80 | return res.json(JSON.parse(cachedStats));
81 | }
82 |
83 | const stats = await calculateStats(); // Your stats calculation function
84 | await redis.set(CACHE_KEYS.STATS, JSON.stringify(stats));
85 |
86 | res.json(stats);
87 | } catch (error) {
88 | console.error('Error fetching stats:', error);
89 | res.status(500).json({ error: 'Failed to fetch stats' });
90 | }
91 | });
92 |
93 | // Invalidate cache when new analysis is added
94 | router.post('/analyze', async (req, res) => {
95 | try {
96 | // ... existing analysis code ...
97 |
98 | // Add similarity check
99 | const similarityCheck = await checkRepositorySimilarity(repoFullName);
100 |
101 | // Add to analysis result
102 | analysis.copyDetection = {
103 | riskLevel: similarityCheck.riskLevel,
104 | flags: similarityCheck.flags,
105 | similarRepos: similarityCheck.similarRepos
106 | };
107 |
108 | // Update legitimacy score if high risk
109 | if (similarityCheck.riskLevel === 'high') {
110 | analysis.legitimacyScore = Math.max(0, analysis.legitimacyScore - 30);
111 | analysis.trustScore = Math.max(0, analysis.trustScore - 40);
112 | }
113 |
114 | // After successful analysis, invalidate caches
115 | await Promise.all([
116 | redis.del(CACHE_KEYS.ALL_ANALYSES),
117 | redis.del(CACHE_KEYS.STATS)
118 | ]);
119 |
120 | res.json(result);
121 | } catch (error) {
122 | console.error('Analysis failed:', error);
123 | res.status(500).json({ error: error.message });
124 | }
125 | });
126 |
127 | // Add to your analysis processing
128 | function processAnalysis(analysis) {
129 | // Extract scores from fullAnalysis if they're not in detailedScores
130 | const scoreRegex = /## (\w+)\s*(?:Quality\s*)?Score:\s*(\d+)\/25/g;
131 | const scores = {
132 | codeQuality: 0,
133 | projectStructure: 0,
134 | implementation: 0,
135 | documentation: 0
136 | };
137 |
138 | let match;
139 | while ((match = scoreRegex.exec(analysis.fullAnalysis)) !== null) {
140 | const category = match[1].toLowerCase().replace(/\s+/g, '');
141 | const score = parseInt(match[2]);
142 |
143 | const categoryMap = {
144 | 'code': 'codeQuality',
145 | 'projectstructure': 'projectStructure',
146 | 'implementation': 'implementation',
147 | 'documentation': 'documentation'
148 | };
149 |
150 | if (categoryMap[category]) {
151 | scores[categoryMap[category]] = score;
152 | }
153 | }
154 |
155 | // Calculate legitimacy score
156 | const legitimacyScore = Math.round(
157 | (scores.codeQuality +
158 | scores.projectStructure +
159 | scores.implementation +
160 | scores.documentation) / 100 * 100
161 | );
162 |
163 | return {
164 | ...analysis,
165 | detailedScores: scores,
166 | legitimacyScore: legitimacyScore || analysis.legitimacyScore
167 | };
168 | }
169 |
170 | // Add a health check endpoint
171 | router.get('/health', (req, res) => {
172 | res.json({ status: 'ok' });
173 | });
--------------------------------------------------------------------------------
/scripts/debugScores.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Repository = require('../models/Repository');
3 | require('dotenv').config();
4 |
5 | async function debugScores() {
6 | try {
7 | // Connect to MongoDB
8 | await mongoose.connect(process.env.MONGODB_URI);
9 | console.log('Connected to MongoDB');
10 |
11 | // Get all repositories
12 | const repos = await Repository.find().sort({ lastAnalyzed: -1 });
13 |
14 | console.log('\n=== Score Analysis Debug ===\n');
15 | console.log(`Total repositories: ${repos.length}\n`);
16 |
17 | repos.forEach(repo => {
18 | console.log(`\nRepository: ${repo.fullName}`);
19 | console.log('Last analyzed:', repo.lastAnalyzed);
20 | console.log('\nScores:');
21 | console.log('- Final Legitimacy Score:', repo.analysis?.finalLegitimacyScore);
22 | console.log('- Technical Score:', repo.analysis?.legitimacyScore);
23 | console.log('- Trust Score:', repo.analysis?.trustScore);
24 | console.log('\nDetailed Scores:');
25 | console.log('- Code Quality:', repo.analysis?.detailedScores?.codeQuality);
26 | console.log('- Project Structure:', repo.analysis?.detailedScores?.projectStructure);
27 | console.log('- Implementation:', repo.analysis?.detailedScores?.implementation);
28 | console.log('- Documentation:', repo.analysis?.detailedScores?.documentation);
29 |
30 | // Check for potential issues
31 | const issues = [];
32 | if (!repo.analysis) issues.push('Missing analysis object');
33 | if (repo.analysis?.finalLegitimacyScore === undefined) issues.push('Missing finalLegitimacyScore');
34 | if (repo.analysis?.legitimacyScore === undefined) issues.push('Missing legitimacyScore');
35 | if (repo.analysis?.trustScore === undefined) issues.push('Missing trustScore');
36 | if (!repo.analysis?.detailedScores) issues.push('Missing detailedScores');
37 |
38 | if (issues.length > 0) {
39 | console.log('\nIssues Found:');
40 | issues.forEach(issue => console.log(`! ${issue}`));
41 | }
42 |
43 | console.log('\n' + '='.repeat(50));
44 | });
45 |
46 | // Summary statistics
47 | const stats = {
48 | totalRepos: repos.length,
49 | missingAnalysis: repos.filter(r => !r.analysis).length,
50 | missingFinalScore: repos.filter(r => r.analysis?.finalLegitimacyScore === undefined).length,
51 | missingTechnicalScore: repos.filter(r => r.analysis?.legitimacyScore === undefined).length,
52 | missingTrustScore: repos.filter(r => r.analysis?.trustScore === undefined).length,
53 | avgFinalScore: calculateAverage(repos.map(r => r.analysis?.finalLegitimacyScore)),
54 | avgTechnicalScore: calculateAverage(repos.map(r => r.analysis?.legitimacyScore)),
55 | avgTrustScore: calculateAverage(repos.map(r => r.analysis?.trustScore))
56 | };
57 |
58 | console.log('\n=== Summary Statistics ===');
59 | console.log(`Total Repositories: ${stats.totalRepos}`);
60 | console.log(`Missing Analysis: ${stats.missingAnalysis}`);
61 | console.log(`Missing Final Score: ${stats.missingFinalScore}`);
62 | console.log(`Missing Technical Score: ${stats.missingTechnicalScore}`);
63 | console.log(`Missing Trust Score: ${stats.missingTrustScore}`);
64 | console.log(`Average Final Score: ${stats.avgFinalScore}`);
65 | console.log(`Average Technical Score: ${stats.avgTechnicalScore}`);
66 | console.log(`Average Trust Score: ${stats.avgTrustScore}`);
67 |
68 | } catch (error) {
69 | console.error('Error:', error);
70 | } finally {
71 | await mongoose.disconnect();
72 | console.log('\nDisconnected from MongoDB');
73 | }
74 | }
75 |
76 | function calculateAverage(numbers) {
77 | const validNumbers = numbers.filter(n => n !== undefined && n !== null);
78 | if (validNumbers.length === 0) return 0;
79 | return Math.round(validNumbers.reduce((a, b) => a + b, 0) / validNumbers.length);
80 | }
81 |
82 | async function debugRecentData() {
83 | try {
84 | await mongoose.connect(process.env.MONGODB_URI);
85 | console.log('Connected to MongoDB');
86 |
87 | const recentRepos = await Repository.find()
88 | .sort({ lastAnalyzed: -1 })
89 | .limit(5);
90 |
91 | console.log('\n=== Recent Repositories Data Debug ===\n');
92 |
93 | recentRepos.forEach(repo => {
94 | console.log(`\nRepository: ${repo.fullName}`);
95 | console.log('Raw analysis object:', JSON.stringify(repo.analysis, null, 2));
96 | console.log('Scores:');
97 | console.log('- Final Legitimacy Score:', repo.analysis?.finalLegitimacyScore);
98 | console.log('- Technical Score:', repo.analysis?.legitimacyScore);
99 | console.log('- Trust Score:', repo.analysis?.trustScore);
100 |
101 | if (repo.analysis?.larpScore !== undefined) {
102 | console.log('! WARNING: Found deprecated larpScore');
103 | }
104 | });
105 |
106 | await mongoose.disconnect();
107 | } catch (error) {
108 | console.error('Error:', error);
109 | }
110 | }
111 |
112 | async function runDebug() {
113 | try {
114 | await mongoose.connect(process.env.MONGODB_URI);
115 | console.log('Connected to MongoDB');
116 |
117 | await debugScores();
118 | await debugRecentData();
119 |
120 | } catch (error) {
121 | console.error('Error:', error);
122 | } finally {
123 | await mongoose.disconnect();
124 | console.log('\nDisconnected from MongoDB');
125 | }
126 | }
127 |
128 | // Replace the direct calls with:
129 | runDebug();
--------------------------------------------------------------------------------
/public/js/whitepaper.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', function() {
2 | // Initialize mermaid with custom theme
3 | mermaid.initialize({
4 | theme: 'dark',
5 | securityLevel: 'loose',
6 | startOnLoad: true,
7 | themeVariables: {
8 | primaryColor: '#ffd700',
9 | primaryTextColor: '#e0e0ff',
10 | primaryBorderColor: '#2a2a3a',
11 | lineColor: '#9090a0',
12 | secondaryColor: '#13131f',
13 | tertiaryColor: '#0a0a0b'
14 | }
15 | });
16 |
17 | loadWhitepaper();
18 | });
19 |
20 | async function loadWhitepaper() {
21 | const content = `
22 | # AI0x Technical Whitepaper
23 | Version 1.0.0
24 |
25 | ## Executive Summary
26 | AI0x is a real-time market intelligence platform powered by Claude AI that provides technical assessments and market insights for blockchain and AI/ML projects. The platform combines advanced code analysis with market sentiment to deliver comprehensive project evaluations.
27 |
28 | ## Core Architecture
29 |
30 | ### Analysis Engine
31 | The AI0x analysis engine utilizes a multi-layered approach:
32 |
33 | \`\`\`mermaid
34 | graph TD
35 | A[Repository Input] --> B[Technical Analysis]
36 | B --> C[Code Quality Assessment]
37 | B --> D[Implementation Verification]
38 | B --> E[Architecture Evaluation]
39 | C --> F[Legitimacy Score]
40 | D --> F
41 | E --> F
42 | F --> G[Final Assessment]
43 | H[Market Data] --> G
44 | \`\`\`
45 |
46 | ### Scoring Methodology
47 | Our legitimacy scoring system evaluates projects across four key dimensions:
48 |
49 | - Code Quality (25 points)
50 | - Project Structure (25 points)
51 | - Implementation (25 points)
52 | - Documentation (25 points)
53 |
54 | ## Technical Components
55 |
56 | ### Real-time Analysis
57 | - WebSocket-based live updates
58 | - Queue management system
59 | - Automated market refreshes
60 | - Social platform integration
61 |
62 | ### Market Intelligence
63 | - Repository technical assessment
64 | - Trust score calculation
65 | - Risk level evaluation
66 | - Growth potential analysis
67 |
68 | ### Data Processing Pipeline
69 | \`\`\`mermaid
70 | sequenceDiagram
71 | participant User
72 | participant Frontend
73 | participant Queue
74 | participant Claude
75 | participant Analysis
76 |
77 | User->>Frontend: Submit Repository
78 | Frontend->>Queue: Add to Queue
79 | Queue->>Claude: Process Repository
80 | Claude->>Analysis: Generate Assessment
81 | Analysis->>Frontend: Return Results
82 | \`\`\`
83 |
84 | ## Risk Assessment
85 |
86 | ### Technical Risk Matrix
87 |
88 |
89 |
High Risk Indicators
90 | - Code implementation inconsistencies
91 | - Missing core functionality
92 | - Security vulnerabilities
93 |
94 |
95 |
Medium Risk Indicators
96 | - Incomplete documentation
97 | - Complex dependencies
98 | - Limited testing coverage
99 |
100 |
101 |
Low Risk Indicators
102 | - Strong code quality
103 | - Comprehensive testing
104 | - Active development
105 |
106 |
107 |
108 | ## Market Analysis
109 |
110 | ### Data Sources
111 |
112 |
113 |
Primary Sources
114 | - GitHub repositories
115 | - Technical documentation
116 | - Implementation code
117 |
118 |
119 |
Secondary Sources
120 | - Market sentiment
121 | - Development activity
122 | - Community engagement
123 |
124 |
125 |
126 | ## Social Integration
127 |
128 | ### Platform Connectivity
129 |
149 |
150 | ## Technical Stack
151 |
152 | ### Core Components
153 |
154 |
155 |
Frontend
156 | - HTML5/CSS3
157 | - Vanilla JavaScript
158 | - WebSocket integration
159 |
160 |
161 |
Analysis
162 | - Claude AI
163 | - Custom scoring algorithms
164 | - Real-time processing
165 |
166 |
167 |
Data Processing
168 | - Queue management
169 | - WebSocket servers
170 | - Market data aggregation
171 |
172 |
173 | `;
174 |
175 | document.querySelector('.whitepaper-content').innerHTML = marked.parse(content);
176 |
177 | // Initialize mermaid diagrams after content is loaded
178 | mermaid.init(undefined, document.querySelectorAll('.mermaid'));
179 |
180 | // Generate table of contents
181 | generateTOC();
182 | }
183 |
184 | function generateTOC() {
185 | const headings = document.querySelectorAll('.whitepaper-content h2');
186 | const toc = document.getElementById('tableOfContents');
187 |
188 | headings.forEach(heading => {
189 | const li = document.createElement('li');
190 | const a = document.createElement('a');
191 | a.href = `#${heading.parentElement.id}`;
192 | a.textContent = heading.textContent;
193 | li.appendChild(a);
194 | toc.appendChild(li);
195 | });
196 | }
197 |
198 | function toggleMobileMenu() {
199 | document.querySelector('.nav-links').classList.toggle('active');
200 | }
--------------------------------------------------------------------------------
/tests/claudeTest.js:
--------------------------------------------------------------------------------
1 | const Anthropic = require('@anthropic-ai/sdk');
2 | require('dotenv').config();
3 |
4 | const anthropic = new Anthropic({
5 | apiKey: process.env.ANTHROPIC_API_KEY,
6 | });
7 |
8 | const TEST_REPO_DETAILS = {
9 | fullName: "test/repo",
10 | stars: 100,
11 | forks: 50,
12 | language: "JavaScript",
13 | description: "Test repository for analysis"
14 | };
15 |
16 | const TEST_CODE_CHUNK = [
17 | {
18 | path: "index.js",
19 | content: `
20 | function hello() {
21 | console.log("Hello World");
22 | }
23 |
24 | // Some AI claims
25 | const model = {
26 | type: "neural-network",
27 | layers: ["input", "hidden", "output"]
28 | };
29 | `
30 | }
31 | ];
32 |
33 | async function testClaudeAnalysis() {
34 | const prompt = `You are a technical legitimacy auditor. Analyze this repository for technical validity, potential red flags, and investment potential (Not Financial Advice).
35 |
36 | Repository Details:
37 | ${JSON.stringify(TEST_REPO_DETAILS, null, 2)}
38 |
39 | Code Contents:
40 | ${JSON.stringify(TEST_CODE_CHUNK, null, 2)}
41 |
42 | You MUST format your response EXACTLY like this template, maintaining all sections and formatting:
43 |
44 | LEGITIMACY_SCORE: [0-100]
45 | STATUS: [LEGITIMATE|PROMISING|QUESTIONABLE|SUSPICIOUS]
46 |
47 | DETAILED_SCORES:
48 | Code Reality: [0-10]
49 | Technical Coherence: [0-10]
50 | Implementation Legitimacy: [0-10]
51 | Claims Assessment: [0-10]
52 |
53 | INVESTMENT_ASSESSMENT: [INVEST|WAIT|AVOID]
54 | RISK_LEVEL: [LOW|MEDIUM|HIGH|EXTREME]
55 | INVESTMENT_REASONING:
56 | [2-3 sentences explaining the investment assessment]
57 |
58 | TECH_STACK:
59 | - [technology1 with brief context]
60 | - [technology2 with brief context]
61 |
62 | RED_FLAGS:
63 | - [specific technical concern]
64 | - [specific implementation issue]
65 | - [other red flags]
66 |
67 | ANALYSIS:
68 | 1. Technical Claims Assessment:
69 | [detailed analysis with code examples]
70 | Score: [0-10]
71 |
72 | 2. Implementation Legitimacy:
73 | [detailed analysis with code examples]
74 | Score: [0-10]
75 |
76 | 3. Technical Coherence:
77 | [detailed analysis with code examples]
78 | Score: [0-10]
79 |
80 | 4. Documentation Assessment:
81 | [detailed analysis]
82 | Score: [0-10]
83 |
84 | Instructions:
85 | 1. Replace all text in [] with actual values
86 | 2. Include specific code examples in markdown format
87 | 3. Focus on technical implementation details
88 | 4. Evaluate security and scalability concerns
89 | 5. Assess documentation completeness
90 | 6. Maintain exact formatting and section headers`;
91 |
92 | try {
93 | console.log("Sending request to Claude...");
94 | const response = await anthropic.messages.create({
95 | model: 'claude-3-sonnet-20240229',
96 | max_tokens: 4000,
97 | temperature: 0.1, // Lower temperature for more consistent outputs
98 | messages: [{ role: 'user', content: prompt }]
99 | });
100 |
101 | console.log("\nClaude's Response:\n");
102 | console.log(response.content[0].text);
103 |
104 | // Test parsing the response
105 | const parsed = parseAnalysisResponse(response.content[0].text);
106 | console.log("\nParsed Response:\n");
107 | console.log(JSON.stringify(parsed, null, 2));
108 |
109 | } catch (error) {
110 | console.error("Error:", error);
111 | }
112 | }
113 |
114 | // Reusing the parsing function from analyzer.js
115 | function parseAnalysisResponse(text) {
116 | const legitimacyScore = parseInt(text.match(/LEGITIMACY_SCORE:\s*(\d+)/)?.[1] || 0);
117 | const status = text.match(/STATUS:\s*(\w+)/)?.[1] || '';
118 |
119 | const detailedScores = {
120 | codeQuality: 0,
121 | technicalCoherence: 0,
122 | implementation: 0,
123 | documentation: 0
124 | };
125 |
126 | const scoresSection = text.match(/DETAILED_SCORES:[\s\S]*?(?=\n\n)/);
127 | if (scoresSection) {
128 | const codeReality = scoresSection[0].match(/Code Reality:\s*(\d+)/)?.[1];
129 | const techCoherence = scoresSection[0].match(/Technical Coherence:\s*(\d+)/)?.[1];
130 | const implLegitimacy = scoresSection[0].match(/Implementation Legitimacy:\s*(\d+)/)?.[1];
131 | const claimsAssessment = scoresSection[0].match(/Claims Assessment:\s*(\d+)/)?.[1];
132 |
133 | detailedScores.codeQuality = parseInt(codeReality) || 0;
134 | detailedScores.technicalCoherence = parseInt(techCoherence) || 0;
135 | detailedScores.implementation = parseInt(implLegitimacy) || 0;
136 | detailedScores.documentation = parseInt(claimsAssessment) || 0;
137 | }
138 |
139 | return {
140 | legitimacyScore,
141 | status,
142 | detailedScores,
143 | techStack: extractList(text, 'TECH_STACK'),
144 | redFlags: extractList(text, 'RED_FLAGS'),
145 | analysis: {
146 | technical: extractSection(text, '1\\. Technical Claims Assessment'),
147 | implementation: extractSection(text, '2\\. Implementation Legitimacy'),
148 | coherence: extractSection(text, '3\\. Technical Coherence'),
149 | documentation: extractSection(text, '4\\. Documentation Assessment')
150 | }
151 | };
152 | }
153 |
154 | function extractList(text, section) {
155 | const sectionMatch = text.match(new RegExp(`${section}:([\\s\\S]*?)(?=\n\\n|$)`));
156 | if (!sectionMatch) return [];
157 |
158 | return sectionMatch[1]
159 | .split('\n')
160 | .filter(line => line.trim().startsWith('-'))
161 | .map(line => line.trim().replace(/^-\s*/, ''));
162 | }
163 |
164 | function extractSection(text, sectionName) {
165 | const sectionRegex = new RegExp(`${sectionName}:([\\s\\S]*?)(?=\\d+\\.\\s|$)`);
166 | const match = text.match(sectionRegex);
167 | if (!match) return { content: '', score: 0 };
168 |
169 | const scoreMatch = match[1].match(/Score:\s*(\d+)/);
170 | return {
171 | content: match[1].replace(/Score:\s*\d+/, '').trim(),
172 | score: scoreMatch ? parseInt(scoreMatch[1]) : 0
173 | };
174 | }
175 |
176 | // Run the test
177 | testClaudeAnalysis();
--------------------------------------------------------------------------------
/public/js/header.js:
--------------------------------------------------------------------------------
1 | // Simplified header.js
2 | const FULL_CA = "5M5cPVHs9K1FoNGNgp58dAFzGvU5G5fEADZHXTHwpump";
3 |
4 | async function loadHeader() {
5 | const headerHTML = `
6 |
55 | `;
56 |
57 | // Insert header at the start of body
58 | document.body.insertAdjacentHTML('afterbegin', headerHTML);
59 |
60 | // Set up CA copy functionality
61 | const caDisplay = document.querySelector('.ca-display');
62 | if (caDisplay) {
63 | caDisplay.addEventListener('click', async () => {
64 | try {
65 | await navigator.clipboard.writeText(FULL_CA);
66 | showCopyNotification();
67 | } catch (err) {
68 | console.error('Failed to copy:', err);
69 | }
70 | });
71 | }
72 |
73 | // Set active nav link
74 | const currentPath = window.location.pathname;
75 | const navLinks = document.querySelectorAll('.nav-link');
76 | navLinks.forEach(link => {
77 | if (link.getAttribute('href') === currentPath) {
78 | link.classList.add('active');
79 | }
80 | });
81 |
82 | // Add resize handler for CA text
83 | window.addEventListener('resize', () => {
84 | const caText = document.querySelector('.ca-text');
85 | if (caText) {
86 | const fullCA = caText.dataset.full;
87 | caText.textContent = window.innerWidth <= 768 ?
88 | fullCA.substring(0, 6) :
89 | fullCA.substring(0, 6) + '...' + fullCA.slice(-4);
90 | }
91 | });
92 | }
93 |
94 | function showCopyNotification() {
95 | const notification = document.createElement('div');
96 | notification.className = 'copy-notification';
97 | notification.innerHTML = `
98 |
101 | Contract Address copied
102 | `;
103 | document.body.appendChild(notification);
104 |
105 | setTimeout(() => {
106 | notification.remove();
107 | }, 2000);
108 | }
109 |
110 | // Initialize when DOM is ready
111 | document.addEventListener('DOMContentLoaded', loadHeader);
--------------------------------------------------------------------------------
/server/discord/bot.js:
--------------------------------------------------------------------------------
1 | const { Client, GatewayIntentBits, EmbedBuilder } = require('discord.js');
2 | const { analyzeRepository } = require('../services/analyzer');
3 |
4 | class DiscordBot {
5 | constructor() {
6 | this.reconnectAttempts = 0;
7 | this.maxReconnectAttempts = 5;
8 | this.activeAnalyses = new Map();
9 |
10 | this.client = new Client({
11 | intents: [
12 | GatewayIntentBits.Guilds,
13 | GatewayIntentBits.GuildMessages,
14 | GatewayIntentBits.MessageContent
15 | ],
16 | failIfNotExists: false,
17 | retryLimit: 5,
18 | presence: {
19 | activities: [{ name: '!repo for analysis' }]
20 | }
21 | });
22 |
23 | this.setupEventHandlers();
24 | }
25 |
26 | setupEventHandlers() {
27 | this.client.on('ready', () => {
28 | console.log(`Discord bot logged in as ${this.client.user.tag}`);
29 | });
30 |
31 | this.client.on('messageCreate', async (message) => {
32 | if (message.author.bot) return;
33 |
34 | try {
35 | // Check for !repo or direct GitHub URL
36 | const githubUrl = this.extractGithubUrl(message.content);
37 | const isCommand = message.content.startsWith('!repo ');
38 |
39 | if (githubUrl || isCommand) {
40 | const repoUrl = githubUrl || this.extractGithubUrl(message.content.slice(6));
41 |
42 | if (!repoUrl) {
43 | await message.reply('Please provide a valid GitHub repository URL');
44 | return;
45 | }
46 |
47 | await this.handleAnalysis(message, repoUrl);
48 | }
49 | } catch (error) {
50 | console.error('Message handling error:', error);
51 | await message.reply('An error occurred while processing your request.');
52 | }
53 | });
54 |
55 | this.client.on('error', error => {
56 | console.error('Discord client error:', error);
57 | this.reconnect();
58 | });
59 | }
60 |
61 | extractGithubUrl(text) {
62 | if (!text) return null;
63 |
64 | // Match both direct URLs and owner/repo format
65 | const urlMatch = text.match(
66 | /(?:https?:\/\/)?(?:www\.)?github\.com\/([^\/]+)\/([^\/\s]+)/
67 | );
68 |
69 | if (urlMatch) return urlMatch[0];
70 |
71 | // Try to match owner/repo format
72 | const repoMatch = text.match(/([^\/\s]+)\/([^\/\s]+)/);
73 | if (repoMatch) return `https://github.com/${repoMatch[0]}`;
74 |
75 | return null;
76 | }
77 |
78 | async handleAnalysis(message, repoUrl) {
79 | const analysisKey = `${message.guild.id}-${message.channel.id}`;
80 |
81 | if (this.activeAnalyses.has(analysisKey)) {
82 | await message.reply('⏳ Analysis already in progress for this channel. Please wait...');
83 | return;
84 | }
85 |
86 | this.activeAnalyses.set(analysisKey, true);
87 | const statusMsg = await message.reply('🔍 Starting analysis...');
88 |
89 | try {
90 | const result = await analyzeRepository(repoUrl);
91 | await this.sendAnalysisResult(message.channel, result, statusMsg);
92 | } catch (error) {
93 | console.error('Analysis error:', error);
94 | await statusMsg.edit(`❌ Analysis failed: ${error.message || 'Unknown error'}`);
95 | } finally {
96 | this.activeAnalyses.delete(analysisKey);
97 | }
98 | }
99 |
100 | async sendAnalysisResult(channel, result, statusMsg) {
101 | const embed = new EmbedBuilder()
102 | .setColor('#0099ff')
103 | .setTitle(`Analysis: ${result.fullName}`)
104 | .setURL(`https://github.com/${result.fullName}`)
105 | .addFields(
106 | {
107 | name: '📊 Scores',
108 | value: [
109 | `Legitimacy: ${result.analysis?.legitimacyScore || 0}/100`,
110 | `Trust: ${result.analysis?.trustScore || 0}/100`
111 | ].join('\n'),
112 | inline: true
113 | },
114 | {
115 | name: '📈 Stats',
116 | value: [
117 | `Stars: ${result.stars || 0}`,
118 | `Forks: ${result.forks || 0}`,
119 | `Language: ${result.language || 'Unknown'}`
120 | ].join('\n'),
121 | inline: true
122 | }
123 | )
124 | .setDescription(result.analysis?.summary || 'No summary available')
125 | .setFooter({
126 | text: `Risk Level: ${result.analysis?.copyDetection?.riskLevel || 'Unknown'}`
127 | });
128 |
129 | if (result.analysis?.copyDetection?.flags?.length > 0) {
130 | embed.addFields({
131 | name: '⚠️ Warnings',
132 | value: result.analysis.copyDetection.flags.join('\n')
133 | });
134 | }
135 |
136 | await statusMsg.edit({
137 | content: '✅ Analysis complete!',
138 | embeds: [embed]
139 | });
140 | }
141 |
142 | async reconnect() {
143 | try {
144 | await this.client.destroy();
145 | await new Promise(resolve => setTimeout(resolve, 5000));
146 | await this.client.login(process.env.DISCORD_TOKEN);
147 | console.log('Discord bot reconnected successfully');
148 | } catch (error) {
149 | console.error('Discord reconnection failed:', error);
150 | setTimeout(() => this.reconnect(), 10000);
151 | }
152 | }
153 |
154 | start() {
155 | this.client.login(process.env.DISCORD_TOKEN)
156 | .catch(error => {
157 | console.error('Discord login failed:', error);
158 | this.reconnect();
159 | });
160 | }
161 |
162 | setupHealthCheck() {
163 | setInterval(() => {
164 | if (!this.client.ws.shards.size) {
165 | console.log('No active shards, attempting reconnect...');
166 | this.reconnect();
167 | }
168 | }, 30000);
169 | }
170 | }
171 |
172 | const discordBot = new DiscordBot();
173 | module.exports = discordBot;
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
AI0x
4 |
Market Intelligence Platform for Code Analysis
5 |
CA: 5M5cPVHs9K1FoNGNgp58dAFzGvU5G5fEADZHXTHwpump
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ## Overview
21 |
22 | AI0x is a real-time market intelligence platform that provides technical assessments and market insights for GitHub repositories. By combining deep code analysis with market sentiment data, we help identify promising technical innovations and potential risks in the rapidly evolving software ecosystem.
23 |
24 | ```mermaid
25 | graph TD
26 | A[Repository Input] --> B[Technical Analysis]
27 | B --> C[Code Quality]
28 | B --> D[Project Structure]
29 | B --> E[Implementation]
30 | B --> F[Documentation]
31 | C & D & E & F --> G[Legitimacy Score]
32 | H[Market Data] --> G
33 | G --> I[Final Assessment]
34 | ```
35 |
36 | ## Key Features
37 |
38 | ### Technical Assessment
39 | Our comprehensive analysis system evaluates repositories across four key dimensions:
40 |
41 |
42 |
43 | |
44 |
45 | #### Code Quality
46 | - Organization
47 | - Error Handling
48 | - Performance
49 | - Best Practices
50 | - Security
51 |
52 | |
53 |
54 |
55 | #### Project Structure
56 | - Architecture
57 | - Dependencies
58 | - Configuration
59 | - Build System
60 | - Resources
61 |
62 | |
63 |
64 |
65 | #### Implementation
66 | - Core Features
67 | - API Design
68 | - Data Flow
69 | - Security
70 | - Efficiency
71 |
72 | |
73 |
74 |
75 | #### Documentation
76 | - Code Comments
77 | - API Reference
78 | - Setup Guide
79 | - Architecture
80 | - Examples
81 |
82 | |
83 |
84 |
85 |
86 | ### Market Intelligence
87 |
88 | ```mermaid
89 | sequenceDiagram
90 | participant User
91 | participant Platform
92 | participant Analysis
93 | participant Market
94 |
95 | User->>Platform: Submit Repository
96 | Platform->>Analysis: Process Code
97 | Analysis->>Market: Gather Data
98 | Market-->>Analysis: Market Context
99 | Analysis-->>Platform: Generate Insights
100 | Platform-->>User: Deliver Report
101 | ```
102 |
103 | - Technical legitimacy scoring (0-100)
104 | - Risk assessment (Low to Extreme)
105 | - Implementation confidence metrics
106 | - Investment potential rating
107 | - Growth trajectory analysis
108 | - Technical trend detection
109 | - Automated market updates
110 |
111 | ### Real-time Processing
112 |
113 | - Live WebSocket updates
114 | - Queue management system
115 | - Progress tracking
116 | - Market data refreshes
117 | - Social platform integration
118 | - Position monitoring
119 | - Instant notifications
120 |
121 | ## Architecture
122 |
123 | ### Technology Stack
124 |
125 |
126 |
127 | |
128 |
129 | #### Frontend
130 | - HTML5/CSS3
131 | - JavaScript
132 | - WebSocket
133 | - Mermaid.js
134 | - Marked.js
135 |
136 | |
137 |
138 |
139 | #### Backend
140 | - Node.js/Express
141 | - Socket.IO
142 | - MongoDB
143 | - Redis
144 | - Bull
145 |
146 | |
147 |
148 |
149 | #### Analysis
150 | - Claude 3 Sonnet
151 | - Custom Algorithms
152 | - Pattern Detection
153 | - Risk Assessment
154 |
155 | |
156 |
157 |
158 | #### Integration
159 | - GitHub API
160 | - Discord.js
161 | - Telegraf
162 | - Twitter API
163 | - WebSocket
164 |
165 | |
166 |
167 |
168 |
169 | ## Getting Started
170 |
171 | ### Prerequisites
172 | - Node.js 16+
173 | - MongoDB
174 | - Redis
175 |
176 | ### Installation
177 |
178 | 1. Clone the repository
179 | ```bash
180 | git clone https://github.com/ai0x/ai0x.git
181 | cd ai0x
182 | ```
183 |
184 | 2. Install dependencies
185 | ```bash
186 | npm install
187 | ```
188 |
189 | 3. Configure environment
190 | ```bash
191 | # .env
192 | ANTHROPIC_API_KEY=your_key
193 | GITHUB_TOKEN=your_token
194 | MONGODB_URI=mongodb://localhost/ai0x
195 | REDIS_URL=redis://localhost:6379
196 | PORT=3000
197 |
198 | # Social Integration
199 | TWITTER_USERNAME=your_username
200 | TWITTER_PASSWORD=your_password
201 | DISCORD_WEBHOOK=your_webhook
202 | TELEGRAM_BOT_TOKEN=your_token
203 | ```
204 |
205 | 4. Start the server
206 | ```bash
207 | # Development
208 | npm run dev
209 |
210 | # Production
211 | npm start
212 | ```
213 |
214 | ## API Reference
215 |
216 | ### Analysis Endpoints
217 | ```http
218 | POST /api/analyze
219 | GET /api/repository/:owner/:repo
220 | GET /api/recent
221 | GET /api/analyses
222 | GET /api/insights
223 | GET /api/trends
224 | ```
225 |
226 | ### Queue Management
227 | ```http
228 | GET /api/queue-position/:jobId
229 | GET /api/queue/status
230 | POST /api/cleanup
231 | ```
232 |
233 | ## Security
234 |
235 | ### Protection Measures
236 | - Secure WebSocket connections
237 | - API authentication
238 | - Rate limiting (Express + Redis)
239 | - Data encryption
240 | - Access control
241 | - Session management
242 |
243 | ### Risk Management
244 | - Input validation
245 | - Error handling
246 | - Audit logging
247 | - Backup systems
248 | - Continuous monitoring
249 | - Security best practices
250 |
251 | ## Community
252 |
253 | Join our growing community:
254 |
255 | - 𝕏 [@ai0xdotfun](https://x.com/ai0xdotfun) - Market updates & alerts
256 | - [Discord](https://discord.gg/sT4aCagN6v) - Technical discussions & bug reports
257 | - [Telegram](https://t.me/ai0xportal)
258 |
259 |
260 | ## Documentation
261 |
262 | For detailed technical information, please refer to our [Technical Whitepaper](public/whitepaper.html).
263 |
264 | ## Dependencies
265 |
266 | ```json
267 | {
268 | "@anthropic-ai/sdk": "^0.14.1",
269 | "@octokit/rest": "^19.0.13",
270 | "bull": "^4.16.5",
271 | "discord.js": "^14.17.2",
272 | "express": "^4.18.2",
273 | "ioredis": "^5.4.2",
274 | "mongoose": "^8.2.0",
275 | "socket.io": "^4.8.1",
276 | "telegraf": "^4.16.3"
277 | }
278 | ```
279 |
280 | ## Contributing
281 |
282 | We welcome contributions!
283 |
284 | ## License
285 |
286 | This project is licensed under the MIT License
287 |
--------------------------------------------------------------------------------
/public/css/explore.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --bg-primary: #0d1117;
3 | --bg-secondary: #161b22;
4 | --text-primary: #e6edf3;
5 | --text-secondary: #7d8590;
6 | --border: #30363d;
7 | --accent: #eab308;
8 | --hover: rgba(234, 179, 8, 0.1);
9 | }
10 |
11 | /* Base layout */
12 | body {
13 | background-color: #0d1117;
14 | color: #e6edf3;
15 | min-height: 100vh;
16 | margin: 0;
17 | padding: 0;
18 | }
19 |
20 | .container {
21 | max-width: 1400px;
22 | margin: 0 auto;
23 | padding: 2rem;
24 | }
25 |
26 | /* Stats cards */
27 | .stats-bar {
28 | display: grid;
29 | grid-template-columns: repeat(3, 1fr);
30 | gap: 1.5rem;
31 | margin-bottom: 2rem;
32 | }
33 |
34 | .stat-item {
35 | background: #161b22;
36 | padding: 2rem;
37 | border-radius: 12px;
38 | text-align: center;
39 | border: 1px solid #30363d;
40 | transition: all 0.2s ease;
41 | }
42 |
43 | .stat-item:hover {
44 | transform: translateY(-2px);
45 | border-color: #eab308;
46 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
47 | }
48 |
49 | .stat-value {
50 | font-size: 3rem;
51 | font-weight: bold;
52 | color: #eab308;
53 | margin-bottom: 1rem;
54 | font-family: 'JetBrains Mono', monospace;
55 | }
56 |
57 | .stat-label {
58 | color: #7d8590;
59 | font-size: 0.875rem;
60 | text-transform: uppercase;
61 | letter-spacing: 0.5px;
62 | }
63 |
64 | /* Search and filters */
65 | .explore-controls {
66 | background: #161b22;
67 | padding: 2rem;
68 | border-radius: 12px;
69 | border: 1px solid #30363d;
70 | margin: 2rem 0;
71 | }
72 |
73 | .search-box {
74 | margin-bottom: 1.5rem;
75 | }
76 |
77 | .search-input {
78 | width: 100%;
79 | padding: 1rem;
80 | background: #0d1117;
81 | border: 1px solid #30363d;
82 | border-radius: 8px;
83 | color: #eab308;
84 | font-family: 'JetBrains Mono', monospace;
85 | font-size: 1rem;
86 | }
87 |
88 | .search-input:focus {
89 | outline: none;
90 | border-color: #eab308;
91 | box-shadow: 0 0 0 2px rgba(234, 179, 8, 0.1);
92 | }
93 |
94 | .search-input::placeholder {
95 | color: rgba(234, 179, 8, 0.5);
96 | }
97 |
98 | .filters {
99 | display: flex;
100 | gap: 1.5rem;
101 | }
102 |
103 | .filter-group {
104 | flex: 1;
105 | }
106 |
107 | .filter-select {
108 | width: 100%;
109 | padding: 0.875rem;
110 | background: #0d1117;
111 | border: 1px solid #30363d;
112 | border-radius: 8px;
113 | color: #e6edf3;
114 | font-family: 'JetBrains Mono', monospace;
115 | cursor: pointer;
116 | }
117 |
118 | .filter-select:focus {
119 | outline: none;
120 | border-color: #eab308;
121 | }
122 |
123 | /* Repository grid */
124 | .repositories-grid {
125 | display: grid;
126 | grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
127 | gap: 1.5rem;
128 | margin: 2rem 0;
129 | }
130 |
131 | .repository-card {
132 | background: #161b22;
133 | border: 1px solid #30363d;
134 | border-radius: 12px;
135 | padding: 1.5rem;
136 | cursor: pointer;
137 | transition: all 0.2s ease;
138 | }
139 |
140 | .repository-card:hover {
141 | transform: translateY(-2px);
142 | border-color: #eab308;
143 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
144 | }
145 |
146 | .repo-header {
147 | display: flex;
148 | justify-content: space-between;
149 | align-items: flex-start;
150 | margin-bottom: 1rem;
151 | }
152 |
153 | .repo-name {
154 | color: #e6edf3;
155 | font-size: 1rem;
156 | font-weight: 500;
157 | font-family: 'JetBrains Mono', monospace;
158 | }
159 |
160 | .repo-stats {
161 | display: flex;
162 | gap: 1rem;
163 | color: #7d8590;
164 | font-size: 0.875rem;
165 | }
166 |
167 | .repo-description {
168 | color: #7d8590;
169 | font-size: 0.875rem;
170 | line-height: 1.5;
171 | margin: 1rem 0;
172 | display: -webkit-box;
173 | -webkit-line-clamp: 2;
174 | -webkit-box-orient: vertical;
175 | overflow: hidden;
176 | }
177 |
178 | .score-badge {
179 | display: inline-block;
180 | padding: 0.5rem 1rem;
181 | border-radius: 8px;
182 | font-size: 0.875rem;
183 | font-family: 'JetBrains Mono', monospace;
184 | }
185 |
186 | .score-exceptional {
187 | background: rgba(34, 197, 94, 0.1);
188 | color: #22c55e;
189 | }
190 |
191 | .score-good {
192 | background: rgba(234, 179, 8, 0.1);
193 | color: #eab308;
194 | }
195 |
196 | .score-fair {
197 | background: rgba(249, 115, 22, 0.1);
198 | color: #f97316;
199 | }
200 |
201 | .score-needs-work {
202 | background: rgba(239, 68, 68, 0.1);
203 | color: #ef4444;
204 | }
205 |
206 | .repo-meta {
207 | display: flex;
208 | justify-content: space-between;
209 | align-items: center;
210 | margin-top: 1rem;
211 | padding-top: 1rem;
212 | border-top: 1px solid #30363d;
213 | color: #7d8590;
214 | font-size: 0.75rem;
215 | }
216 |
217 | /* Pagination */
218 | .pagination {
219 | display: flex;
220 | justify-content: center;
221 | gap: 0.5rem;
222 | margin: 2rem 0;
223 | }
224 |
225 | .page-button {
226 | padding: 0.75rem 1rem;
227 | background: #161b22;
228 | border: 1px solid #30363d;
229 | border-radius: 8px;
230 | color: #e6edf3;
231 | cursor: pointer;
232 | font-family: 'JetBrains Mono', monospace;
233 | }
234 |
235 | .page-button.active {
236 | background: #eab308;
237 | color: #0d1117;
238 | border-color: #eab308;
239 | }
240 |
241 | .page-button:hover:not(.active) {
242 | border-color: #eab308;
243 | background: #1c2128;
244 | }
245 |
246 | /* States */
247 | .loading, .error, .no-results {
248 | text-align: center;
249 | padding: 3rem;
250 | background: #161b22;
251 | border-radius: 12px;
252 | border: 1px solid #30363d;
253 | color: #7d8590;
254 | font-family: 'JetBrains Mono', monospace;
255 | margin: 2rem 0;
256 | }
257 |
258 | .error {
259 | background: var(--bg-secondary);
260 | border: 1px solid var(--border);
261 | border-radius: 8px;
262 | padding: 2rem;
263 | text-align: center;
264 | margin: 2rem 0;
265 | }
266 |
267 | .error h3 {
268 | color: var(--text-primary);
269 | margin-bottom: 1rem;
270 | }
271 |
272 | .error p {
273 | color: var(--text-secondary);
274 | margin-bottom: 1.5rem;
275 | }
276 |
277 | .retry-button {
278 | background: var(--accent);
279 | color: var(--bg-primary);
280 | border: none;
281 | padding: 0.75rem 1.5rem;
282 | border-radius: 6px;
283 | cursor: pointer;
284 | font-family: 'JetBrains Mono', monospace;
285 | transition: all 0.2s ease;
286 | }
287 |
288 | .retry-button:hover {
289 | opacity: 0.9;
290 | transform: translateY(-1px);
291 | }
292 |
293 | .error-details {
294 | margin-top: 0.5rem;
295 | font-size: 0.875rem;
296 | color: #7d8590;
297 | }
--------------------------------------------------------------------------------
/public/css/header.css:
--------------------------------------------------------------------------------
1 | .header {
2 | background: linear-gradient(to right, #1a1a1a, #2d2d2d);
3 | padding: 1rem;
4 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
5 | position: sticky;
6 | top: 0;
7 | z-index: 1000;
8 | }
9 |
10 | .header-content {
11 | max-width: 1200px;
12 | margin: 0 auto;
13 | display: flex;
14 | justify-content: space-between;
15 | align-items: center;
16 | padding: 0 1.5rem;
17 | gap: 2rem;
18 | }
19 |
20 | .header-left {
21 | display: flex;
22 | align-items: center;
23 | gap: 1rem;
24 | }
25 |
26 | .logo {
27 | display: flex;
28 | align-items: center;
29 | text-decoration: none;
30 | gap: 0.5rem;
31 | }
32 |
33 | .logo-img {
34 | width: 32px;
35 | height: 32px;
36 | border-radius: 50%;
37 | transition: transform 0.3s ease;
38 | }
39 |
40 | .logo:hover .logo-img {
41 | transform: scale(1.1);
42 | }
43 |
44 | .logo-text {
45 | font-size: 1.5rem;
46 | font-weight: bold;
47 | color: #fff;
48 | font-family: 'JetBrains Mono', monospace;
49 | }
50 |
51 | .nav-links {
52 | display: flex;
53 | align-items: center;
54 | gap: 1.25rem;
55 | }
56 |
57 | .nav-link {
58 | color: #b3b3b3;
59 | text-decoration: none;
60 | font-size: 0.9rem;
61 | font-weight: 500;
62 | padding: 0.5rem 0.75rem;
63 | border-radius: 4px;
64 | transition: all 0.2s ease;
65 | position: relative;
66 | }
67 |
68 | .nav-link:hover {
69 | color: #fff;
70 | background: rgba(255, 255, 255, 0.1);
71 | }
72 |
73 | .nav-link.active {
74 | color: #fff;
75 | background: rgba(255, 255, 255, 0.15);
76 | }
77 |
78 | .nav-link.active::after {
79 | content: '';
80 | position: absolute;
81 | bottom: 0;
82 | left: 50%;
83 | transform: translateX(-50%);
84 | width: 20px;
85 | height: 2px;
86 | background: #4CAF50;
87 | border-radius: 2px;
88 | }
89 |
90 | .social-links {
91 | display: flex;
92 | gap: 0.75rem;
93 | margin-left: 0.5rem;
94 | }
95 |
96 | .social-link {
97 | color: #b3b3b3;
98 | transition: all 0.2s ease;
99 | padding: 0.5rem;
100 | border-radius: 50%;
101 | display: flex;
102 | align-items: center;
103 | justify-content: center;
104 | }
105 |
106 | .social-link:hover {
107 | color: #fff;
108 | background: rgba(255, 255, 255, 0.1);
109 | transform: translateY(-2px);
110 | }
111 |
112 | /* Mobile responsive styles */
113 | @media (max-width: 768px) {
114 | .header-content {
115 | padding: 1rem;
116 | }
117 |
118 | .menu-btn {
119 | display: block;
120 | }
121 |
122 | .nav-links {
123 | display: none;
124 | position: fixed;
125 | top: 70px;
126 | left: 0;
127 | right: 0;
128 | background: #1a1a1a;
129 | padding: 1rem;
130 | flex-direction: column;
131 | align-items: center;
132 | gap: 1rem;
133 | border-top: 1px solid rgba(255, 255, 255, 0.1);
134 | }
135 |
136 | .nav-links.active {
137 | display: flex;
138 | }
139 |
140 | .nav-link {
141 | width: 100%;
142 | text-align: center;
143 | padding: 0.75rem;
144 | }
145 |
146 | .ca-display {
147 | width: 100%;
148 | justify-content: center;
149 | margin: 0.75rem 0;
150 | padding: 8px 12px;
151 | }
152 |
153 | .ca-display .ca-text {
154 | font-size: 0.8rem;
155 | }
156 |
157 | .social-links {
158 | margin: 0.5rem 0;
159 | justify-content: center;
160 | gap: 1rem;
161 | }
162 | }
163 |
164 | /* Add these styles for better mobile handling */
165 | @media (max-width: 480px) {
166 | .logo-text {
167 | font-size: 1.2rem;
168 | }
169 |
170 | .nav-link {
171 | width: 100%;
172 | text-align: center;
173 | padding: 0.75rem;
174 | }
175 |
176 | .social-links {
177 | gap: 1.5rem;
178 | }
179 | }
180 |
181 | .ca-display {
182 | cursor: pointer;
183 | transition: all 0.3s ease;
184 | padding: 6px 12px;
185 | border-radius: 4px;
186 | background: rgba(255, 255, 255, 0.08);
187 | display: flex;
188 | align-items: center;
189 | gap: 6px;
190 | margin: 0 0.5rem;
191 | }
192 |
193 | .ca-display .ca-text {
194 | font-family: 'JetBrains Mono', monospace;
195 | font-size: 0.85rem;
196 | color: #fff;
197 | min-width: 80px;
198 | transition: min-width 0.3s ease;
199 | }
200 |
201 | .ca-display .copy-icon {
202 | font-size: 0.9rem;
203 | color: #4CAF50;
204 | opacity: 0.8;
205 | padding: 4px;
206 | }
207 |
208 | .ca-display:hover {
209 | background: rgba(255, 255, 255, 0.2);
210 | }
211 |
212 | .ca-display:hover .copy-icon {
213 | opacity: 1;
214 | }
215 |
216 | .ca-display.copied {
217 | background: rgba(76, 175, 80, 0.2) !important;
218 | }
219 |
220 | .copy-notification {
221 | position: fixed;
222 | top: 20px;
223 | right: 20px;
224 | background: #1a1a1a;
225 | color: white;
226 | padding: 0.75rem 1.5rem;
227 | border-radius: 4px;
228 | font-size: 0.9rem;
229 | display: flex;
230 | align-items: center;
231 | gap: 8px;
232 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
233 | border: 1px solid rgba(76, 175, 80, 0.3);
234 | animation: slideIn 0.3s ease, slideOut 0.3s ease 1.7s;
235 | }
236 |
237 | .copy-notification i {
238 | color: #4CAF50;
239 | }
240 |
241 | @media (max-width: 768px) {
242 | .ca-display {
243 | padding: 8px 12px;
244 | width: auto;
245 | min-width: 120px;
246 | background: rgba(255, 255, 255, 0.15);
247 | }
248 |
249 | .ca-display .ca-text {
250 | font-size: 0.85rem;
251 | }
252 |
253 | .ca-display .copy-icon {
254 | opacity: 1;
255 | font-size: 1rem;
256 | }
257 | }
258 |
259 | .menu-btn {
260 | display: none;
261 | background: none;
262 | border: none;
263 | color: #fff;
264 | cursor: pointer;
265 | padding: 8px;
266 | }
267 |
268 | .menu-icon {
269 | display: block;
270 | }
271 |
272 | .menu-icon-close {
273 | display: none;
274 | }
275 |
276 | .menu-btn.active .menu-icon-bars {
277 | display: none;
278 | }
279 |
280 | .menu-btn.active .menu-icon-close {
281 | display: block;
282 | }
283 |
284 | @media (max-width: 768px) {
285 | .menu-btn {
286 | display: block;
287 | }
288 |
289 | .nav-links {
290 | display: none;
291 | position: fixed;
292 | top: 70px;
293 | left: 0;
294 | right: 0;
295 | background: #1a1a1a;
296 | padding: 1rem;
297 | flex-direction: column;
298 | align-items: center;
299 | gap: 1rem;
300 | border-top: 1px solid rgba(255, 255, 255, 0.1);
301 | }
302 |
303 | .nav-links.active {
304 | display: flex;
305 | }
306 |
307 | .nav-link {
308 | width: 100%;
309 | text-align: center;
310 | padding: 0.75rem;
311 | }
312 |
313 | .ca-display {
314 | width: 100%;
315 | justify-content: center;
316 | margin: 0.75rem 0;
317 | }
318 |
319 | .social-links {
320 | margin: 0.5rem 0;
321 | justify-content: center;
322 | gap: 1rem;
323 | }
324 | }
--------------------------------------------------------------------------------
/public/search.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | AI0x | Search Repositories
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Search GitHub Repositories
21 |
22 |
24 |
25 |
26 |
27 |
30 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
161 |
162 |
--------------------------------------------------------------------------------
/services/siteAnalyzer.js:
--------------------------------------------------------------------------------
1 | const Anthropic = require('@anthropic-ai/sdk');
2 | const { extractScores, calculateTrustScore, calculateFinalLegitimacyScore } = require('../utils/analysisUtils');
3 |
4 | const anthropic = new Anthropic({
5 | apiKey: process.env.ANTHROPIC_API_KEY,
6 | });
7 |
8 | async function analyzeSiteContent(scrapeData) {
9 | try {
10 | const analysisPrompt = `# Site Analysis Categories
11 |
12 | ## Technical Implementation (Score: [0-25]/25)
13 | - Frontend frameworks and libraries
14 | - Performance optimization
15 | - Code quality and organization
16 | - Error handling and resilience
17 |
18 | ## Security & Infrastructure (Score: [0-25]/25)
19 | - Security headers and protocols
20 | - SSL/TLS implementation
21 | - Access controls
22 | - Data protection measures
23 |
24 | ## SEO & Accessibility (Score: [0-25]/25)
25 | - Meta information
26 | - Search engine optimization
27 | - Accessibility standards
28 | - Mobile responsiveness
29 |
30 | ## Content & Structure (Score: [0-25]/25)
31 | - Information architecture
32 | - Content organization
33 | - User experience
34 | - Navigation design
35 |
36 | ## Red Flags
37 | - Security vulnerabilities
38 | - Performance issues
39 | - Accessibility problems
40 | - UX concerns
41 |
42 | ## Overall Assessment
43 | Provide a comprehensive evaluation of the site's technical implementation, security, and user experience.
44 |
45 | # Site Details
46 | URL: ${scrapeData.metadata.sourceURL}
47 | Title: ${scrapeData.metadata.title}
48 | Description: ${scrapeData.metadata.description}
49 |
50 | # Technical Review
51 | HTML Content Sample:
52 | \`\`\`html
53 | ${scrapeData.html.substring(0, 1500)}...
54 | \`\`\`
55 |
56 | Metadata:
57 | ${JSON.stringify(scrapeData.metadata, null, 2)}
58 |
59 | # Analysis Points
60 |
61 | ## Technology Stack
62 | - Identify frameworks and libraries
63 | - Evaluate implementation quality
64 | - Assess build optimization
65 | - Check for best practices
66 |
67 | ## Security Analysis
68 | - Review security headers
69 | - Check SSL configuration
70 | - Evaluate access controls
71 | - Identify vulnerabilities
72 |
73 | ## Performance Review
74 | - Load time indicators
75 | - Resource optimization
76 | - Caching implementation
77 | - Mobile performance
78 |
79 | ## SEO & Accessibility
80 | - Meta tags implementation
81 | - Schema markup
82 | - ARIA compliance
83 | - Mobile friendliness
84 |
85 | Provide scores as "Score: X/25" format. Include specific examples to support findings.`;
86 |
87 | const analysisResponse = await anthropic.messages.create({
88 | model: 'claude-3-5-sonnet-20241022',
89 | max_tokens: 4000,
90 | temperature: 0.3,
91 | messages: [{
92 | role: 'user',
93 | content: `You are a technical website analyzer. Analyze this site data and provide a detailed assessment. Start directly with the scores and analysis without any introductory text.\n\n${analysisPrompt}`
94 | }]
95 | });
96 |
97 | const analysis = analysisResponse.content[0].text;
98 | const scores = extractScores(analysis);
99 | const trustScore = calculateTrustScore({
100 | redFlags: analysis.match(/## Red Flags\n([\s\S]*?)(?=\n##|$)/)?.[1]?.split('\n').filter(line => line.trim().startsWith('-')) || [],
101 | larpIndicators: [],
102 | misrepresentationChecks: [],
103 | aiAnalysis: { misleadingLevel: 'None', concerns: [] }
104 | });
105 |
106 | const finalScore = calculateFinalLegitimacyScore(scores.legitimacyScore, trustScore);
107 |
108 | return {
109 | success: true,
110 | analysis: {
111 | detailedScores: scores.detailedScores,
112 | legitimacyScore: scores.legitimacyScore,
113 | trustScore,
114 | finalScore,
115 | fullAnalysis: analysis,
116 | metadata: scrapeData.metadata,
117 | technologies: detectTechnologies(scrapeData.html),
118 | performance: analyzePerformance(scrapeData),
119 | security: analyzeSecurityFeatures(scrapeData.metadata),
120 | seo: analyzeSEO(scrapeData.metadata),
121 | accessibility: analyzeAccessibility(scrapeData.html)
122 | }
123 | };
124 | } catch (error) {
125 | console.error('Error analyzing site:', error);
126 | throw error;
127 | }
128 | }
129 |
130 | function detectTechnologies(html) {
131 | const techs = [];
132 | if (html.includes('react')) techs.push({ name: 'React', confidence: 'High' });
133 | if (html.includes('next')) techs.push({ name: 'Next.js', confidence: 'High' });
134 | if (html.includes('vue')) techs.push({ name: 'Vue.js', confidence: 'High' });
135 | if (html.includes('angular')) techs.push({ name: 'Angular', confidence: 'High' });
136 | if (html.includes('tailwind')) techs.push({ name: 'Tailwind CSS', confidence: 'High' });
137 | if (html.includes('bootstrap')) techs.push({ name: 'Bootstrap', confidence: 'High' });
138 | return techs;
139 | }
140 |
141 | function analyzePerformance(data) {
142 | return {
143 | pageSize: data.html.length,
144 | resourceCount: {
145 | images: (data.html.match(/
= 90) return { label: 'Excellent', icon: '🟢' };
204 | if (score >= 70) return { label: 'Good', icon: '🟡' };
205 | if (score >= 50) return { label: 'Needs Improvement', icon: '🟠' };
206 | return { label: 'Poor', icon: '🔴' };
207 | }
208 |
209 | module.exports = { analyzeSiteContent };
--------------------------------------------------------------------------------
/public/js/main.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', async function() {
2 | const socket = io();
3 | let currentAnalysis = null;
4 |
5 | // Load recent analyses and most recent analysis for placeholder
6 | async function loadRecentAnalyses() {
7 | try {
8 | const response = await fetch('/api/recent-analyses');
9 |
10 | // Check if response is ok before trying to parse JSON
11 | if (!response.ok) {
12 | throw new Error(`Server returned ${response.status}: ${response.statusText}`);
13 | }
14 |
15 | const data = await response.json();
16 |
17 | if (data && data.analyses && data.analyses.length > 0) {
18 | // Update recent list
19 | displayRecentList(data.analyses);
20 |
21 | // Display most recent analysis in the placeholder
22 | const mostRecent = data.analyses[0];
23 | displayAnalysis(mostRecent, 'lastAnalysis');
24 | } else {
25 | document.getElementById('lastAnalysis').innerHTML = `
26 |
27 |
No analyses yet. Start by analyzing a repository!
28 |
29 | `;
30 | document.getElementById('recentList').innerHTML = `
31 |
32 |
No recent analyses
33 |
34 | `;
35 | }
36 | } catch (error) {
37 | console.error('Failed to load recent analyses:', error);
38 | const errorMessage = error.message.includes('Failed to fetch') ?
39 | 'Unable to connect to server' :
40 | 'Failed to load recent analyses';
41 |
42 | document.getElementById('recentList').innerHTML = `
43 |
44 |
${errorMessage}
45 |
Please try again later
46 |
47 | `;
48 | document.getElementById('lastAnalysis').innerHTML = `
49 |
50 |
${errorMessage}
51 |
Please try again later
52 |
53 | `;
54 | }
55 | }
56 |
57 | // Display analysis results in a specified container
58 | function displayAnalysis(data, containerId = 'result') {
59 | const container = document.getElementById(containerId);
60 | if (!container) return;
61 |
62 | // Create repository header
63 | const repoHeader = `
64 |
77 |
78 |
79 | Legitimacy Score
80 | ${data.analysis?.legitimacyScore || 'N/A'}
81 |
82 |
83 | Trust Score
84 | ${data.analysis?.trustScore || 'N/A'}
85 |
86 |
`;
87 |
88 | // Convert markdown to HTML for the analysis content
89 | const analysisContent = marked.parse(data.analysis?.fullAnalysis || data.analysis || '');
90 |
91 | container.innerHTML = `
92 | ${repoHeader}
93 |
94 | ${analysisContent}
95 |
96 | `;
97 | }
98 |
99 | // Display recent analyses list
100 | function displayRecentList(analyses) {
101 | const recentList = document.getElementById('recentList');
102 | if (!recentList) return;
103 |
104 | const recentHtml = analyses.map(analysis => `
105 |
106 |
${analysis.repoFullName}
107 |
${new Date(analysis.timestamp).toLocaleString()}
108 |
109 | `).join('');
110 |
111 | recentList.innerHTML = `${recentHtml}
`;
112 |
113 | // Add click handlers for analysis cards
114 | document.querySelectorAll('.analysis-card').forEach(card => {
115 | card.addEventListener('click', () => {
116 | window.location.href = `/analysis.html?repo=${card.dataset.repo}`;
117 | });
118 | });
119 | }
120 |
121 | // Handle repository analysis
122 | async function analyzeRepository(repoUrl) {
123 | try {
124 | // Extract owner and repo from URL
125 | const match = repoUrl.match(/github\.com\/([^/]+)\/([^/]+)/);
126 | if (!match) {
127 | throw new Error('Invalid GitHub repository URL');
128 | }
129 |
130 | const [, owner, repo] = match;
131 | document.getElementById('result').innerHTML = 'Analyzing repository...
';
132 |
133 | // Start analysis
134 | const response = await fetch('/api/analyze', {
135 | method: 'POST',
136 | headers: { 'Content-Type': 'application/json' },
137 | body: JSON.stringify({ owner, repo })
138 | });
139 |
140 | if (!response.ok) {
141 | const error = await response.json();
142 | throw new Error(error.message || 'Analysis failed');
143 | }
144 |
145 | // Redirect to analysis page
146 | window.location.href = `/analysis.html?repo=${owner}/${repo}`;
147 |
148 | } catch (error) {
149 | document.getElementById('result').innerHTML = `
150 |
151 | ${error.message || 'Failed to analyze repository'}
152 |
153 | `;
154 | }
155 | }
156 |
157 | // Event Listeners
158 | document.getElementById('analyzeButton').addEventListener('click', () => {
159 | const repoUrl = document.getElementById('repoUrl').value.trim();
160 | if (repoUrl) {
161 | analyzeRepository(repoUrl);
162 | }
163 | });
164 |
165 | document.getElementById('repoUrl').addEventListener('keypress', (e) => {
166 | if (e.key === 'Enter') {
167 | const repoUrl = e.target.value.trim();
168 | if (repoUrl) {
169 | analyzeRepository(repoUrl);
170 | }
171 | }
172 | });
173 |
174 | // Socket.io event handlers
175 | socket.on('analysisProgress', (data) => {
176 | if (currentAnalysis === data.id) {
177 | document.getElementById('result').innerHTML = `
178 |
179 | ${data.message || 'Analyzing repository...'}
180 |
181 | `;
182 | }
183 | });
184 |
185 | // Load initial data
186 | loadRecentAnalyses();
187 | });
--------------------------------------------------------------------------------
/services/analyzer.js:
--------------------------------------------------------------------------------
1 | const Anthropic = require('@anthropic-ai/sdk');
2 | const { getRepoDetails, getRepoContents, parseGitHubUrl } = require('../utils/githubUtils');
3 | const {
4 | sanitizeCodeContent,
5 | getFileExtension,
6 | extractScores,
7 | extractCodeReview,
8 | calculateTrustScore,
9 | calculateFinalLegitimacyScore
10 | } = require('../utils/analysisUtils');
11 | const { saveAnalysis } = require('./historyManager');
12 | const TwitterAgent = require('./twitterAgent');
13 |
14 | let twitterAgent = null;
15 |
16 | // Initialize twitter agent
17 | const initTwitterAgent = async () => {
18 | if (!twitterAgent) {
19 | twitterAgent = new TwitterAgent();
20 | await twitterAgent.init();
21 | }
22 | return twitterAgent;
23 | };
24 |
25 | const anthropic = new Anthropic({
26 | apiKey: process.env.ANTHROPIC_API_KEY,
27 | });
28 |
29 | async function analyzeRepo(repoInfo) {
30 | try {
31 | if (!repoInfo || !repoInfo.owner || !repoInfo.repo) {
32 | throw new Error('Invalid repository info');
33 | }
34 |
35 | console.log(`Performing new analysis for: ${repoInfo.owner}/${repoInfo.repo}`);
36 |
37 | // Get repository details from GitHub
38 | const repoDetails = await getRepoDetails(repoInfo);
39 | if (!repoDetails) {
40 | throw new Error('Repository not found');
41 | }
42 |
43 | const files = await getRepoContents(repoInfo);
44 |
45 | const analysisPrompt = `# Analysis Categories
46 |
47 | ## Code Quality (Score: [0-25]/25)
48 | - Architecture patterns and design principles
49 | - Code organization and modularity
50 | - Error handling and resilience
51 | - Performance optimization
52 | - Best practices adherence
53 |
54 | ## Project Structure (Score: [0-25]/25)
55 | - Directory organization
56 | - Dependency management
57 | - Configuration approach
58 | - Build system
59 | - Resource organization
60 |
61 | ## Implementation (Score: [0-25]/25)
62 | - Core functionality implementation
63 | - API integrations and interfaces
64 | - Data flow and state management
65 | - Security practices
66 | - Code efficiency and scalability
67 |
68 | ## Documentation (Score: [0-25]/25)
69 | - Code comments and documentation
70 | - API documentation
71 | - Setup instructions
72 | - Architecture documentation
73 | - Usage examples and guides
74 |
75 | ## Misrepresentation Checks
76 | - Check for code authenticity
77 | - Verify claimed features
78 | - Validate technical claims
79 | - Cross-reference documentation
80 |
81 | ## LARP Indicators
82 | - Code implementation depth
83 | - Feature completeness
84 | - Development history
85 | - Technical consistency
86 |
87 | ## Red Flags
88 | - Security concerns
89 | - Implementation issues
90 | - Documentation gaps
91 | - Architectural problems
92 |
93 | ## Overall Assessment
94 | Provide a comprehensive evaluation of the project's technical merit, implementation quality, and potential risks.
95 |
96 | ## Investment Ranking (NFA)
97 | Rating: [High/Medium/Low]
98 | Confidence: [0-100]%
99 | - Include key factors influencing the rating
100 | - List major considerations
101 | - Note potential risks and opportunities
102 |
103 | ## AI Implementation Analysis
104 | - Identify and list any AI/ML components
105 | - Evaluate implementation quality and correctness
106 | - Check for misleading AI claims
107 | - Assess model integration and usage
108 | - Verify data processing methods
109 | - Compare claimed vs actual AI capabilities
110 | - Note any AI-related security concerns
111 | - Check for proper model attribution
112 | - Evaluate AI performance considerations
113 |
114 | Rate the AI implementation if present:
115 | AI Score: [0-100]
116 | Misleading Level: [None/Low/Medium/High]
117 | Implementation Quality: [Poor/Basic/Good/Excellent]
118 |
119 | Provide specific examples and evidence for any AI-related findings.
120 |
121 | # Repository Details
122 | Repository: ${repoDetails.full_name}
123 | Description: ${repoDetails.description || 'N/A'}
124 | Language: ${repoDetails.language}
125 | Stars: ${repoDetails.stargazers_count}
126 |
127 | # Code Review
128 | ${files.map(file => {
129 | const ext = getFileExtension(file.path);
130 | const sanitizedContent = sanitizeCodeContent(file.content);
131 | return `
132 | File: ${file.path}
133 | \`\`\`${ext}
134 | ${sanitizedContent}
135 | \`\`\`
136 | `;
137 | }).join('\n')}
138 |
139 | # Technical Assessment
140 |
141 | ## AI Implementation Analysis
142 | - Identify any AI/ML components
143 | - Verify implementation correctness
144 | - Evaluate model integration
145 | - Assess data processing
146 | - Validate AI claims against code
147 |
148 | ## Logic Flow
149 | - Core application flow
150 | - Data processing patterns
151 | - Control flow architecture
152 | - Error handling paths
153 |
154 | ## Process Architecture
155 | - System components
156 | - Service interactions
157 | - Scalability approach
158 | - Integration patterns
159 |
160 | ## Code Organization Review
161 | - Module structure
162 | - Dependency patterns
163 | - Code reusability
164 | - Architecture patterns
165 |
166 | ## Critical Path Analysis
167 | - Performance bottlenecks
168 | - Security considerations
169 | - Scalability challenges
170 | - Technical debt
171 |
172 | Provide scores as "Score: X/25" format. Include specific code examples to support findings.`;
173 |
174 | const analysisResponse = await anthropic.messages.create({
175 | model: 'claude-3-5-sonnet-20241022',
176 | max_tokens: 4000,
177 | temperature: 0.3,
178 | messages: [{
179 | role: 'user',
180 | content: `You are a technical code reviewer. Analyze this repository and provide a detailed assessment. Start directly with the scores and analysis without any introductory text.\n\n${analysisPrompt}`
181 | }]
182 | });
183 |
184 | const analysis = analysisResponse.content[0].text;
185 | const scores = extractScores(analysis);
186 | const codeReview = extractCodeReview(analysis);
187 | const trustScore = calculateTrustScore(codeReview);
188 | const finalLegitimacyScore = calculateFinalLegitimacyScore(scores.legitimacyScore, trustScore);
189 |
190 | // Generate a more natural, informative summary
191 | const summaryPrompt = `Given this technical analysis, tell me what's most interesting and notable about this repository in 1-2 conversational sentences. Focus on unique features, technical achievements, or interesting implementation details. Be specific but natural in tone:
192 |
193 | ${analysis}
194 |
195 | Remember to highlight what makes this repo special or noteworthy from a technical perspective.`;
196 |
197 | const summaryResponse = await anthropic.messages.create({
198 | model: 'claude-3-5-sonnet-20241022',
199 | max_tokens: 300,
200 | temperature: 0.7, // Higher temperature for more creative/varied responses
201 | messages: [{
202 | role: 'user',
203 | content: summaryPrompt
204 | }]
205 | });
206 |
207 | const summary = summaryResponse.content[0].text.trim();
208 |
209 | // Initialize twitter agent if needed
210 | if (!twitterAgent) {
211 | twitterAgent = await initTwitterAgent();
212 | }
213 |
214 | // Save to history
215 | await saveAnalysis(repoDetails, {
216 | codeReview,
217 | fullAnalysis: analysis,
218 | trustScore,
219 | finalLegitimacyScore
220 | }, scores, summary);
221 |
222 | // Trigger tweet generation if it's an interesting finding
223 | if (finalLegitimacyScore > 75 ||
224 | codeReview.aiAnalysis.score > 80) {
225 | await twitterAgent.processRecentAnalyses();
226 | }
227 |
228 | // Return complete analysis object
229 | return {
230 | repoDetails: {
231 | ...repoDetails,
232 | description: summary
233 | },
234 | analysis: {
235 | detailedScores: scores.detailedScores,
236 | legitimacyScore: scores.legitimacyScore,
237 | trustScore,
238 | finalLegitimacyScore,
239 | codeReview,
240 | fullAnalysis: analysis,
241 | summary
242 | }
243 | };
244 | } catch (error) {
245 | console.error('Error analyzing repository:', error);
246 | throw new Error(`Failed to analyze repository: ${error.message}`);
247 | }
248 | }
249 |
250 | module.exports = { analyzeRepo };
--------------------------------------------------------------------------------
/services/claudeTools.js:
--------------------------------------------------------------------------------
1 | const Repository = require('../models/Repository');
2 | const mongoose = require('mongoose');
3 |
4 | // Validation helpers
5 | const validateInput = {
6 | searchRepos: (input) => {
7 | const { minLegitimacyScore, minAiScore, techStack, limit } = input;
8 |
9 | if (minLegitimacyScore !== undefined && (
10 | typeof minLegitimacyScore !== 'number' ||
11 | minLegitimacyScore < 0 ||
12 | minLegitimacyScore > 100
13 | )) {
14 | throw new Error('Invalid minLegitimacyScore: must be number between 0-100');
15 | }
16 |
17 | if (minAiScore !== undefined && (
18 | typeof minAiScore !== 'number' ||
19 | minAiScore < 0 ||
20 | minAiScore > 100
21 | )) {
22 | throw new Error('Invalid minAiScore: must be number between 0-100');
23 | }
24 |
25 | if (techStack !== undefined && (!Array.isArray(techStack) ||
26 | !techStack.every(tech => typeof tech === 'string'))
27 | ) {
28 | throw new Error('Invalid techStack: must be array of strings');
29 | }
30 |
31 | if (limit !== undefined && (
32 | typeof limit !== 'number' ||
33 | limit < 1 ||
34 | limit > 100
35 | )) {
36 | throw new Error('Invalid limit: must be number between 1-100');
37 | }
38 |
39 | return true;
40 | },
41 |
42 | getRepoDetails: (input) => {
43 | const { repoName } = input;
44 | if (!repoName || typeof repoName !== 'string' || !repoName.includes('/')) {
45 | throw new Error('Invalid repoName: must be string in format owner/repo');
46 | }
47 | return true;
48 | },
49 |
50 | findSimilarRepos: (input) => {
51 | const { repoName, limit } = input;
52 | if (!repoName || typeof repoName !== 'string' || !repoName.includes('/')) {
53 | throw new Error('Invalid repoName: must be string in format owner/repo');
54 | }
55 | if (limit !== undefined && (
56 | typeof limit !== 'number' ||
57 | limit < 1 ||
58 | limit > 100
59 | )) {
60 | throw new Error('Invalid limit: must be number between 1-100');
61 | }
62 | return true;
63 | }
64 | };
65 |
66 | // Tool implementations with validation
67 | async function searchRepos(input) {
68 | try {
69 | validateInput.searchRepos(input);
70 |
71 | const { minLegitimacyScore = 0, minAiScore = 0, techStack = [], limit = 10 } = input;
72 |
73 | const query = {
74 | 'analysis.finalLegitimacyScore': { $gte: minLegitimacyScore }
75 | };
76 |
77 | if (minAiScore > 0) {
78 | query['analysis.codeReview.aiAnalysis.score'] = { $gte: minAiScore };
79 | }
80 |
81 | if (techStack.length > 0) {
82 | query['analysis.codeReview.techStack'] = { $in: techStack };
83 | }
84 |
85 | const results = await Repository.find(query)
86 | .sort({ 'analysis.finalLegitimacyScore': -1 })
87 | .limit(limit)
88 | .lean();
89 |
90 | return results || [];
91 | } catch (error) {
92 | console.error('Error in searchRepos:', error);
93 | throw error;
94 | }
95 | }
96 |
97 | async function getRepoDetails(input) {
98 | try {
99 | validateInput.getRepoDetails(input);
100 |
101 | const result = await Repository.findOne({
102 | fullName: input.repoName
103 | }).lean();
104 |
105 | if (!result) {
106 | throw new Error(`Repository ${input.repoName} not found`);
107 | }
108 |
109 | return result;
110 | } catch (error) {
111 | console.error('Error in getRepoDetails:', error);
112 | throw error;
113 | }
114 | }
115 |
116 | async function findSimilarRepos(input) {
117 | try {
118 | validateInput.findSimilarRepos(input);
119 |
120 | const { repoName, limit = 5 } = input;
121 |
122 | const sourceRepo = await Repository.findOne({
123 | fullName: repoName
124 | }).lean();
125 |
126 | if (!sourceRepo) {
127 | throw new Error(`Source repository ${repoName} not found`);
128 | }
129 |
130 | const similarRepos = await Repository.find({
131 | fullName: { $ne: repoName },
132 | 'analysis.detailedScores.codeQuality': {
133 | $gte: sourceRepo.analysis.detailedScores.codeQuality - 5,
134 | $lte: sourceRepo.analysis.detailedScores.codeQuality + 5
135 | },
136 | 'analysis.codeReview.techStack': {
137 | $in: sourceRepo.analysis.codeReview.techStack || []
138 | }
139 | })
140 | .sort({ 'analysis.finalLegitimacyScore': -1 })
141 | .limit(limit)
142 | .lean();
143 |
144 | return similarRepos || [];
145 | } catch (error) {
146 | console.error('Error in findSimilarRepos:', error);
147 | throw error;
148 | }
149 | }
150 |
151 | // Tool definitions that we'll provide to Claude
152 | const CLAUDE_TOOLS = [
153 | {
154 | name: "search_repos",
155 | description: `Search through analyzed repositories to find interesting patterns or specific technical implementations.
156 | Use this when you want to:
157 | - Find repos with similar technical approaches
158 | - Compare implementations across different projects
159 | - Identify trending technologies or patterns
160 | - Discover high-scoring projects in specific areas
161 |
162 | The scores object contains:
163 | - codeQuality (0-25): Code architecture, patterns, and best practices
164 | - projectStructure (0-25): Directory organization, dependency management
165 | - implementation (0-25): Core functionality, integrations, efficiency
166 | - documentation (0-25): Code comments, API docs, setup instructions
167 |
168 | The legitimacyScore (0-100) indicates overall project validity
169 | The aiScore (0-100) represents AI/ML implementation quality`,
170 | input_schema: {
171 | type: "object",
172 | properties: {
173 | minLegitimacyScore: {
174 | type: "number",
175 | description: "Minimum legitimacy score (0-100)"
176 | },
177 | minAiScore: {
178 | type: "number",
179 | description: "Minimum AI implementation score (0-100)"
180 | },
181 | techStack: {
182 | type: "array",
183 | items: { type: "string" },
184 | description: "List of technologies to search for"
185 | },
186 | limit: {
187 | type: "number",
188 | description: "Maximum number of results to return"
189 | }
190 | }
191 | }
192 | },
193 | {
194 | name: "get_repo_details",
195 | description: `Get detailed analysis information about a specific repository.
196 | Use this when you want to:
197 | - Deep dive into a specific project's implementation
198 | - Verify technical claims or features
199 | - Get comprehensive scoring and review data
200 | - Access the full technical analysis
201 |
202 | Returns complete analysis including code review, scores, and technical assessment.`,
203 | input_schema: {
204 | type: "object",
205 | properties: {
206 | repoName: {
207 | type: "string",
208 | description: "Full repository name (owner/repo)"
209 | }
210 | },
211 | required: ["repoName"]
212 | }
213 | },
214 | {
215 | name: "find_similar_repos",
216 | description: `Find repositories with similar technical characteristics or implementations.
217 | Use this to:
218 | - Compare different approaches to similar problems
219 | - Identify alternative implementations
220 | - Find projects in the same technical domain
221 |
222 | Returns repositories that share technical similarities based on analysis.`,
223 | input_schema: {
224 | type: "object",
225 | properties: {
226 | repoName: {
227 | type: "string",
228 | description: "Source repository to find similar ones to"
229 | },
230 | limit: {
231 | type: "number",
232 | description: "Maximum number of similar repos to return"
233 | }
234 | },
235 | required: ["repoName"]
236 | }
237 | }
238 | ];
239 |
240 | module.exports = {
241 | CLAUDE_TOOLS,
242 | toolFunctions: {
243 | search_repos: searchRepos,
244 | get_repo_details: getRepoDetails,
245 | find_similar_repos: findSimilarRepos
246 | }
247 | };
--------------------------------------------------------------------------------
/utils/analysisUtils.js:
--------------------------------------------------------------------------------
1 | // Helper functions for analysis parsing and extraction
2 | function sanitizeCodeContent(content) {
3 | if (typeof content !== 'string') return '';
4 | return content
5 | .replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\uD800-\uDFFF]/g, '')
6 | .replace(/\r\n/g, '\n')
7 | .replace(/\r/g, '\n')
8 | .slice(0, 2000);
9 | }
10 |
11 | function getFileExtension(filename) {
12 | const ext = filename.split('.').pop().toLowerCase();
13 | const languageMap = {
14 | 'js': 'javascript',
15 | 'ts': 'typescript',
16 | 'py': 'python',
17 | 'java': 'java',
18 | 'go': 'go',
19 | 'rs': 'rust',
20 | 'cpp': 'cpp',
21 | 'c': 'c',
22 | 'jsx': 'javascript',
23 | 'tsx': 'typescript',
24 | 'vue': 'vue',
25 | 'php': 'php',
26 | 'rb': 'ruby',
27 | 'sol': 'solidity',
28 | 'cs': 'csharp',
29 | 'html': 'html',
30 | 'css': 'css',
31 | 'scss': 'scss',
32 | 'md': 'markdown',
33 | 'json': 'json',
34 | 'yml': 'yaml',
35 | 'yaml': 'yaml'
36 | };
37 | return languageMap[ext] || ext;
38 | }
39 |
40 | function extractScores(analysis) {
41 | // Updated regex to better match the prompt format
42 | const scorePattern = /(?:Code Quality|Project Structure|Implementation|Documentation)\s*\(Score:\s*(\d+)\/25\)/g;
43 | const scores = {};
44 | let totalScore = 0;
45 | let count = 0;
46 |
47 | // Extract category scores
48 | while ((match = scorePattern.exec(analysis)) !== null) {
49 | const score = parseInt(match[1], 10);
50 | if (!isNaN(score)) {
51 | const category = match[0].split('(')[0].trim().replace(/\s+/g, '').toLowerCase();
52 | scores[category] = score;
53 | totalScore += score;
54 | count++;
55 | }
56 | }
57 |
58 | // Calculate legitimacy score as percentage (0-100)
59 | const legitimacyScore = count > 0 ? Math.round((totalScore / (count * 25)) * 100) : 0;
60 |
61 | return {
62 | detailedScores: {
63 | codeQuality: scores.codequality || 0,
64 | projectStructure: scores.projectstructure || 0,
65 | implementation: scores.implementation || 0,
66 | documentation: scores.documentation || 0
67 | },
68 | legitimacyScore
69 | };
70 | }
71 |
72 | function extractCodeReview(analysis) {
73 | try {
74 | const codeReview = {
75 | logicFlow: [],
76 | processArchitecture: [],
77 | codeOrganization: [],
78 | criticalPath: [],
79 | misrepresentationChecks: [],
80 | larpIndicators: [],
81 | redFlags: [],
82 | overallAssessment: '',
83 | investmentRanking: {
84 | rating: '',
85 | confidence: 0,
86 | reasoning: []
87 | },
88 | aiAnalysis: {
89 | hasAI: false,
90 | components: [],
91 | score: 0,
92 | misleadingLevel: 'None',
93 | implementationQuality: 'N/A',
94 | concerns: [],
95 | details: ''
96 | }
97 | };
98 |
99 | const sections = {
100 | logicFlow: /## Logic Flow\n([\s\S]*?)(?=\n##|$)/,
101 | processArchitecture: /## Process Architecture\n([\s\S]*?)(?=\n##|$)/,
102 | codeOrganization: /## Code Organization Review\n([\s\S]*?)(?=\n##|$)/,
103 | criticalPath: /## Critical Path Analysis\n([\s\S]*?)(?=\n##|$)/,
104 | misrepresentationChecks: /## Misrepresentation Checks\n([\s\S]*?)(?=\n##|$)/,
105 | larpIndicators: /## LARP Indicators\n([\s\S]*?)(?=\n##|$)/,
106 | redFlags: /## Red Flags\n([\s\S]*?)(?=\n##|$)/,
107 | overallAssessment: /## Overall Assessment\n([\s\S]*?)(?=\n##|$)/,
108 | investmentRanking: /## Investment Ranking \(NFA\)\n([\s\S]*?)(?=\n##|$)/
109 | };
110 |
111 | for (const [key, pattern] of Object.entries(sections)) {
112 | const section = analysis.match(pattern);
113 | if (section) {
114 | if (key === 'overallAssessment') {
115 | codeReview[key] = section[1].trim();
116 | } else if (key === 'investmentRanking') {
117 | const investmentSection = section[1];
118 | const ratingMatch = investmentSection.match(/Rating: (.*?)(?:\n|$)/);
119 | const confidenceMatch = investmentSection.match(/Confidence: (\d+)%/);
120 | const reasoningLines = investmentSection
121 | .split('\n')
122 | .filter(line => line.trim().startsWith('-'))
123 | .map(line => line.trim().replace(/^- /, ''));
124 |
125 | codeReview[key] = {
126 | rating: ratingMatch ? ratingMatch[1].trim() : '',
127 | confidence: confidenceMatch ? parseInt(confidenceMatch[1]) : 0,
128 | reasoning: reasoningLines
129 | };
130 | } else {
131 | codeReview[key] = section[1]
132 | .split('\n')
133 | .filter(line => line.trim().startsWith('-'))
134 | .map(line => line.trim().replace(/^- /, ''));
135 | }
136 | }
137 | }
138 |
139 | // Add AI section extraction
140 | const aiSection = analysis.match(/## AI Implementation Analysis\n([\s\S]*?)(?=\n##|$)/);
141 | if (aiSection) {
142 | const aiText = aiSection[1];
143 |
144 | // Extract AI components
145 | const components = aiText
146 | .split('\n')
147 | .filter(line => line.trim().startsWith('-'))
148 | .map(line => line.trim().replace(/^- /, ''));
149 |
150 | // Extract AI score
151 | const scoreMatch = aiText.match(/AI Score: (\d+)/);
152 | const score = scoreMatch ? parseInt(scoreMatch[1]) : 0;
153 |
154 | // Extract misleading level
155 | const misleadingMatch = aiText.match(/Misleading Level: (None|Low|Medium|High)/);
156 | const misleadingLevel = misleadingMatch ? misleadingMatch[1] : 'None';
157 |
158 | // Extract implementation quality
159 | const qualityMatch = aiText.match(/Implementation Quality: (Poor|Basic|Good|Excellent)/);
160 | const implementationQuality = qualityMatch ? qualityMatch[1] : 'N/A';
161 |
162 | // Update AI analysis object
163 | codeReview.aiAnalysis = {
164 | hasAI: components.length > 0,
165 | components,
166 | score,
167 | misleadingLevel,
168 | implementationQuality,
169 | concerns: components.filter(c =>
170 | c.toLowerCase().includes('concern') ||
171 | c.toLowerCase().includes('issue') ||
172 | c.toLowerCase().includes('misleading')
173 | ),
174 | details: aiText.trim()
175 | };
176 | }
177 |
178 | return codeReview;
179 | } catch (error) {
180 | console.error('Error extracting code review:', error);
181 | return null;
182 | }
183 | }
184 |
185 | // Update calculateTrustScore to contribute to legitimacy score
186 | function calculateTrustScore(codeReview) {
187 | try {
188 | const redFlagsCount = codeReview.redFlags.length;
189 | const larpIndicatorsCount = codeReview.larpIndicators.length;
190 | const misrepresentationCount = codeReview.misrepresentationChecks.filter(check =>
191 | check.toLowerCase().includes('suspicious') ||
192 | check.toLowerCase().includes('concern')
193 | ).length;
194 |
195 | // Add AI-specific trust factors
196 | const aiMisleadingPenalty = {
197 | 'None': 0,
198 | 'Low': 10,
199 | 'Medium': 20,
200 | 'High': 30
201 | }[codeReview.aiAnalysis.misleadingLevel] || 0;
202 |
203 | const aiConcernsPenalty = codeReview.aiAnalysis.concerns.length * 5;
204 |
205 | // Start with 100 and deduct for issues
206 | let trustScore = 100;
207 | trustScore -= (redFlagsCount * 15); // -15 points per red flag
208 | trustScore -= (larpIndicatorsCount * 10); // -10 points per LARP indicator
209 | trustScore -= (misrepresentationCount * 20); // -20 points per misrepresentation
210 | trustScore -= aiMisleadingPenalty; // Penalty for misleading AI claims
211 | trustScore -= aiConcernsPenalty; // -5 points per AI concern
212 |
213 | // Ensure score stays between 0 and 100
214 | return Math.max(0, Math.min(100, trustScore));
215 | } catch (error) {
216 | console.error('Error calculating trust score:', error);
217 | return 0;
218 | }
219 | }
220 |
221 | // New function to combine scores into final legitimacy score
222 | function calculateFinalLegitimacyScore(legitimacyScore, trustScore) {
223 | // Weight both scores equally
224 | return Math.round((legitimacyScore + trustScore) / 2);
225 | }
226 |
227 | module.exports = {
228 | sanitizeCodeContent,
229 | getFileExtension,
230 | extractScores,
231 | extractCodeReview,
232 | calculateTrustScore,
233 | calculateFinalLegitimacyScore
234 | };
--------------------------------------------------------------------------------
/public/css/whitepaper.css:
--------------------------------------------------------------------------------
1 | .whitepaper-container {
2 | max-width: 1200px;
3 | margin: 40px auto;
4 | padding: 20px;
5 | display: grid;
6 | grid-template-columns: 250px 1fr;
7 | gap: 40px;
8 | }
9 |
10 | .toc {
11 | position: sticky;
12 | top: 100px;
13 | height: fit-content;
14 | background: var(--bg-secondary);
15 | padding: 20px;
16 | border-radius: 12px;
17 | border: 1px solid var(--border);
18 | }
19 |
20 | .toc h2 {
21 | color: var(--accent);
22 | margin-bottom: 20px;
23 | }
24 |
25 | .toc ul {
26 | list-style: none;
27 | padding: 0;
28 | }
29 |
30 | .toc li {
31 | margin: 10px 0;
32 | }
33 |
34 | .toc a {
35 | color: var(--text-secondary);
36 | text-decoration: none;
37 | transition: color 0.3s;
38 | }
39 |
40 | .toc a:hover {
41 | color: var(--accent);
42 | }
43 |
44 | .whitepaper-content {
45 | background: var(--bg-secondary);
46 | padding: 40px;
47 | border-radius: 12px;
48 | border: 1px solid var(--border);
49 | }
50 |
51 | .whitepaper-content h1 {
52 | color: var(--accent);
53 | font-size: 2.5em;
54 | margin-bottom: 30px;
55 | }
56 |
57 | .whitepaper-content h2 {
58 | color: var(--text-primary);
59 | font-size: 1.8em;
60 | margin: 40px 0 20px;
61 | }
62 |
63 | .whitepaper-content h3 {
64 | color: var(--text-primary);
65 | font-size: 1.4em;
66 | margin: 30px 0 15px;
67 | }
68 |
69 | .mermaid {
70 | background: var(--bg-primary);
71 | padding: 30px;
72 | border-radius: 12px;
73 | margin: 20px 0;
74 | overflow: auto;
75 | max-width: 100%;
76 | cursor: zoom-in;
77 | transition: transform 0.3s ease;
78 | }
79 |
80 | .mermaid:hover {
81 | transform: scale(1.02);
82 | }
83 |
84 | .mermaid-fullscreen {
85 | position: fixed;
86 | top: 0;
87 | left: 0;
88 | width: 100vw;
89 | height: 100vh;
90 | background: rgba(0, 0, 0, 0.9);
91 | z-index: 1000;
92 | display: flex;
93 | align-items: center;
94 | justify-content: center;
95 | padding: 40px;
96 | cursor: zoom-out;
97 | }
98 |
99 | .mermaid-fullscreen svg {
100 | max-width: 90%;
101 | max-height: 90%;
102 | }
103 |
104 | /* Mobile Responsiveness */
105 | @media (max-width: 768px) {
106 | .whitepaper-container {
107 | grid-template-columns: 1fr;
108 | }
109 |
110 | .toc {
111 | position: relative;
112 | top: 0;
113 | }
114 | }
115 |
116 | /* Dark theme adjustments */
117 | @media (prefers-color-scheme: dark) {
118 | .mermaid {
119 | background: rgba(0, 0, 0, 0.2);
120 | }
121 | }
122 |
123 | /* Add to existing styles */
124 | .whitepaper-header {
125 | margin-bottom: 40px;
126 | padding-bottom: 20px;
127 | border-bottom: 1px solid var(--border);
128 | }
129 |
130 | .version-info {
131 | display: flex;
132 | justify-content: space-between;
133 | align-items: center;
134 | margin-top: 20px;
135 | }
136 |
137 | .version {
138 | color: var(--accent);
139 | font-size: 1.2em;
140 | }
141 |
142 | .social-links {
143 | display: flex;
144 | gap: 15px;
145 | }
146 |
147 | .social-link svg {
148 | fill: var(--text-secondary);
149 | transition: fill 0.3s;
150 | }
151 |
152 | .social-link:hover svg {
153 | fill: var(--accent);
154 | }
155 |
156 | .summary-box {
157 | background: rgba(255, 215, 0, 0.05);
158 | border: 1px solid var(--accent);
159 | border-radius: 12px;
160 | padding: 25px;
161 | margin: 20px 0;
162 | }
163 |
164 | .key-metrics {
165 | display: grid;
166 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
167 | gap: 20px;
168 | margin-top: 25px;
169 | text-align: center;
170 | }
171 |
172 | .metric {
173 | padding: 15px;
174 | background: var(--bg-primary);
175 | border-radius: 8px;
176 | }
177 |
178 | .metric-value {
179 | display: block;
180 | font-size: 1.8em;
181 | color: var(--accent);
182 | font-weight: bold;
183 | }
184 |
185 | .metric-label {
186 | color: var(--text-secondary);
187 | font-size: 0.9em;
188 | }
189 |
190 | .capabilities-grid {
191 | display: grid;
192 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
193 | gap: 25px;
194 | margin-top: 30px;
195 | }
196 |
197 | .capability {
198 | background: var(--bg-primary);
199 | border: 1px solid var(--border);
200 | border-radius: 12px;
201 | padding: 20px;
202 | }
203 |
204 | .capability h4 {
205 | color: var(--accent);
206 | margin-bottom: 15px;
207 | }
208 |
209 | .capability ul {
210 | list-style: none;
211 | padding: 0;
212 | }
213 |
214 | .capability li {
215 | margin: 10px 0;
216 | padding-left: 20px;
217 | position: relative;
218 | }
219 |
220 | .capability li:before {
221 | content: "→";
222 | position: absolute;
223 | left: 0;
224 | color: var(--accent);
225 | }
226 |
227 | .tech-stack {
228 | margin-top: 40px;
229 | }
230 |
231 | .tech-grid {
232 | display: grid;
233 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
234 | gap: 20px;
235 | margin-top: 20px;
236 | }
237 |
238 | .tech-item {
239 | background: var(--bg-primary);
240 | border: 1px solid var(--border);
241 | border-radius: 8px;
242 | padding: 20px;
243 | }
244 |
245 | .tech-item h4 {
246 | color: var(--accent);
247 | margin-bottom: 15px;
248 | }
249 |
250 | @media (max-width: 768px) {
251 | .key-metrics,
252 | .capabilities-grid,
253 | .tech-grid {
254 | grid-template-columns: 1fr;
255 | }
256 |
257 | .version-info {
258 | flex-direction: column;
259 | gap: 15px;
260 | }
261 | }
262 |
263 | /* Add to existing styles */
264 | .methodology-box {
265 | background: var(--bg-primary);
266 | border: 1px solid var(--border);
267 | border-radius: 12px;
268 | padding: 25px;
269 | margin: 20px 0;
270 | }
271 |
272 | .risk-matrix {
273 | display: grid;
274 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
275 | gap: 20px;
276 | margin: 20px 0;
277 | }
278 |
279 | .risk-category {
280 | padding: 20px;
281 | border-radius: 8px;
282 | border: 1px solid var(--border);
283 | }
284 |
285 | .risk-category.high {
286 | background: rgba(239, 68, 68, 0.1);
287 | border-color: #ef4444;
288 | }
289 |
290 | .risk-category.medium {
291 | background: rgba(234, 179, 8, 0.1);
292 | border-color: #eab308;
293 | }
294 |
295 | .risk-category.low {
296 | background: rgba(34, 197, 94, 0.1);
297 | border-color: #22c55e;
298 | }
299 |
300 | .data-sources-grid {
301 | display: grid;
302 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
303 | gap: 20px;
304 | margin: 20px 0;
305 | }
306 |
307 | .source-card {
308 | background: var(--bg-primary);
309 | border: 1px solid var(--border);
310 | border-radius: 8px;
311 | padding: 20px;
312 | }
313 |
314 | .social-platforms {
315 | display: grid;
316 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
317 | gap: 20px;
318 | margin: 20px 0;
319 | }
320 |
321 | .platform-card {
322 | background: var(--bg-primary);
323 | border: 1px solid var(--border);
324 | border-radius: 8px;
325 | padding: 20px;
326 | }
327 |
328 | .components-grid {
329 | display: grid;
330 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
331 | gap: 20px;
332 | margin: 20px 0;
333 | }
334 |
335 | .component-card {
336 | background: var(--bg-primary);
337 | border: 1px solid var(--border);
338 | border-radius: 8px;
339 | padding: 20px;
340 | }
341 |
342 | .whitepaper-section {
343 | margin-bottom: 60px;
344 | }
345 |
346 | .whitepaper-section h3 {
347 | color: var(--accent);
348 | margin: 30px 0 20px;
349 | }
350 |
351 | .whitepaper-section h4 {
352 | color: var(--text-primary);
353 | margin: 20px 0 10px;
354 | }
355 |
356 | .whitepaper-section ul {
357 | list-style: none;
358 | padding-left: 20px;
359 | }
360 |
361 | .whitepaper-section ul li {
362 | position: relative;
363 | margin: 10px 0;
364 | }
365 |
366 | .whitepaper-section ul li:before {
367 | content: "→";
368 | position: absolute;
369 | left: -20px;
370 | color: var(--accent);
371 | }
372 |
373 | @media (max-width: 768px) {
374 | .risk-matrix,
375 | .data-sources-grid,
376 | .social-platforms,
377 | .components-grid {
378 | grid-template-columns: 1fr;
379 | }
380 | }
381 |
382 | /* Add these new styles */
383 | .architecture-grid {
384 | display: grid;
385 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
386 | gap: 30px;
387 | margin: 30px 0;
388 | }
389 |
390 | .arch-component {
391 | background: var(--bg-primary);
392 | border: 1px solid var(--border);
393 | border-radius: 12px;
394 | padding: 20px;
395 | }
396 |
397 | .arch-component h4 {
398 | color: var(--accent);
399 | margin-bottom: 15px;
400 | text-align: center;
401 | }
402 |
403 | .arch-component .mermaid {
404 | min-height: 200px;
405 | display: flex;
406 | align-items: center;
407 | justify-content: center;
408 | }
409 |
410 | .key-features {
411 | display: grid;
412 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
413 | gap: 25px;
414 | margin-top: 30px;
415 | }
416 |
417 | .feature {
418 | background: var(--bg-primary);
419 | border: 1px solid var(--border);
420 | border-radius: 12px;
421 | padding: 20px;
422 | }
423 |
424 | .feature h4 {
425 | color: var(--accent);
426 | margin-bottom: 10px;
427 | display: flex;
428 | align-items: center;
429 | gap: 10px;
430 | }
431 |
432 | .feature p {
433 | color: var(--text-secondary);
434 | font-size: 0.9em;
435 | line-height: 1.6;
436 | }
--------------------------------------------------------------------------------
/services/insightsService.js:
--------------------------------------------------------------------------------
1 | const Repository = require('../models/Repository');
2 | const { Octokit } = require('@octokit/rest');
3 | const vectorStore = require('./vectorStoreService');
4 | const { Anthropic } = require('@anthropic-ai/sdk');
5 |
6 | class InsightsService {
7 | constructor() {
8 | this.octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
9 | this.anthropic = new Anthropic();
10 | }
11 |
12 | async generateInsights(repoFullName) {
13 | try {
14 | const [owner, repo] = repoFullName.split('/');
15 |
16 | // Parallel fetch all data we need
17 | const [
18 | repoData,
19 | commits,
20 | issues,
21 | pullRequests,
22 | contributors,
23 | languages
24 | ] = await Promise.all([
25 | this.octokit.repos.get({ owner, repo }),
26 | this.octokit.repos.listCommits({ owner, repo, per_page: 100 }),
27 | this.octokit.issues.listForRepo({ owner, repo, state: 'all', per_page: 100 }),
28 | this.octokit.pulls.list({ owner, repo, state: 'all', per_page: 100 }),
29 | this.octokit.repos.listContributors({ owner, repo, per_page: 100 }),
30 | this.octokit.repos.listLanguages({ owner, repo })
31 | ]);
32 |
33 | // Calculate activity metrics
34 | const lastMonth = new Date();
35 | lastMonth.setMonth(lastMonth.getMonth() - 1);
36 |
37 | const recentCommits = commits.data.filter(commit =>
38 | new Date(commit.commit.author.date) > lastMonth
39 | );
40 |
41 | const recentIssues = issues.data.filter(issue =>
42 | new Date(issue.created_at) > lastMonth
43 | );
44 |
45 | const recentPRs = pullRequests.data.filter(pr =>
46 | new Date(pr.created_at) > lastMonth
47 | );
48 |
49 | // Enhanced insights object
50 | const insights = {
51 | basic: {
52 | stars: repoData.data.stargazers_count,
53 | forks: repoData.data.forks_count,
54 | watchers: repoData.data.subscribers_count,
55 | size: repoData.data.size,
56 | defaultBranch: repoData.data.default_branch,
57 | license: repoData.data.license?.name || 'No license',
58 | },
59 | activity: {
60 | totalCommits: commits.data.length,
61 | recentCommits: recentCommits.length,
62 | totalIssues: issues.data.length,
63 | openIssues: repoData.data.open_issues_count,
64 | recentIssues: recentIssues.length,
65 | totalPRs: pullRequests.data.length,
66 | recentPRs: recentPRs.length,
67 | lastUpdated: repoData.data.updated_at,
68 | contributors: contributors.data.length,
69 | },
70 | technical: {
71 | codeQuality: this.calculateCodeQuality(repoData, commits),
72 | architectureScore: this.evaluateArchitecture(repoData),
73 | testCoverage: await this.analyzeTestCoverage(owner, repo),
74 | dependencies: await this.analyzeDependencies(owner, repo),
75 | securityScore: await this.calculateSecurityScore(owner, repo)
76 | },
77 | market: {
78 | trendingScore: this.calculateTrendingScore({
79 | repoData: repoData.data,
80 | recentCommits: recentCommits.length,
81 | recentIssues: recentIssues.length,
82 | recentPRs: recentPRs.length
83 | }),
84 | competitorAnalysis: await this.analyzeCompetitors(repoFullName),
85 | communityHealth: this.calculateCommunityHealth({
86 | issues: issues.data,
87 | pullRequests: pullRequests.data,
88 | contributors: contributors.data,
89 | recentActivity: recentCommits.length
90 | }),
91 | growthMetrics: this.calculateGrowthMetrics(commits.data, repoData.data.stargazers_count)
92 | },
93 | aiAnalysis: await this.generateEnhancedAIAnalysis(insights, repoFullName)
94 | };
95 |
96 | // Store in vector database
97 | await vectorStore.storeRepoInsights(repoFullName, insights);
98 |
99 | // Find similar repositories
100 | const similarRepos = await vectorStore.findSimilarRepos(repoFullName);
101 | insights.similarRepos = similarRepos;
102 |
103 | // Store complete insights in MongoDB
104 | await Repository.findOneAndUpdate(
105 | { fullName: repoFullName },
106 | {
107 | $set: {
108 | insights,
109 | lastInsightUpdate: new Date()
110 | }
111 | },
112 | { upsert: true }
113 | );
114 |
115 | return insights;
116 | } catch (error) {
117 | console.error('Error generating insights:', error);
118 | throw error;
119 | }
120 | }
121 |
122 | async generateAIAnalysis(insights, repoFullName) {
123 | const prompt = `Analyze this GitHub repository (${repoFullName}) data and provide detailed insights:
124 |
125 | 1. What makes this repository stand out?
126 | 2. What are its strengths and potential areas for improvement?
127 | 3. What patterns or best practices are evident?
128 | 4. What recommendations would you make?
129 | 5. Are there any security or performance concerns?
130 |
131 | Repository Data: ${JSON.stringify(insights, null, 2)}`;
132 |
133 | const response = await this.anthropic.messages.create({
134 | model: "claude-3-sonnet-20240229",
135 | max_tokens: 2048,
136 | messages: [{
137 | role: "user",
138 | content: prompt
139 | }]
140 | });
141 |
142 | return {
143 | summary: response.content,
144 | timestamp: new Date()
145 | };
146 | }
147 |
148 | async generateTrendsReport() {
149 | // Get patterns analysis from vector store
150 | const patterns = await vectorStore.analyzePatterns();
151 |
152 | // Generate comprehensive report
153 | const reportPrompt = `Based on these patterns in GitHub repositories, generate a detailed trend report: ${patterns}`;
154 |
155 | const response = await this.anthropic.messages.create({
156 | model: "claude-3-sonnet-20240229",
157 | max_tokens: 3072,
158 | messages: [{
159 | role: "user",
160 | content: reportPrompt
161 | }]
162 | });
163 |
164 | return {
165 | trends: response.content,
166 | patterns: patterns,
167 | timestamp: new Date()
168 | };
169 | }
170 |
171 | calculateTrendingScore({ repoData, recentCommits, recentIssues, recentPRs }) {
172 | const now = new Date();
173 | const lastUpdate = new Date(repoData.updated_at);
174 | const daysSinceUpdate = (now - lastUpdate) / (1000 * 60 * 60 * 24);
175 |
176 | // Weight recent activity more heavily
177 | const activityScore = (
178 | recentCommits * 3 +
179 | recentIssues * 2 +
180 | recentPRs * 2
181 | );
182 |
183 | // Base score from stars and forks
184 | const baseScore = (
185 | repoData.stargazers_count * 2 +
186 | repoData.forks_count * 3
187 | );
188 |
189 | // Combine scores with time decay
190 | return Math.round((baseScore + activityScore) / (daysSinceUpdate + 1));
191 | }
192 |
193 | calculateHealthScore({ hasDescription, hasLicense, hasReadme, issuesEnabled, wikiEnabled }) {
194 | let score = 0;
195 | const total = 5; // Total number of checks
196 |
197 | if (hasDescription) score++;
198 | if (hasLicense) score++;
199 | if (hasReadme) score++;
200 | if (issuesEnabled) score++;
201 | if (wikiEnabled) score++;
202 |
203 | return (score / total) * 100;
204 | }
205 |
206 | calculatePRMergeRate(prs) {
207 | const merged = prs.filter(pr => pr.merged_at).length;
208 | return prs.length > 0 ? (merged / prs.length) * 100 : 0;
209 | }
210 |
211 | calculateAvgIssueTime(issues) {
212 | const closedIssues = issues.filter(issue => issue.closed_at);
213 | if (closedIssues.length === 0) return 0;
214 |
215 | const totalTime = closedIssues.reduce((sum, issue) => {
216 | const created = new Date(issue.created_at);
217 | const closed = new Date(issue.closed_at);
218 | return sum + (closed - created);
219 | }, 0);
220 |
221 | return Math.round(totalTime / closedIssues.length / (1000 * 60 * 60 * 24)); // Convert to days
222 | }
223 |
224 | async generateEnhancedAIAnalysis(insights, repoFullName) {
225 | const prompt = `Analyze this GitHub repository (${repoFullName}) and provide detailed insights in the following categories:
226 |
227 | 1. Technical Implementation
228 | 2. Market Potential
229 | 3. Community & Growth
230 | 4. Security & Quality
231 | 5. Competitive Analysis
232 | 6. Investment Signals
233 |
234 | Repository Data: ${JSON.stringify(insights, null, 2)}`;
235 |
236 | const response = await this.anthropic.messages.create({
237 | model: "claude-3-sonnet-20240229",
238 | max_tokens: 3072,
239 | messages: [{
240 | role: "user",
241 | content: prompt
242 | }]
243 | });
244 |
245 | return {
246 | summary: response.content,
247 | categories: this.parseAIResponse(response.content),
248 | timestamp: new Date()
249 | };
250 | }
251 |
252 | calculateCommunityHealth({ issues, pullRequests, contributors, recentActivity }) {
253 | const metrics = {
254 | issueResolutionRate: this.calculateIssueResolutionRate(issues),
255 | prAcceptanceRate: this.calculatePRAcceptanceRate(pullRequests),
256 | contributorGrowth: this.calculateContributorGrowth(contributors),
257 | activityLevel: this.normalizeActivityScore(recentActivity)
258 | };
259 |
260 | return {
261 | score: this.aggregateHealthMetrics(metrics),
262 | details: metrics
263 | };
264 | }
265 | }
266 |
267 | module.exports = new InsightsService();
--------------------------------------------------------------------------------