@neurolint/cli
Version:
NeuroLint CLI for React/Next.js modernization with advanced 6-layer orchestration and intelligent AST transformations
260 lines (221 loc) • 6.5 kB
JavaScript
const { createClient } = require('@supabase/supabase-js');
const fs = require('fs-extra');
const path = require('path');
const os = require('os');
require('dotenv').config();
// Use production Supabase URL from the main project
const supabase = createClient(
process.env.SUPABASE_URL || 'https://app.neurolint.dev',
process.env.SUPABASE_ANON_KEY || 'your-anon-key'
);
class AuthManager {
constructor() {
this.user = null;
this.configPath = path.join(os.homedir(), '.neurolint', 'config.json');
this.loadStoredAuth();
}
async loadStoredAuth() {
try {
if (await fs.pathExists(this.configPath)) {
const config = await fs.readJson(this.configPath);
if (config.session) {
const { data, error } = await supabase.auth.setSession(config.session);
if (!error && data.user) {
this.user = data.user;
}
}
}
} catch (error) {
// Ignore auth loading errors - user will need to login
}
}
async saveSession(session) {
try {
await fs.ensureDir(path.dirname(this.configPath));
await fs.writeJson(this.configPath, { session }, { spaces: 2 });
} catch (error) {
console.warn('Failed to save session:', error.message);
}
}
async login(email, password) {
try {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password
});
if (error) throw error;
this.user = data.user;
// Save session for persistent login
if (data.session) {
await this.saveSession(data.session);
}
// Get or create user profile
await this.ensureUserProfile();
return { success: true, user: data.user };
} catch (error) {
return { success: false, error: error.message };
}
}
async signup(email, password) {
try {
const { data, error } = await supabase.auth.signUp({
email,
password
});
if (error) throw error;
return {
success: true,
user: data.user,
needsConfirmation: !data.session
};
} catch (error) {
return { success: false, error: error.message };
}
}
async logout() {
try {
await supabase.auth.signOut();
this.user = null;
// Clear stored session
if (await fs.pathExists(this.configPath)) {
await fs.remove(this.configPath);
}
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
}
async ensureUserProfile() {
if (!this.user) return;
try {
// Check if profile exists
const { data: existingProfile } = await supabase
.from('user_profiles')
.select('*')
.eq('id', this.user.id)
.single();
if (!existingProfile) {
// Create profile with free tier defaults
const { error } = await supabase
.from('user_profiles')
.insert({
id: this.user.id,
email: this.user.email,
tier: 'free',
usage: {
remainingFixes: 5,
usedFixes: 0,
monthlyResetDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
}
});
if (error) {
console.warn('Failed to create user profile:', error.message);
}
}
} catch (error) {
console.warn('Failed to ensure user profile:', error.message);
}
}
isAuthenticated() {
return !!this.user;
}
getUserId() {
return this.user?.id;
}
getUserEmail() {
return this.user?.email;
}
async checkUsageLimit(action) {
if (!this.user) {
return { canUse: false, reason: 'Authentication required' };
}
try {
const { data: profile, error } = await supabase
.from('user_profiles')
.select('tier, usage')
.eq('id', this.user.id)
.single();
if (error) throw error;
const tier = profile.tier || 'free';
const usage = profile.usage || { remainingFixes: -1 };
// Tier limits - aligned with pricing structure
const limits = {
free: {
analyze: -1, // unlimited analyze
fix: -1, // unlimited fixes for layers 1-2 only
layers: [1, 2] // restricted to layers 1-2
},
basic: {
analyze: -1,
fix: -1, // unlimited fixes for layers 1-4
layers: [1, 2, 3, 4]
},
professional: {
analyze: -1,
fix: -1, // unlimited all layers
layers: [1, 2, 3, 4, 5, 6]
},
business: {
analyze: -1,
fix: -1,
layers: [1, 2, 3, 4, 5, 6]
},
enterprise: {
analyze: -1,
fix: -1,
layers: [1, 2, 3, 4, 5, 6]
},
premium: {
analyze: -1,
fix: -1,
layers: [1, 2, 3, 4, 5, 6]
}
};
const tierLimits = limits[tier] || limits.free;
const actionLimit = tierLimits[action];
if (actionLimit === -1) {
return {
canUse: true,
allowedLayers: tierLimits.layers
}; // unlimited within allowed layers
}
return {
canUse: false,
reason: `${tier} tier limit reached. Upgrade at https://neurolint.dev/pricing`
};
} catch (error) {
console.error('Usage check failed:', error);
return { canUse: false, reason: 'Unable to verify usage limits' };
}
}
async recordUsage(action, metadata = {}) {
if (!this.user) return;
try {
// Record usage log
const { error: logError } = await supabase
.from('usage_logs')
.insert({
user_id: this.user.id,
action,
metadata,
timestamp: new Date().toISOString()
});
if (logError) {
console.warn('Failed to log usage:', logError.message);
}
// Decrement remaining fixes for fix actions
if (action === 'fix') {
const { error: updateError } = await supabase.rpc('decrement_user_fixes', {
p_user_id: this.user.id,
p_count: metadata.filesFixed || 1
});
if (updateError) {
console.warn('Failed to update usage limits:', updateError.message);
}
}
} catch (error) {
console.warn('Failed to record usage:', error.message);
}
}
}
module.exports = { AuthManager, supabase };