UNPKG

@basicsu/courses-mcp

Version:

Interactive programming courses from Basics - MCP server for Cursor

818 lines (766 loc) • 43.4 kB
// Phase Zero Tools - Interactive programming courses import { z } from 'zod'; import { getLocalState, saveLocalState, clearLocalState } from '../state.js'; import { createInstructorResponse } from '../instructor-v2.js'; import { getUserProfile, getUserEnrollments, getPublishedCourses, getCourseByIdentifier, getCourseModules, getCourseLessons, enrollUserInCourse, updateUserProgress, getLessonSteps, getStepById, markStepComplete, getUserStepProgress, getNextStep, getUserPreferences } from '../database-helper.js'; import { calculateHierarchicalProgress } from '../hierarchical-progress.js'; import { getDeviceId, saveDeviceCredentials, getDeviceEmail, getDeviceUserProfileId, clearDeviceCredentials } from '../device-credentials.js'; import { loadConfig, hasValidAuth, clearAuth } from '../config.js'; export const startBasics = { name: 'startBasics', description: 'Start or continue learning with Basics. If you are not registered, you will be prompted to register first. Otherwise, it will start at your dashboard or pick up where you last left off. ALWAYS ask the user for their email address if they are not registered. DO NOT assume their credentials, they must confirm their email.', inputSchema: z.object({ email: z.string().email().optional().describe('Your email address for authentication'), courseId: z.string().optional().describe('Specific course ID to start (optional)') }), handler: async ({ email, courseId }, context) => { try { // Check if the user is already authenticated const deviceId = await getDeviceId(); // If already authenticated and no email provided, show current status if (deviceId !== null && !email) { const currentEmail = await getDeviceEmail(); if (currentEmail) { // If they provided a courseId, continue with current auth if (courseId) { // Continue below to start the course } else { return `# Already Logged In You're currently logged in as: **${currentEmail}** To switch accounts: 1. Use \`logoutBasics\` to logout 2. Then use \`startBasics({ email: "newemail@example.com" })\` to login with a different account To check current status: \`getBasicsCourseStatus\` To continue learning: \`nextBasicsCourseStep\``; } } } // If they provided an email and are already logged in as someone else if (deviceId !== null && email) { const currentEmail = await getDeviceEmail(); if (currentEmail && currentEmail !== email) { return `# āš ļø Already Logged In as Different User You're currently logged in as: **${currentEmail}** But trying to login as: **${email}** Please logout first: \`logoutBasics\` Then login with the new account: \`startBasics({ email: "${email}" })\``; } } if (deviceId === null) { // User is not authenticated if (!email) { return `# šŸ” Authentication Required To get started with Basics, I'll need your email address. If you don't have an account yet, you can register at [basicsu.com/auth/register](https://basicsu.com/auth/register). Could you please provide your email address? Example: \`startBasics({ email: "you@example.com" })\``; } // Get user profile directly without auth code verification const userProfile = await getUserProfile(email); if (!userProfile) { return `# āŒ User Not Found No account found for ${email}. Please register at [basicsu.com/auth/register](https://basicsu.com/auth/register)`; } // Save device credentials await saveDeviceCredentials(userProfile.id, email); } // Get user email from device credentials if not provided const userEmail = email || await getDeviceEmail(); if (!userEmail) { return 'Authentication error. Please use the `startBasics` tool with your email and auth code.'; } // Get user's course enrollments with progress const userCourses = await getUserEnrollments(userEmail); // Get all published courses const availableCourses = await getPublishedCourses(); // If courseId provided, start that specific course if (courseId) { console.error(`[Course Start] Starting course with ID: ${courseId}`); const targetCourse = await getCourseByIdentifier(courseId); console.error(`[Course Start] Found course: ${targetCourse ? targetCourse.title : 'NOT FOUND'}`); if (!targetCourse) { return `# āŒ Course Not Found The course "${courseId}" was not found. **Available courses:** ${availableCourses.map(c => `- ${c.title} (${c.slug || c.id})`).join('\n')} Try again with a valid course ID or slug.`; } // Check if user is enrolled const isEnrolled = userCourses?.some((uc) => uc.course_id === targetCourse.id); const userProfile = await getUserProfile(userEmail); if (!isEnrolled && userProfile) { // Enroll the user await enrollUserInCourse(userProfile.id, targetCourse.id); } // Get the course modules and lessons console.error(`[Course Start] Fetching modules for course ${targetCourse.id}`); const modules = await getCourseModules(targetCourse.id); console.error(`[Course Start] Found ${modules ? modules.length : 0} modules`); if (!modules || modules.length === 0) { console.error(`[Course Start] āŒ ERROR: No modules found for course`); return `# āŒ Course Content Not Ready The course "${targetCourse.title}" doesn't have any modules yet. Please try another course or check back later.`; } const firstModule = modules[0]; console.error(`[Course Start] First module: ${firstModule.title} (${firstModule.id})`); const lessons = await getCourseLessons(firstModule.id); console.error(`[Course Start] Found ${lessons ? lessons.length : 0} lessons in first module`); if (!lessons || lessons.length === 0) { console.error(`[Course Start] āŒ ERROR: No lessons found in first module`); return `# āŒ Course Content Not Ready The course "${targetCourse.title}" doesn't have any lessons yet. Please try another course or check back later.`; } const firstLesson = lessons[0]; console.error(`[Course Start] First lesson: ${firstLesson.title} (${firstLesson.id})`); // Set up local state for course navigation const courseState = { currentCourse: targetCourse.id, currentModule: firstModule.id, currentLesson: firstLesson.id, currentStep: 0, currentStepId: undefined, // Will be set when they go to first step userEmail: userEmail }; console.error(`[Course Start] Saving initial course state`); await saveLocalState(courseState); console.error(`[Course Start] Course started successfully: ${targetCourse.title}`); // Get user preferences for personalized explanations const userPreferences = userProfile ? await getUserPreferences(userProfile.id) : null; // Build the RAW lesson content (exactly as created by course author) const rawLessonContent = `# šŸŽ‰ ${targetCourse.title} - Started! ## ${firstModule.title} ### ${firstLesson.title} ${firstLesson.content || 'Welcome to this course!'} **Course Details:** - Duration: ${targetCourse.estimated_duration || 'Self-paced'} - Difficulty: ${targetCourse.difficulty_level} - Tags: ${targetCourse.tags?.join(', ') || 'None'} **Your Progress Dashboard:** 🌐 [View full course](https://basicsu.com/courses/${targetCourse.slug || targetCourse.id})`; return createInstructorResponse(rawLessonContent, { lessonName: firstLesson.title, stepName: 'Starting', type: 'course_start', customSummary: 'Course Navigation is now active! The lesson content above is your starting material. Use `nextBasicsCourseStep` to proceed to the next step.', userPreferences }); } // No courseId provided - show dashboard or ask which course let response = `# šŸŽÆ Your Basics Learning Dashboard Welcome back, ${userEmail}! **🌐 [View Your Full Dashboard](https://basicsu.com/dashboard)** `; // Show enrolled courses if (userCourses && userCourses.length > 0) { response += `## šŸ“š Your Courses\n\n`; for (const enrollment of userCourses) { const progressPercent = Math.round(enrollment.progress * 100); const statusEmoji = progressPercent >= 100 ? 'āœ…' : progressPercent > 0 ? 'šŸ”„' : '⭐'; response += `### ${statusEmoji} ${enrollment.title} - Progress: ${progressPercent}% complete - Difficulty: ${enrollment.difficulty_level} - Duration: ${enrollment.estimated_duration || 'Self-paced'} - **Continue**: \`startBasics({ courseId: "${enrollment.slug || enrollment.course_id}" })\` `; } } else { response += `## šŸ“š No Courses Yet You haven't enrolled in any courses yet. Check out our available courses below or visit [basicsu.com/courses](https://basicsu.com/courses) to browse all options. `; } // If user has exactly one course, offer to start it if (userCourses && userCourses.length === 1 && !courseId) { const onlyCourse = userCourses[0]; response += `\n## šŸ’” Quick Start You have one course in progress. Would you like to continue? - **Continue "${onlyCourse.title}"**: \`startBasics({ courseId: "${onlyCourse.slug || onlyCourse.course_id}" })\``; } response += `\n## šŸ› ļø Commands - Start specific course: \`startBasics({ courseId: "course-name" })\` - Continue current lesson: \`nextBasicsCourseStep\` - Check progress: \`getBasicsCourseStatus\` - Reset progress: \`clearBasicsCourseHistory({ confirm: true })\``; return response; } catch (error) { return `Error: ${error.message}`; } } }; export const getBasicsCourseStatus = { name: 'getBasicsCourseStatus', description: 'Show detailed course progress and web URL', inputSchema: z.object({}), handler: async (params, context) => { try { // Check if the user is authenticated const deviceId = await getDeviceId(); if (deviceId === null) { return 'You need to authenticate first. Please use the `startBasics` tool to authenticate.'; } // Get user email and profile ID from device credentials const email = await getDeviceEmail(); const userProfileId = await getDeviceUserProfileId(); if (!email || !userProfileId) { return 'Authentication error. Please use the `startBasics` tool to re-authenticate.'; } // Use the stored profile ID const userProfile = { id: userProfileId }; // Get user's course enrollments with progress const userCourses = await getUserEnrollments(email); if (!userCourses || userCourses.length === 0) { return `# šŸ“Š Course Status No courses started yet! Use \`startBasics\` to explore available courses. 🌐 **[View Dashboard](https://basicsu.com/dashboard)**`; } // Get current state const state = await getLocalState(); let response = `# šŸ“Š Course Progress Report **Student**: ${email} `; // Show detailed progress for each course for (const enrollment of userCourses) { const progressPercent = Math.round(enrollment.progress * 100); const isCurrent = state.currentCourse === enrollment.course_id; response += `\n## ${isCurrent ? 'ā–¶ļø' : 'šŸ“š'} ${enrollment.title} - **Progress**: ${progressPercent}% complete ${progressPercent >= 100 ? 'āœ…' : ''} - **Started**: ${new Date(enrollment.started_at).toLocaleDateString()} - **Last accessed**: ${new Date(enrollment.last_accessed_at).toLocaleDateString()} - **Difficulty**: ${enrollment.difficulty_level} - **Web URL**: [View Course](https://basicsu.com/courses/${enrollment.slug || enrollment.course_id}) `; if (isCurrent && state.currentLesson && state.currentModule) { try { // Get hierarchical progress const hierarchicalProgress = await calculateHierarchicalProgress(userProfile.id, enrollment.course_id, state.currentModule, state.currentLesson, state.currentStepId); response += `\n**Currently on**: ${hierarchicalProgress.currentLesson.title} - **Current Step**: ${hierarchicalProgress.currentStep.index + 1} of ${hierarchicalProgress.currentStep.totalSteps} - **Lesson Progress**: ${hierarchicalProgress.lessonProgress.currentLessonPercentage}% of "${hierarchicalProgress.currentLesson.title}" - **Module Progress**: ${hierarchicalProgress.moduleProgress.currentModulePercentage}% of "${hierarchicalProgress.currentModule.title}" (${hierarchicalProgress.currentModule.index + 1} of ${hierarchicalProgress.currentModule.totalModules}) - **Course Progress**: ${hierarchicalProgress.courseProgress.percentage}% overall **Detailed Progress**: - **Steps**: ${hierarchicalProgress.stepProgress.completed} of ${hierarchicalProgress.stepProgress.total} completed - **Lessons**: ${hierarchicalProgress.lessonProgress.completed} of ${hierarchicalProgress.lessonProgress.total} completed - **Modules**: ${hierarchicalProgress.moduleProgress.completed} of ${hierarchicalProgress.moduleProgress.total} completed - Continue: \`nextBasicsCourseStep\` - Jump to different lesson: \`startBasicsCourseLesson({ lessonNumber: X })\` `; } catch (error) { // Error calculating hierarchical progress response += `\n**Currently on**: ${state.currentLesson} - Continue: \`nextBasicsCourseStep\` `; } } } response += `\n## šŸ› ļø Available Commands - Continue learning: \`nextBasicsCourseStep\` - Start a specific course: \`startBasics({ courseId: "course-name" })\` - Reset all progress: \`clearBasicsCourseHistory({ confirm: true })\` 🌐 **[Full Dashboard](https://basicsu.com/dashboard)**`; return response; } catch (error) { return `Error getting status: ${error.message}`; } } }; export const nextBasicsCourseStep = { name: 'nextBasicsCourseStep', description: 'Move to the next step in the course', inputSchema: z.object({}), handler: async (params, context) => { const startTime = Date.now(); console.error(`[SECURITY] Tool invoked: nextBasicsCourseStep at ${new Date().toISOString()}`); try { // Check if the user is authenticated const deviceId = await getDeviceId(); if (deviceId === null) { console.error(`[AUTH] āŒ No device ID found - user not authenticated`); return 'You need to authenticate first. Please use the `startBasics` tool to authenticate.'; } // Get user email and profile ID from device credentials const email = await getDeviceEmail(); const userProfileId = await getDeviceUserProfileId(); console.error(`[AUTH] Authenticated as: ${email ? email.substring(0, 3) + '***' : 'UNKNOWN'}`); console.error(`[AUTH] Profile ID: ${userProfileId ? userProfileId.substring(0, 8) + '***' : 'UNKNOWN'}`); if (!email || !userProfileId) { console.error(`[AUTH] āŒ Critical: Missing email or profile ID after authentication`); console.error(`[AUTH] Email: ${email || 'NULL'}, Profile ID: ${userProfileId || 'NULL'}`); return `It appears that there is a persistent issue with your user profile not being found, even after re-authenticating with your email. This may be a problem with the course platform's authentication or session handling. Here are a few steps you can try: 1. Make sure you are logged in to the platform at basicsu.com with your email address. 2. If you are already logged in, try logging out and logging back in. 3. Then use \`startBasics({ email: "your-email@example.com" })\` to re-authenticate. If the issue persists, please contact support at support@basicsu.com.`; } const state = await getLocalState(); if (!state.currentCourse || !state.currentModule || !state.currentLesson) { return "No course in progress. Use `startBasics` to begin a course first!"; } // Use the stored profile ID instead of fetching the profile const userProfile = { id: userProfileId }; // Get user preferences for personalized explanations const userPreferences = await getUserPreferences(userProfileId); // Get all modules and lessons for the course to calculate total progress const allModules = await getCourseModules(state.currentCourse); let totalLessons = 0; let completedLessons = 0; let currentModuleIndex = -1; // Calculate total course progress for (let i = 0; i < allModules.length; i++) { const module = allModules[i]; const moduleLessons = await getCourseLessons(module.id); totalLessons += moduleLessons.length; if (module.id === state.currentModule) { currentModuleIndex = i; // Count completed lessons in current module const currentLessonIndex = moduleLessons.findIndex(l => l.id === state.currentLesson); completedLessons += currentLessonIndex; } else if (i < currentModuleIndex) { // All lessons in previous modules are completed completedLessons += moduleLessons.length; } } // Get current lesson content const lessons = await getCourseLessons(state.currentModule); const currentLessonIndex = lessons.findIndex(l => l.id === state.currentLesson); const currentLesson = lessons[currentLessonIndex]; if (!currentLesson) { return 'Error: Current lesson not found. Please restart the course.'; } // Get lesson steps from database console.error(`[Course Content] Fetching steps for lesson: ${state.currentLesson}`); const lessonSteps = await getLessonSteps(state.currentLesson); console.error(`[Course Content] Found ${lessonSteps.length} steps in lesson`); if (lessonSteps.length === 0) { console.error(`[Course Content] āŒ ERROR: No steps found for lesson ${state.currentLesson}`); return 'Error: No steps found for this lesson. Please contact support.'; } // Get the next step let currentStep = null; let nextStep = null; if (state.currentStepId) { // We have a current step, get the next one console.error(`[Course Content] Current step ID: ${state.currentStepId}`); currentStep = await getStepById(state.currentStepId); console.error(`[Course Content] Getting next step after current...`); nextStep = await getNextStep(userProfile.id, state.currentLesson, state.currentStepId); console.error(`[Course Content] Next step: ${nextStep ? nextStep.id : 'none (end of lesson)'}`); } else { // First time in this lesson, get the first step console.error(`[Course Content] First time in lesson, getting first step`); nextStep = lessonSteps[0]; console.error(`[Course Content] First step ID: ${nextStep.id}`); } // Check if we need to move to next lesson if (!nextStep) { // Mark the last step of current lesson as complete if (state.currentStepId) { console.error(`[Progress Tracking] End of lesson - marking final step complete`); console.error(`[Progress Tracking] User: ${email}, Step: ${state.currentStepId}`); const markResult = await markStepComplete(userProfile.id, state.currentStepId); if (markResult) { console.error(`[Progress Tracking] āœ… Final step marked complete`); } else { console.error(`[Progress Tracking] āŒ Failed to mark final step complete`); } } // Check if we're at the last lesson of the last module const isLastModule = currentModuleIndex === allModules.length - 1; const isLastLessonInModule = currentLessonIndex === lessons.length - 1; if (isLastModule && isLastLessonInModule) { // Course completed! await updateUserProgress(userProfile.id, state.currentCourse, 1.0); const course = await getCourseByIdentifier(state.currentCourse); // Get other enrolled courses const allEnrollments = await getUserEnrollments(email); const otherCourses = allEnrollments.filter(e => e.course_id !== state.currentCourse && e.progress < 1); const completedCourses = allEnrollments.filter(e => e.course_id !== state.currentCourse && e.progress >= 1); // Get available courses for discovery const allCourses = await getPublishedCourses(); const unenrolledCourses = allCourses.filter(c => !allEnrollments.some(e => e.course_id === c.id)).slice(0, 3); // Show top 3 suggestions let nextStepsContent = `# šŸŽ‰ Congratulations! Course Completed! You've successfully completed **${course?.title || 'this course'}**! **Your Achievement:** - āœ… All ${totalLessons} lessons completed - āœ… 100% progress achieved - āœ… Certificate available at [basicsu.com/certificates](https://basicsu.com/certificates) `; // Show other enrolled courses first if (otherCourses.length > 0) { nextStepsContent += `## šŸ“š Continue Your Learning Journey **Your Other Active Courses:** `; for (const enrollment of otherCourses) { const progress = Math.round(enrollment.progress * 100); nextStepsContent += `- **${enrollment.title}** (${progress}% complete) \`startBasics({ courseId: "${enrollment.slug || enrollment.course_id}" })\` `; } } // Show course discovery if (unenrolledCourses.length > 0) { nextStepsContent += ` ## šŸš€ Discover New Courses `; for (const course of unenrolledCourses) { nextStepsContent += `- **${course.title}** - ${course.description?.substring(0, 60)}... \`startBasics({ courseId: "${course.slug || course.id}" })\` `; } } nextStepsContent += ` ## šŸ› ļø Commands - View full dashboard: \`startBasics\` - Check all progress: \`getBasicsCourseStatus\` Keep learning and growing! šŸš€`; // Clear the local state since course is completed await clearLocalState(); return nextStepsContent; } // Move to next lesson or module if (currentLessonIndex < lessons.length - 1) { // Next lesson in current module const nextLesson = lessons[currentLessonIndex + 1]; console.error(`[Course Content] Moving to next lesson: ${nextLesson.title} (${nextLesson.id})`); state.currentLesson = nextLesson.id; state.currentStep = 0; state.currentStepId = undefined; // Reset step ID for new lesson await saveLocalState(state); // Update user progress based on total course progress const newProgress = (completedLessons + currentLessonIndex + 1) / totalLessons; await updateUserProgress(userProfile.id, state.currentCourse, newProgress); // Check if approaching end of course const lessonsRemaining = totalLessons - (completedLessons + currentLessonIndex + 1); let progressNote = `**Progress**: ${Math.round(newProgress * 100)}% of course completed`; if (lessonsRemaining <= 2) { progressNote += ` (${lessonsRemaining} lesson${lessonsRemaining === 1 ? '' : 's'} remaining!)`; } const rawLessonContent = `# ✨ New Lesson: ${nextLesson.title} ${nextLesson.content || 'Welcome to this lesson!'} ${progressNote}`; console.error(`[Course Content] New lesson loaded: ${nextLesson.title}`); console.error(`[Course Content] Lesson content length: ${nextLesson.content ? nextLesson.content.length : 0} characters`); console.error(`[Course Content] Returning lesson transition response`); return createInstructorResponse(rawLessonContent, { lessonName: nextLesson.title, stepName: 'Starting', type: 'lesson_complete', customSummary: 'You\'ve moved to a new lesson! The content above is your new lesson material. Use `nextBasicsCourseStep` to continue learning.', userPreferences }); } else if (currentModuleIndex < allModules.length - 1) { // Move to next module const nextModule = allModules[currentModuleIndex + 1]; const nextModuleLessons = await getCourseLessons(nextModule.id); if (nextModuleLessons.length > 0) { const nextLesson = nextModuleLessons[0]; state.currentModule = nextModule.id; state.currentLesson = nextLesson.id; state.currentStep = 0; state.currentStepId = undefined; // Reset step ID for new lesson await saveLocalState(state); // Update progress const newProgress = (completedLessons + lessons.length) / totalLessons; await updateUserProgress(userProfile.id, state.currentCourse, newProgress); const rawLessonContent = `# šŸŽÆ New Module: ${nextModule.title} ${nextModule.description || ''} ## ${nextLesson.title} ${nextLesson.content || 'Welcome to this lesson!'} **Progress**: ${Math.round(newProgress * 100)}% of course completed **Module**: ${currentModuleIndex + 2} of ${allModules.length}`; console.error(`[Course Content] New module loaded: ${nextModule.title} (${nextModule.id})`); console.error(`[Course Content] First lesson of module: ${nextLesson.title}`); console.error(`[Course Content] Module ${currentModuleIndex + 2}/${allModules.length}`); return createInstructorResponse(rawLessonContent, { lessonName: nextLesson.title, stepName: 'Starting', type: 'module_start', customSummary: 'You\'ve started a new module! The content above introduces the new material. Use `nextBasicsCourseStep` to continue learning.', userPreferences }); } } } // Mark the previous step as complete when moving to next step if (state.currentStepId && currentStep) { console.error(`[Progress Tracking] Marking step complete for user: ${email}`); console.error(`[Progress Tracking] Profile ID: ${userProfile.id}`); console.error(`[Progress Tracking] Step ID: ${state.currentStepId}`); const markResult = await markStepComplete(userProfile.id, state.currentStepId); if (markResult) { console.error(`[Progress Tracking] āœ… Step marked complete successfully`); // Get updated progress const progressData = await getUserStepProgress(userProfile.id, state.currentCourse); console.error(`[Progress Tracking] Course Progress: ${progressData.completedSteps}/${progressData.totalSteps} steps (${Math.round(progressData.progress * 100)}%)`); } else { console.error(`[Progress Tracking] āŒ Failed to mark step complete`); } } // We have a next step! Update state and display it console.error(`[Course Content] Loading step content for: ${nextStep.id}`); console.error(`[Course Content] Step title: ${nextStep.title}`); console.error(`[Course Content] Step type: ${nextStep.step_type}`); state.currentStepId = nextStep.id; state.currentStep = lessonSteps.findIndex(s => s.id === nextStep.id); // Keep backward compatibility await saveLocalState(state); console.error(`[Course Content] State saved, step index: ${state.currentStep + 1}/${lessonSteps.length}`); // Get hierarchical progress for this course console.error(`[Course Content] Calculating hierarchical progress...`); const hierarchicalProgress = await calculateHierarchicalProgress(userProfile.id, state.currentCourse, state.currentModule, state.currentLesson, nextStep.id); console.error(`[Course Content] Course progress: ${hierarchicalProgress.courseProgress.percentage}%`); // Find step number within lesson const stepIndex = lessonSteps.findIndex(s => s.id === nextStep.id); const stepNumber = stepIndex + 1; // Build the RAW lesson content (exactly as created by course author) const rawLessonContent = `# ${currentLesson.title} ## ${nextStep.title} ${nextStep.content}`; console.error(`[Course Content] Step content length: ${nextStep.content ? nextStep.content.length : 0} characters`); if (!nextStep.content || nextStep.content.trim() === '') { console.error(`[Course Content] āš ļø WARNING: Step has no content!`); } // For quiz steps, provide context about previous steps const instructorContext = { lessonName: currentLesson.title, stepName: `Step ${stepNumber}`, type: 'step_navigation', stepType: nextStep.step_type, progress: { stepNumber, totalSteps: lessonSteps.length, lessonProgress: `${hierarchicalProgress.lessonProgress.currentLessonPercentage}% of "${hierarchicalProgress.currentLesson.title}" (${hierarchicalProgress.lessonProgress.completed}/${hierarchicalProgress.lessonProgress.total} lessons completed)`, moduleProgress: `${hierarchicalProgress.moduleProgress.currentModulePercentage}% of "${hierarchicalProgress.currentModule.title}" (${hierarchicalProgress.moduleProgress.completed}/${hierarchicalProgress.moduleProgress.total} modules completed)`, courseProgress: `${hierarchicalProgress.courseProgress.percentage}% overall` }, userPreferences }; // If it's a quiz step, add previous steps context if (nextStep.step_type === 'quiz') { instructorContext.quizStepIds = nextStep.quiz_step_ids || []; instructorContext.previousSteps = lessonSteps.slice(0, stepIndex); } // Use smart instructor wrapper that preserves content integrity console.error(`[Course Content] Creating instructor response for step ${stepNumber}/${lessonSteps.length}`); const response = createInstructorResponse(rawLessonContent, instructorContext); console.error(`[Course Content] Response created, length: ${response.length} characters`); const endTime = Date.now(); console.error(`[PERFORMANCE] nextBasicsCourseStep completed in ${endTime - startTime}ms`); console.error(`[SECURITY] Operation completed successfully at ${new Date().toISOString()}`); return response; } catch (error) { console.error(`[ERROR] nextBasicsCourseStep failed: ${error.message}`); console.error(`[ERROR] Stack trace: ${error.stack}`); console.error(`[SECURITY] Failed operation logged at ${new Date().toISOString()}`); return `Error moving to next step: ${error.message}`; } } }; export const startBasicsCourseLesson = { name: 'startBasicsCourseLesson', description: 'Jump to a specific lesson', inputSchema: z.object({ lessonNumber: z.number() }), handler: async ({ lessonNumber }, context) => { try { // Check if the user is authenticated const deviceId = await getDeviceId(); if (deviceId === null) { return 'You need to authenticate first. Please use the `startBasics` tool to authenticate.'; } // Get user email from device credentials const email = await getDeviceEmail(); if (!email) { return 'Authentication error. Please use the `startBasics` tool to re-authenticate.'; } const state = await getLocalState(); if (!state.currentCourse || !state.currentModule) { return "No course in progress. Use `startBasics` to begin a course first!"; } // Get all lessons for current module const lessons = await getCourseLessons(state.currentModule); if (!lessons || lessons.length === 0) { return "No lessons found in current module."; } // Validate lesson number if (lessonNumber < 1 || lessonNumber > lessons.length) { return `Invalid lesson number. Please choose between 1 and ${lessons.length}. Available lessons: ${lessons.map((l, i) => `${i + 1}. ${l.title}`).join('\n')}`; } // Jump to the specified lesson const targetLesson = lessons[lessonNumber - 1]; state.currentLesson = targetLesson.id; state.currentStep = 0; await saveLocalState(state); // Get user profile ID for preferences const userProfileId = await getDeviceUserProfileId(); // Get user preferences for personalized explanations const userPreferences = userProfileId ? await getUserPreferences(userProfileId) : null; // Get course for context const course = await getCourseByIdentifier(state.currentCourse); const rawLessonContent = `# Jumped to Lesson ${lessonNumber}: ${targetLesson.title} ${targetLesson.content || 'Welcome to this lesson!'} **Course**: ${course?.title || 'Current Course'} **Lesson**: ${lessonNumber} of ${lessons.length}`; return createInstructorResponse(rawLessonContent, { lessonName: targetLesson.title, stepName: 'Starting', type: 'lesson_jump', customSummary: 'You\'ve jumped to a specific lesson. The content above is your lesson material. Use `nextBasicsCourseStep` to continue learning.', userPreferences }); } catch (error) { return `Error jumping to lesson: ${error.message}`; } } }; export const completeCurrentStep = { name: 'completeCurrentStep', description: 'Mark the current step as complete (useful for the last step in a course)', inputSchema: z.object({}), handler: async (params, context) => { try { const deviceId = await getDeviceId(); if (!deviceId) { return 'You need to authenticate first. Please use the `startBasics` tool to authenticate.'; } const email = await getDeviceEmail(); const userProfileId = await getDeviceUserProfileId(); if (!email || !userProfileId) { return 'Authentication error. Please use the `startBasics` tool to re-authenticate.'; } const userProfile = { id: userProfileId }; const state = await getLocalState(); if (!state.currentCourse || !state.currentStepId) { return 'No active step to complete. Use `startBasics` to begin a course.'; } // Mark current step as complete await markStepComplete(userProfile.id, state.currentStepId); // Check if this was the last step in the course const courseModules = await getCourseModules(state.currentCourse); if (courseModules && courseModules.length > 0) { const lastModule = courseModules[courseModules.length - 1]; const lastModuleLessons = await getCourseLessons(lastModule.id); if (lastModuleLessons && lastModuleLessons.length > 0) { const lastLesson = lastModuleLessons[lastModuleLessons.length - 1]; const lastLessonSteps = await getLessonSteps(lastLesson.id); if (lastLessonSteps && lastLessonSteps.length > 0) { const lastStep = lastLessonSteps[lastLessonSteps.length - 1]; if (state.currentStepId === lastStep.id) { // This was the last step - mark course as complete await updateUserProgress(userProfile.id, state.currentCourse, 1.0); return `# šŸŽ‰ Course Completed! Congratulations! You've completed the entire course! **Next Steps:** - View your certificate: [basicsu.com/certificates](https://basicsu.com/certificates) - Browse more courses: \`startBasics\` - Check your progress: \`getBasicsCourseStatus\``; } } } } return `āœ… Step completed! Use \`nextBasicsCourseStep\` to continue.`; } catch (error) { return `Error: ${error.message}`; } } }; export const clearBasicsCourseHistory = { name: 'clearBasicsCourseHistory', description: 'Reset all course progress', inputSchema: z.object({ confirm: z.boolean() }), handler: async ({ confirm }, context) => { try { // Check if the user is authenticated const deviceId = await getDeviceId(); if (deviceId === null) { return 'You need to authenticate first. Please use the `startBasics` tool to authenticate.'; } // Get user email from device credentials const email = await getDeviceEmail(); if (!email) { return 'Authentication error. Please use the `startBasics` tool to re-authenticate.'; } if (!confirm) { return "Are you sure? This will reset all progress. Use clearBasicsCourseHistory({ confirm: true }) to proceed."; } await clearLocalState(); // Note: We're not clearing database progress, just local navigation state // To fully reset, we would need to update all enrollments to 0% progress return "✨ Course navigation has been reset. Use `startBasics` to begin fresh!"; } catch (error) { return `Error clearing history: ${error.message}`; } } }; export const logoutBasics = { name: 'logoutBasics', description: 'Logout from Basics and clear stored credentials', inputSchema: z.object({}), handler: async (params, context) => { try { // Get the current email before logging out for the message const currentEmail = await getDeviceEmail(); // Clear the authentication clearAuth(); // Clear device credentials await clearDeviceCredentials(); // Also clear any local state await clearLocalState(); const message = currentEmail ? `āœ… Successfully logged out from Basics (${currentEmail}). Use \`startBasics\` to login with a different account.` : "āœ… Successfully logged out from Basics. Use `startBasics` to login with a different account."; return message; } catch (error) { return `Error logging out: ${error.message}`; } } }; export const getBasicsAuthStatus = { name: 'getBasicsAuthStatus', description: 'Check current authentication status and logged-in user', inputSchema: z.object({}), handler: async (params, context) => { try { if (!hasValidAuth()) { return "Not authenticated. Use `startBasics` to login."; } const config = loadConfig(); return `Authenticated as: ${config.userEmail}`; } catch (error) { return `Error checking auth status: ${error.message}`; } } }; // Export all tools as an array for easy registration export const phaseZeroTools = [ startBasics, getBasicsCourseStatus, nextBasicsCourseStep, startBasicsCourseLesson, completeCurrentStep, clearBasicsCourseHistory, logoutBasics, getBasicsAuthStatus ]; //# sourceMappingURL=phase-zero-tools.js.map