mya-cli
Version:
MYA - AI-Powered Stock & Options Analysis CLI Tool
383 lines • 13.8 kB
JavaScript
/**
* MYA API Backend - Multi-User Architecture with RabbitMQ + Cloudflare KV
* Main entry point for the backend API
*
* This module provides the core functionality for the MYA trading intelligence platform,
* including user authentication, session management, and analysis request processing.
*
* Last updated: July 7, 2025
*/
import { HybridUserContextManager } from './utils/hybrid-user-context.js';
/**
* Global services for multi-user support
*/
let hybridContextManager;
/**
* Initialize application services
*/
export async function initializeServices(config) {
try {
// Initialize hybrid context manager
hybridContextManager = HybridUserContextManager.getInstance({
apiUrl: config.apiUrl,
kvNamespace: config.kvNamespace,
sessionTtl: 30 * 24 * 60 * 60, // 30 days for persistent login
queueTtl: 300, // 5 minutes
maxRetries: 3,
env: config.env, // Pass environment variables to context manager
});
await hybridContextManager.initialize();
console.log('✅ All services initialized successfully');
}
catch (error) {
console.error('❌ Failed to initialize services:', error);
throw error;
}
}
/**
* Process authentication request
* Initiates the authentication flow by sending an OTP to the user's email
*
* @param email - User's email address
* @param _userAgent - User agent string (unused, kept for API compatibility)
* @param _ipAddress - IP address (unused, kept for API compatibility)
* @param env - Optional environment object for Worker contexts
* @returns Authentication result with method ID for OTP verification
*/
export async function processAuth(email, _userAgent, _ipAddress, env) {
try {
// Import the Stytch provider components
const { StytchAuthProvider, StytchConfigFactory } = await import('./auth/stytch-auth-provider.js');
let envProvider;
if (env) {
// Use Worker environment provider for Cloudflare Workers
const { WorkerEnvironmentProvider } = await import('./storage/worker-env-provider.js');
envProvider = new WorkerEnvironmentProvider(env);
}
else {
// Use file environment provider for CLI/local development
const { FileEnvironmentProvider } = await import('./storage/env-provider.js');
envProvider = new FileEnvironmentProvider();
}
// Create Stytch configuration
const configFactory = new StytchConfigFactory(envProvider);
const config = configFactory.createConfig();
// Create auth provider and send OTP
const authProvider = new StytchAuthProvider(config);
const otpResponse = await authProvider.sendOtpCode(email);
return {
success: true,
method_id: otpResponse.method_id,
methodId: otpResponse.method_id,
user_id: otpResponse.user_id,
};
}
catch (error) {
console.error('Auth error:', error);
return {
success: false,
error: 'Authentication failed',
details: error instanceof Error ? error.message : String(error),
};
}
}
/**
* Verify OTP and create session
* Completes the authentication flow by verifying the OTP code and creating a user session
*
* @param methodId - Method ID from the authentication process
* @param code - OTP code entered by the user
* @param email - User's email address
* @param _userAgent - User agent string (unused, kept for API compatibility)
* @param _ipAddress - IP address (unused, kept for API compatibility)
* @returns Session creation result with user and session details
*/
export async function verifyOtpAndCreateSession(methodId, code, email, _userAgent, _ipAddress, env) {
try {
console.log('DEBUG: Starting OTP verification with methodId:', methodId, 'code:', code);
// Ensure hybrid context manager is initialized
if (!hybridContextManager) {
// Get the KV namespace from the environment
const envWithKV = env;
const globalWithKV = globalThis;
const kvNamespace = envWithKV?.KV_NAMESPACE || globalWithKV.env?.KV_NAMESPACE || globalWithKV.KV_NAMESPACE;
// Initialize with default config for verification
hybridContextManager = HybridUserContextManager.getInstance({
apiUrl: 'http://localhost:8787',
kvNamespace: kvNamespace || {}, // Fallback if KV not available
sessionTtl: 30 * 24 * 60 * 60, // 30 days
queueTtl: 300, // 5 minutes
maxRetries: 3,
});
await hybridContextManager.initialize();
}
// Import the Stytch provider components
const { StytchAuthProvider, StytchConfigFactory } = await import('./auth/stytch-auth-provider.js');
const { WorkerEnvironmentProvider } = await import('./storage/worker-env-provider.js');
// Create Stytch configuration using Worker environment
const workerEnv = env;
const envProvider = new WorkerEnvironmentProvider(workerEnv);
const configFactory = new StytchConfigFactory(envProvider);
const config = configFactory.createConfig();
// Create auth provider and verify OTP
const authProvider = new StytchAuthProvider(config);
const verifyResponse = await authProvider.verifyOtpCode(methodId, code);
if (!verifyResponse.session_token) {
return {
success: false,
error: 'Invalid OTP code',
};
}
// Generate machine fingerprint
const machineId = await hybridContextManager.generateMachineFingerprint({
userAgent: _userAgent,
platform: 'cli',
architecture: process.arch,
hostname: 'unknown',
ipAddress: _ipAddress,
});
// Create session
const sessionData = {
userId: verifyResponse.user_id,
email,
machineId,
sessionToken: verifyResponse.session_token,
expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000, // 30 days
permissions: ['analyze', 'options', 'volatility', 'earnings', 'announcements', 'benchmark'],
metadata: {
userAgent: _userAgent,
ipAddress: _ipAddress,
deviceName: 'CLI',
osInfo: process.platform,
},
};
const session = await hybridContextManager.createSession(sessionData);
// Store session in KV for CLI access
const envWithKV = env;
if (envWithKV?.KV_NAMESPACE) {
const sessionKey = `cli_session:${verifyResponse.user_id}:${machineId}`;
await envWithKV.KV_NAMESPACE.put(sessionKey, JSON.stringify({
...sessionData,
sessionJwt: verifyResponse.session_jwt,
}), {
expirationTtl: 30 * 24 * 60 * 60, // 30 days
});
}
return {
success: true,
userId: verifyResponse.user_id,
machineId,
sessionToken: verifyResponse.session_token,
sessionJwt: verifyResponse.session_jwt,
expiresAt: session.expiresAt,
};
}
catch (error) {
console.error('Verify OTP error:', error);
// More detailed error logging
if (error instanceof Error) {
console.error('Error message:', error.message);
console.error('Error stack:', error.stack);
}
return {
success: false,
error: error instanceof Error ? error.message : 'Verification failed',
};
}
}
/**
* Validate session
*/
export async function validateSession(userId, machineId, sessionToken) {
try {
const isValid = await hybridContextManager.setUserContext(userId, machineId, sessionToken);
if (!isValid) {
return { valid: false, error: 'Session invalid or expired' };
}
const session = hybridContextManager.getCurrentContext();
return { valid: true, session };
}
catch (error) {
console.error('Session validation error:', error);
return { valid: false, error: 'Session validation failed' };
}
}
/**
* Submit analysis request to queue
* Queues an analysis request for processing based on the specified type
*
* @param userId - Unique user identifier
* @param machineId - Unique machine identifier
* @param analysisType - Type of analysis (double, analyze, earnings, announcements)
* @param parameters - Additional parameters for the analysis
* @returns Submission result with request ID for tracking
*/
export async function submitAnalysisRequest(userId, machineId, analysisType, parameters) {
try {
// Set user context
await hybridContextManager.setUserContext(userId, machineId, '');
const result = await hybridContextManager.submitAnalysisRequest(analysisType, {
...parameters,
priority: 1,
waitForResult: false,
});
return {
success: true,
requestId: result.requestId,
status: result.status,
};
}
catch (error) {
console.error('Submit analysis request error:', error);
return {
success: false,
error: 'Failed to submit request',
};
}
}
/**
* Get analysis result
* Retrieves the result of a previously submitted analysis request
*
* @param requestId - Unique request identifier
* @param userId - Unique user identifier
* @returns Analysis result or status information
*/
export async function getAnalysisResult(requestId, userId) {
try {
// For now, return a placeholder since we need to implement result retrieval
return {
requestId,
userId,
status: 'processing',
message: 'Analysis in progress',
};
}
catch (error) {
console.error('Get analysis result error:', error);
return {
success: false,
error: 'Failed to get result',
};
}
}
/**
* Process analysis request (for worker/processor)
* Internal function to process queued analysis requests
*
* @param request - Analysis request object from the queue
* @returns Processing result
*/
export async function processAnalysisRequest(request) {
try {
return await hybridContextManager.processAnalysisRequest(request);
}
catch (error) {
console.error('Process analysis request error:', error);
throw error;
}
}
/**
* Start processing analysis requests (for worker processes)
* Initiates the background processing of queued analysis requests
*
* @returns Promise that resolves when processing starts
*/
export async function startProcessingRequests() {
try {
await hybridContextManager.startProcessingRequests();
}
catch (error) {
console.error('Failed to start processing requests:', error);
throw error;
}
}
/**
* Wait for analysis result
* Polls for an analysis result until completion or timeout
*
* @param requestId - Unique request identifier
* @param userId - Unique user identifier
* @param timeoutMs - Timeout in milliseconds (default: 180 seconds for cache warming + analysis)
* @returns Analysis result when ready
*/
export async function waitForAnalysisResult(requestId, userId, timeoutMs = 180000) {
try {
return await hybridContextManager.waitForResult(requestId, timeoutMs);
}
catch (error) {
console.error('Wait for analysis result error:', error);
return null;
}
}
/**
* Logout user
*/
export async function logoutUser(userId, machineId) {
try {
await hybridContextManager.invalidateSession(userId, machineId);
return { success: true };
}
catch (error) {
console.error('Logout error:', error);
return { success: false, error: 'Logout failed' };
}
}
/**
* Get user status
*/
export async function getUserStatus(userId, machineId) {
try {
const isValid = await hybridContextManager.setUserContext(userId, machineId, '');
if (!isValid) {
return { authenticated: false };
}
const context = hybridContextManager.getCurrentContext();
if (!context) {
return { authenticated: false };
}
return {
authenticated: true,
userId: context.userId,
email: context.email,
machineId: context.machineId,
expiresAt: context.expiresAt,
permissions: context.permissions,
};
}
catch (error) {
console.error('Get user status error:', error);
return { authenticated: false, error: 'Failed to get status' };
}
}
/**
* Get service statistics
*/
export async function getServiceStats() {
try {
const queueStats = await hybridContextManager.getQueueStats();
const sessionStats = await hybridContextManager.getSessionStats();
return {
queue: queueStats,
sessions: sessionStats,
};
}
catch (error) {
console.error('Get service stats error:', error);
return { error: 'Failed to get stats' };
}
}
/**
* Cleanup resources
*/
export async function cleanup() {
try {
await hybridContextManager.cleanup();
}
catch (error) {
console.error('Cleanup error:', error);
}
}
// Export the hybrid context manager for use in different environments
export { hybridContextManager };
//# sourceMappingURL=index.js.map