@@ -246,6 +246,9 @@ class CopilotTokenTracker implements vscode.Disposable {
246246 // Backend facade instance for accessing table storage data
247247 private backend : BackendFacade | undefined ;
248248
249+ // GitHub authentication session (undefined = not signed in)
250+ private githubSession : vscode . AuthenticationSession | undefined ;
251+
249252 // Helper method to get repository URL from package.json
250253 private getRepositoryUrl ( ) : string {
251254 return _getRepositoryUrl ( ) ;
@@ -826,6 +829,9 @@ class CopilotTokenTracker implements vscode.Disposable {
826829 // Load persisted cache from storage
827830 this . cacheManager . loadCacheFromStorage ( ) ;
828831
832+ // Silently restore GitHub session if user was previously authenticated
833+ this . restoreGitHubSession ( ) ;
834+
829835 // Check GitHub Copilot extension status
830836 this . sessionDiscovery . checkCopilotExtension ( ) ;
831837
@@ -898,6 +904,92 @@ class CopilotTokenTracker implements vscode.Disposable {
898904 }
899905 }
900906
907+ /**
908+ * Silently restore a GitHub authentication session on startup.
909+ * Only runs if the user was previously authenticated.
910+ * Uses createIfNone: false to never prompt the user automatically.
911+ */
912+ private async restoreGitHubSession ( ) : Promise < void > {
913+ try {
914+ const wasAuthenticated = this . context . globalState . get < boolean > ( 'github.authenticated' , false ) ;
915+ if ( wasAuthenticated ) {
916+ this . log ( 'Attempting to restore GitHub authentication session...' ) ;
917+ const session = await vscode . authentication . getSession ( 'github' , [ 'read:user' ] , { createIfNone : false } ) ;
918+ if ( session ) {
919+ this . githubSession = session ;
920+ this . log ( `✅ GitHub session restored for ${ session . account . label } ` ) ;
921+ await this . context . globalState . update ( 'github.username' , session . account . label ) ;
922+ } else {
923+ this . log ( 'GitHub session not found — clearing authenticated state' ) ;
924+ await this . context . globalState . update ( 'github.authenticated' , false ) ;
925+ await this . context . globalState . update ( 'github.username' , undefined ) ;
926+ }
927+ }
928+ } catch ( error ) {
929+ this . warn ( 'Failed to restore GitHub session: ' + String ( error ) ) ;
930+ await this . context . globalState . update ( 'github.authenticated' , false ) ;
931+ await this . context . globalState . update ( 'github.username' , undefined ) ;
932+ }
933+ }
934+
935+ /**
936+ * Authenticate with GitHub using VS Code's built-in authentication API.
937+ * Only called when the user explicitly requests it (e.g. clicks "Authenticate").
938+ */
939+ public async authenticateWithGitHub ( ) : Promise < void > {
940+ try {
941+ this . log ( 'Attempting GitHub authentication...' ) ;
942+ const session = await vscode . authentication . getSession (
943+ 'github' ,
944+ [ 'read:user' ] ,
945+ { createIfNone : true } ,
946+ ) ;
947+ if ( session ) {
948+ this . githubSession = session ;
949+ this . log ( `✅ Successfully authenticated as ${ session . account . label } ` ) ;
950+ vscode . window . showInformationMessage ( `GitHub authentication successful! Logged in as ${ session . account . label } ` ) ;
951+ await this . context . globalState . update ( 'github.authenticated' , true ) ;
952+ await this . context . globalState . update ( 'github.username' , session . account . label ) ;
953+ }
954+ } catch ( error ) {
955+ this . error ( 'GitHub authentication failed:' , error ) ;
956+ vscode . window . showErrorMessage ( 'Failed to authenticate with GitHub. Please try again.' ) ;
957+ }
958+ }
959+
960+ /** Sign out from GitHub and clear the persisted authentication state. */
961+ public async signOutFromGitHub ( ) : Promise < void > {
962+ try {
963+ this . log ( 'Signing out from GitHub...' ) ;
964+ this . githubSession = undefined ;
965+ await this . context . globalState . update ( 'github.authenticated' , false ) ;
966+ await this . context . globalState . update ( 'github.username' , undefined ) ;
967+ this . log ( '✅ Successfully signed out from GitHub' ) ;
968+ vscode . window . showInformationMessage ( 'Signed out from GitHub successfully.' ) ;
969+ } catch ( error ) {
970+ this . error ( 'Failed to sign out from GitHub:' , error ) ;
971+ vscode . window . showErrorMessage ( 'Failed to sign out from GitHub.' ) ;
972+ }
973+ }
974+
975+ /** Return the current GitHub authentication status based on the in-memory session. */
976+ public getGitHubAuthStatus ( ) : { authenticated : boolean ; username ?: string } {
977+ if ( this . githubSession ) {
978+ return { authenticated : true , username : this . githubSession . account . label } ;
979+ }
980+ return { authenticated : false } ;
981+ }
982+
983+ /** Return true if the user is currently authenticated with GitHub. */
984+ public isGitHubAuthenticated ( ) : boolean {
985+ return this . githubSession !== undefined ;
986+ }
987+
988+ /** Return the live GitHub authentication session, or undefined if not signed in. */
989+ public getGitHubSession ( ) : vscode . AuthenticationSession | undefined {
990+ return this . githubSession ;
991+ }
992+
901993 private async showFluencyScoreNewsBanner ( ) : Promise < void > {
902994 const dismissedKey = 'news.fluencyScoreBanner.v1.dismissed' ;
903995 if ( this . context . globalState . get < boolean > ( dismissedKey ) ) {
@@ -6785,6 +6877,24 @@ ${hashtag}`;
67856877 } ) ;
67866878 }
67876879 break ;
6880+ case "authenticateGitHub" :
6881+ await this . dispatch ( 'authenticateGitHub:diagnostics' , async ( ) => {
6882+ this . log ( 'authenticateGitHub message received from diagnostics webview' ) ;
6883+ await this . authenticateWithGitHub ( ) ;
6884+ if ( this . diagnosticsPanel ) {
6885+ await this . loadDiagnosticDataInBackground ( this . diagnosticsPanel ) ;
6886+ }
6887+ } ) ;
6888+ break ;
6889+ case "signOutGitHub" :
6890+ await this . dispatch ( 'signOutGitHub:diagnostics' , async ( ) => {
6891+ this . log ( 'signOutGitHub message received from diagnostics webview' ) ;
6892+ await this . signOutFromGitHub ( ) ;
6893+ if ( this . diagnosticsPanel ) {
6894+ await this . loadDiagnosticDataInBackground ( this . diagnosticsPanel ) ;
6895+ }
6896+ } ) ;
6897+ break ;
67886898 }
67896899 } ) ;
67906900
@@ -6926,6 +7036,7 @@ ${hashtag}`;
69267036 sessionFolders,
69277037 candidatePaths,
69287038 backendStorageInfo,
7039+ githubAuth : this . getGitHubAuthStatus ( ) ,
69297040 } ) ;
69307041
69317042 this . log ( "✅ Diagnostic data loaded and sent to webview" ) ;
@@ -7677,6 +7788,23 @@ export function activate(context: vscode.ExtensionContext) {
76777788 } ,
76787789 ) ;
76797790
7791+ // Register the GitHub authentication commands
7792+ const authenticateGitHubCommand = vscode . commands . registerCommand (
7793+ "copilot-token-tracker.authenticateGitHub" ,
7794+ async ( ) => {
7795+ tokenTracker . log ( "GitHub authentication command called" ) ;
7796+ await tokenTracker . authenticateWithGitHub ( ) ;
7797+ } ,
7798+ ) ;
7799+
7800+ const signOutGitHubCommand = vscode . commands . registerCommand (
7801+ "copilot-token-tracker.signOutGitHub" ,
7802+ async ( ) => {
7803+ tokenTracker . log ( "GitHub sign out command called" ) ;
7804+ await tokenTracker . signOutFromGitHub ( ) ;
7805+ } ,
7806+ ) ;
7807+
76807808 // Add to subscriptions for proper cleanup
76817809 context . subscriptions . push (
76827810 refreshCommand ,
@@ -7690,6 +7818,8 @@ export function activate(context: vscode.ExtensionContext) {
76907818 showEnvironmentalCommand ,
76917819 generateDiagnosticReportCommand ,
76927820 clearCacheCommand ,
7821+ authenticateGitHubCommand ,
7822+ signOutGitHubCommand ,
76937823 tokenTracker ,
76947824 ) ;
76957825
0 commit comments