├── 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 |
2 |
3 | 9 | 10 | 23 |
24 |
-------------------------------------------------------------------------------- /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 |
15 |
16 | 20 | 30 |
31 |
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 | 70 | 71 | 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 |
2 |
3 | 7 |
8 | CA: 9 | 5M5cPVHs9K1FoNGNgp58dAFzGvU5G5fEADZHXTHwpump 10 | 15 |
16 | 42 |
43 |
-------------------------------------------------------------------------------- /public/whitepaper.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AI0x | Technical Whitepaper 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 21 | 44 |
45 |
46 | 47 |
48 |
49 |

Contents

50 | 51 |
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 |
2 |
3 | 7 | 8 | 9 | 15 | 16 | 51 |
52 |
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 |
130 |
131 |

X (Twitter)

132 | - Automated market updates 133 | - Technical alerts 134 | - Trend analysis 135 |
136 |
137 |

Discord

138 | - Community insights 139 | - Real-time notifications 140 | - Technical discussions 141 |
142 |
143 |

Telegram

144 | - Market alerts 145 | - Project updates 146 | - Community engagement 147 |
148 |
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 |
7 |
8 | 12 | 13 |
14 | 15 | ${window.innerWidth <= 768 ? FULL_CA.substring(0, 6) : FULL_CA.substring(0, 6) + '...' + FULL_CA.slice(-4)} 16 | 17 | 18 | 19 | 20 |
21 | 22 | 53 |
54 |
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 | 99 | 100 | 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 | AI0x Logo 3 |

AI0x

4 |

Market Intelligence Platform for Code Analysis

5 |

CA: 5M5cPVHs9K1FoNGNgp58dAFzGvU5G5fEADZHXTHwpump

6 |

7 | 8 | Follow AI0x on Twitter 9 | 10 | 11 | Join our Discord 12 | 13 | 14 | Join our Telegram 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 | 53 | 63 | 73 | 83 | 84 |
44 | 45 | #### Code Quality 46 | - Organization 47 | - Error Handling 48 | - Performance 49 | - Best Practices 50 | - Security 51 | 52 | 54 | 55 | #### Project Structure 56 | - Architecture 57 | - Dependencies 58 | - Configuration 59 | - Build System 60 | - Resources 61 | 62 | 64 | 65 | #### Implementation 66 | - Core Features 67 | - API Design 68 | - Data Flow 69 | - Security 70 | - Efficiency 71 | 72 | 74 | 75 | #### Documentation 76 | - Code Comments 77 | - API Reference 78 | - Setup Guide 79 | - Architecture 80 | - Examples 81 | 82 |
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 | 137 | 147 | 156 | 166 | 167 |
128 | 129 | #### Frontend 130 | - HTML5/CSS3 131 | - JavaScript 132 | - WebSocket 133 | - Mermaid.js 134 | - Marked.js 135 | 136 | 138 | 139 | #### Backend 140 | - Node.js/Express 141 | - Socket.IO 142 | - MongoDB 143 | - Redis 144 | - Bull 145 | 146 | 148 | 149 | #### Analysis 150 | - Claude 3 Sonnet 151 | - Custom Algorithms 152 | - Pattern Detection 153 | - Risk Assessment 154 | 155 | 157 | 158 | #### Integration 159 | - GitHub API 160 | - Discord.js 161 | - Telegraf 162 | - Twitter API 163 | - WebSocket 164 | 165 |
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 | 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 |
65 |
66 | 67 | 68 | 69 |

${data.repoFullName}

70 |
71 |
72 | ⭐ ${data.stars || 0} 73 | 🔄 ${data.forks || 0} 74 | ${data.language || 'Unknown'} 75 |
76 |
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(); --------------------------------------------------------------------------------