@@ -5,6 +5,9 @@ import { Command } from "commander";
55import fs from "fs" ;
66import path from "path" ;
77import { fileURLToPath } from "url" ;
8+ import inquirer from "inquirer" ;
9+ import qrcode from "qrcode" ;
10+ import clipboardy from "clipboardy" ;
811
912const __filename = fileURLToPath ( import . meta. url ) ;
1013const __dirname = path . dirname ( __filename ) ;
@@ -20,7 +23,7 @@ const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.jso
2023 */
2124
2225// Resume data structure
23- const resumeData = {
26+ export const resumeData = {
2427 personal : {
2528 name : "Bharathkumar Palanisamy" ,
2629 role : "Full-Stack Engineer (JavaScript / Node.js & React)" ,
@@ -298,7 +301,14 @@ program
298301 . option ( '-f, --format <type>' , 'output format (colored, plain, json)' , 'colored' )
299302 . option ( '-s, --section <sections...>' , 'specific sections to display (personal, profile, techStack, experience, projects, leadership, openSource, education)' )
300303 . option ( '-o, --output <file>' , 'save resume to file' )
301- . action ( ( options ) => {
304+ . option ( '-i, --interactive' , 'enable interactive navigation mode' )
305+ . action ( async ( options ) => {
306+ // Handle interactive mode
307+ if ( options . interactive ) {
308+ await runInteractiveMode ( ) ;
309+ return ;
310+ }
311+
302312 let output ;
303313 const sections = options . section ;
304314
@@ -341,4 +351,297 @@ program
341351 }
342352 } ) ;
343353
354+ // Interactive mode function
355+ async function runInteractiveMode ( ) {
356+ console . log ( chalk . cyanBright . bold ( '\n🚀 Interactive Resume Navigator\n' ) ) ;
357+
358+ while ( true ) {
359+ const { action } = await inquirer . prompt ( [
360+ {
361+ type : 'list' ,
362+ name : 'action' ,
363+ message : 'What would you like to do?' ,
364+ choices : [
365+ { name : '📄 View Resume Sections' , value : 'sections' } ,
366+ { name : '📱 Generate QR Codes' , value : 'qr' } ,
367+ { name : '📋 Copy Contact Info' , value : 'clipboard' } ,
368+ { name : '💾 Export Resume' , value : 'export' } ,
369+ { name : '❌ Exit' , value : 'exit' }
370+ ]
371+ }
372+ ] ) ;
373+
374+ switch ( action ) {
375+ case 'sections' :
376+ await navigateSections ( ) ;
377+ break ;
378+ case 'qr' :
379+ await generateQRCodes ( ) ;
380+ break ;
381+ case 'clipboard' :
382+ await copyToClipboard ( ) ;
383+ break ;
384+ case 'export' :
385+ await exportResume ( ) ;
386+ break ;
387+ case 'exit' :
388+ console . log ( chalk . greenBright ( '\n👋 Thanks for using the interactive resume!\n' ) ) ;
389+ return ;
390+ }
391+ }
392+ }
393+
394+ // Navigate through resume sections
395+ async function navigateSections ( ) {
396+ const sectionChoices = [
397+ { name : '👤 Personal Info' , value : 'personal' } ,
398+ { name : '📝 Profile' , value : 'profile' } ,
399+ { name : '⚡ Tech Stack' , value : 'techStack' } ,
400+ { name : '💼 Experience' , value : 'experience' } ,
401+ { name : '🚀 Projects' , value : 'projects' } ,
402+ { name : '👥 Leadership' , value : 'leadership' } ,
403+ { name : '🌟 Open Source' , value : 'openSource' } ,
404+ { name : '🎓 Education' , value : 'education' } ,
405+ { name : '📄 Full Resume' , value : 'full' } ,
406+ { name : '⬅️ Back to Main Menu' , value : 'back' }
407+ ] ;
408+
409+ while ( true ) {
410+ const { section } = await inquirer . prompt ( [
411+ {
412+ type : 'list' ,
413+ name : 'section' ,
414+ message : 'Which section would you like to view?' ,
415+ choices : sectionChoices
416+ }
417+ ] ) ;
418+
419+ if ( section === 'back' ) break ;
420+
421+ console . log ( '\n' + '=' . repeat ( 50 ) ) ;
422+ if ( section === 'full' ) {
423+ console . log ( formatColoredResume ( ) ) ;
424+ } else {
425+ console . log ( formatColoredResume ( [ section ] ) ) ;
426+ }
427+ console . log ( '=' . repeat ( 50 ) + '\n' ) ;
428+
429+ // Ask if user wants to continue viewing sections
430+ const { continueViewing } = await inquirer . prompt ( [
431+ {
432+ type : 'confirm' ,
433+ name : 'continueViewing' ,
434+ message : 'Would you like to view another section?' ,
435+ default : true
436+ }
437+ ] ) ;
438+
439+ if ( ! continueViewing ) break ;
440+ }
441+ }
442+
443+ // Generate QR codes for contact information
444+ async function generateQRCodes ( ) {
445+ const qrChoices = [
446+ { name : '📧 Email' , value : 'email' } ,
447+ { name : '📱 Phone' , value : 'phone' } ,
448+ { name : '💼 LinkedIn' , value : 'linkedin' } ,
449+ { name : '🐙 GitHub' , value : 'github' } ,
450+ { name : '🌐 Portfolio' , value : 'portfolio' } ,
451+ { name : '⬅️ Back to Main Menu' , value : 'back' }
452+ ] ;
453+
454+ while ( true ) {
455+ const { contact } = await inquirer . prompt ( [
456+ {
457+ type : 'list' ,
458+ name : 'contact' ,
459+ message : 'Generate QR code for which contact method?' ,
460+ choices : qrChoices
461+ }
462+ ] ) ;
463+
464+ if ( contact === 'back' ) break ;
465+
466+ let contactData = '' ;
467+ let contactLabel = '' ;
468+
469+ switch ( contact ) {
470+ case 'email' :
471+ contactData = `mailto:${ resumeData . personal . email . replace ( '📧 ' , '' ) } ` ;
472+ contactLabel = 'Email' ;
473+ break ;
474+ case 'phone' :
475+ contactData = `tel:${ resumeData . personal . phone . replace ( '📱 ' , '' ) } ` ;
476+ contactLabel = 'Phone' ;
477+ break ;
478+ case 'linkedin' :
479+ contactData = resumeData . personal . linkedin . replace ( '🔗 ' , '' ) ;
480+ contactLabel = 'LinkedIn' ;
481+ break ;
482+ case 'github' :
483+ contactData = resumeData . personal . github . replace ( '🐙 ' , '' ) ;
484+ contactLabel = 'GitHub' ;
485+ break ;
486+ case 'portfolio' :
487+ contactData = resumeData . personal . portfolio . replace ( '🌐 ' , '' ) ;
488+ contactLabel = 'Portfolio' ;
489+ break ;
490+ }
491+
492+ try {
493+ console . log ( `\n${ chalk . cyanBright . bold ( `QR Code for ${ contactLabel } :` ) } ` ) ;
494+ console . log ( chalk . dim ( `Data: ${ contactData } \n` ) ) ;
495+
496+ const qrString = await qrcode . toString ( contactData , {
497+ type : 'terminal' ,
498+ small : true
499+ } ) ;
500+
501+ console . log ( qrString ) ;
502+ console . log ( chalk . yellowBright ( '📱 Scan with your phone to access this contact info!\n' ) ) ;
503+
504+ } catch ( error ) {
505+ console . error ( chalk . red ( `Error generating QR code: ${ error . message } ` ) ) ;
506+ }
507+
508+ // Ask if user wants to generate another QR code
509+ const { continueQR } = await inquirer . prompt ( [
510+ {
511+ type : 'confirm' ,
512+ name : 'continueQR' ,
513+ message : 'Would you like to generate another QR code?' ,
514+ default : true
515+ }
516+ ] ) ;
517+
518+ if ( ! continueQR ) break ;
519+ }
520+ }
521+
522+ // Copy contact information to clipboard
523+ async function copyToClipboard ( ) {
524+ const clipboardChoices = [
525+ { name : '📧 Email Address' , value : 'email' } ,
526+ { name : '📱 Phone Number' , value : 'phone' } ,
527+ { name : '💼 LinkedIn URL' , value : 'linkedin' } ,
528+ { name : '🐙 GitHub URL' , value : 'github' } ,
529+ { name : '🌐 Portfolio URL' , value : 'portfolio' } ,
530+ { name : '📄 All Contact Info' , value : 'all' } ,
531+ { name : '⬅️ Back to Main Menu' , value : 'back' }
532+ ] ;
533+
534+ while ( true ) {
535+ const { contact } = await inquirer . prompt ( [
536+ {
537+ type : 'list' ,
538+ name : 'contact' ,
539+ message : 'What would you like to copy to clipboard?' ,
540+ choices : clipboardChoices
541+ }
542+ ] ) ;
543+
544+ if ( contact === 'back' ) break ;
545+
546+ let clipboardData = '' ;
547+ let contactLabel = '' ;
548+
549+ switch ( contact ) {
550+ case 'email' :
551+ clipboardData = resumeData . personal . email . replace ( '📧 ' , '' ) ;
552+ contactLabel = 'Email address' ;
553+ break ;
554+ case 'phone' :
555+ clipboardData = resumeData . personal . phone . replace ( '📱 ' , '' ) ;
556+ contactLabel = 'Phone number' ;
557+ break ;
558+ case 'linkedin' :
559+ clipboardData = resumeData . personal . linkedin . replace ( '🔗 ' , '' ) ;
560+ contactLabel = 'LinkedIn URL' ;
561+ break ;
562+ case 'github' :
563+ clipboardData = resumeData . personal . github . replace ( '🐙 ' , '' ) ;
564+ contactLabel = 'GitHub URL' ;
565+ break ;
566+ case 'portfolio' :
567+ clipboardData = resumeData . personal . portfolio . replace ( '🌐 ' , '' ) ;
568+ contactLabel = 'Portfolio URL' ;
569+ break ;
570+ case 'all' :
571+ clipboardData = `Email: ${ resumeData . personal . email . replace ( '📧 ' , '' ) } \nPhone: ${ resumeData . personal . phone . replace ( '📱 ' , '' ) } \nLinkedIn: ${ resumeData . personal . linkedin . replace ( '🔗 ' , '' ) } \nGitHub: ${ resumeData . personal . github . replace ( '🐙 ' , '' ) } \nPortfolio: ${ resumeData . personal . portfolio . replace ( '🌐 ' , '' ) } ` ;
572+ contactLabel = 'All contact information' ;
573+ break ;
574+ }
575+
576+ try {
577+ await clipboardy . write ( clipboardData ) ;
578+ console . log ( chalk . greenBright ( `\n✅ ${ contactLabel } copied to clipboard!` ) ) ;
579+ console . log ( chalk . dim ( `Copied: ${ clipboardData . split ( '\n' ) [ 0 ] } ${ clipboardData . includes ( '\n' ) ? '...' : '' } \n` ) ) ;
580+ } catch ( error ) {
581+ console . error ( chalk . red ( `Error copying to clipboard: ${ error . message } ` ) ) ;
582+ }
583+
584+ // Ask if user wants to copy something else
585+ const { continueCopy } = await inquirer . prompt ( [
586+ {
587+ type : 'confirm' ,
588+ name : 'continueCopy' ,
589+ message : 'Would you like to copy something else?' ,
590+ default : true
591+ }
592+ ] ) ;
593+
594+ if ( ! continueCopy ) break ;
595+ }
596+ }
597+
598+ // Export resume in different formats
599+ async function exportResume ( ) {
600+ const { format } = await inquirer . prompt ( [
601+ {
602+ type : 'list' ,
603+ name : 'format' ,
604+ message : 'Which format would you like to export?' ,
605+ choices : [
606+ { name : '🎨 Colored (Terminal)' , value : 'colored' } ,
607+ { name : '📝 Plain Text' , value : 'plain' } ,
608+ { name : '📊 JSON' , value : 'json' }
609+ ]
610+ }
611+ ] ) ;
612+
613+ const { filename } = await inquirer . prompt ( [
614+ {
615+ type : 'input' ,
616+ name : 'filename' ,
617+ message : 'Enter filename (without extension):' ,
618+ default : 'bharathkumar-resume'
619+ }
620+ ] ) ;
621+
622+ const extensions = { colored : 'txt' , plain : 'txt' , json : 'json' } ;
623+ const fullFilename = `${ filename } .${ extensions [ format ] } ` ;
624+
625+ let output ;
626+ switch ( format ) {
627+ case 'json' :
628+ output = formatJsonResume ( ) ;
629+ break ;
630+ case 'plain' :
631+ output = formatPlainResume ( ) ;
632+ break ;
633+ case 'colored' :
634+ default :
635+ output = formatColoredResume ( ) ;
636+ break ;
637+ }
638+
639+ try {
640+ fs . writeFileSync ( fullFilename , output ) ;
641+ console . log ( chalk . greenBright ( `\n✅ Resume exported to ${ fullFilename } !\n` ) ) ;
642+ } catch ( error ) {
643+ console . error ( chalk . red ( `Error exporting resume: ${ error . message } ` ) ) ;
644+ }
645+ }
646+
344647program . parse ( ) ;
0 commit comments