Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to the "copilot-token-tracker" extension will be documented

Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.

## [Unreleased]

### Added
- GitHub authentication support using VS Code's built-in authentication provider
- New commands: "Authenticate with GitHub" and "Sign Out from GitHub"
- GitHub Auth tab in Diagnostic Report panel showing authentication status
- Foundation for future GitHub-specific features (repository tracking, team collaboration, advanced analytics)

## [0.0.8]

### Changed
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ You can also use a **shared Azure Storage account** (a “shared storage server
- **Smart Estimation**: Uses character-based analysis with model-specific ratios for token estimation
- **Intelligent Caching**: Caches processed session files to speed up subsequent updates when files haven't changed
- **Diagnostic Reporting**: Generate comprehensive diagnostic reports to help troubleshoot issues
- **GitHub Authentication**: Authenticate with your GitHub account to unlock future features

### GitHub Authentication

- **Opt-in Authentication**: Sign in with your configured GitHub account in VS Code
- **Built-in VS Code Integration**: Uses VS Code's native authentication provider for GitHub
- **Secure Storage**: Authentication state is securely stored in VS Code's global state
- **Future Features**: Foundation for upcoming GitHub-specific features such as:
- Repository-specific usage tracking
- Team collaboration features
- Advanced analytics and insights

To authenticate, open the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`) and search for "Copilot Token Tracker: Authenticate with GitHub", or access it through the Diagnostic Report's GitHub Auth tab.

### Cloud Backend (Opt-in)

Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@
"command": "copilot-token-tracker.clearCache",
"title": "Clear Cache",
"category": "Copilot Token Tracker"
},
{
"command": "copilot-token-tracker.authenticateGitHub",
"title": "Authenticate with GitHub",
"category": "Copilot Token Tracker"
},
{
"command": "copilot-token-tracker.signOutGitHub",
"title": "Sign Out from GitHub",
"category": "Copilot Token Tracker"
}
],
"configuration": {
Expand Down
153 changes: 151 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ class CopilotTokenTracker implements vscode.Disposable {
// These are reference prices for cost estimation purposes only
private modelPricing: { [key: string]: ModelPricing } = modelPricingData.pricing as { [key: string]: ModelPricing };

// GitHub authentication session
private githubSession: vscode.AuthenticationSession | undefined;

// Helper method to get repository URL from package.json
private getRepositoryUrl(): string {
const repoUrl = packageJson.repository?.url?.replace(/^git\+/, '').replace(/\.git$/, '');
Expand Down Expand Up @@ -470,6 +473,9 @@ class CopilotTokenTracker implements vscode.Disposable {
// Load persisted cache from storage
this.loadCacheFromStorage();

// Restore GitHub authentication session if previously authenticated
this.restoreGitHubSession();

// Check GitHub Copilot extension status
this.checkCopilotExtension();

Expand Down Expand Up @@ -528,6 +534,38 @@ class CopilotTokenTracker implements vscode.Disposable {
}
}

/**
* Restore GitHub authentication session on extension startup
* Attempts to silently retrieve an existing session if user was previously authenticated
*/
private async restoreGitHubSession(): Promise<void> {
try {
const wasAuthenticated = this.context.globalState.get<boolean>('github.authenticated', false);
if (wasAuthenticated) {
this.log('Attempting to restore GitHub authentication session...');
// Try to get the existing session without prompting the user
// createIfNone: false ensures we don't prompt for authentication
const session = await vscode.authentication.getSession('github', ['read:user'], { createIfNone: false });
if (session) {
this.githubSession = session;
this.log(`✅ GitHub session restored for ${session.account.label}`);
// Update the stored username in case it changed
await this.context.globalState.update('github.username', session.account.label);
} else {
// Session doesn't exist anymore - clear the authenticated state
this.log('GitHub session not found - clearing authenticated state');
await this.context.globalState.update('github.authenticated', false);
await this.context.globalState.update('github.username', undefined);
}
}
} catch (error) {
this.warn('Failed to restore GitHub session: ' + String(error));
// Clear authentication state on error
await this.context.globalState.update('github.authenticated', false);
await this.context.globalState.update('github.username', undefined);
}
}

private recheckCopilotExtensionsAfterDelay(): void {
const copilotExtension = vscode.extensions.getExtension('GitHub.copilot');
const copilotChatExtension = vscode.extensions.getExtension('GitHub.copilot-chat');
Expand All @@ -542,6 +580,85 @@ class CopilotTokenTracker implements vscode.Disposable {
}
}

/**
* Authenticate with GitHub using VS Code's authentication API
* This creates a session that can be used for future GitHub-related features
*/
public async authenticateWithGitHub(): Promise<void> {
try {
this.log('Attempting GitHub authentication...');

// Request authentication with GitHub
// Using 'read:user' scope as a minimal requirement
const session = await vscode.authentication.getSession(
'github',
['read:user'],
{ createIfNone: true }
);

if (session) {
this.githubSession = session;
this.log(`✅ Successfully authenticated as ${session.account.label}`);
vscode.window.showInformationMessage(`GitHub authentication successful! Logged in as ${session.account.label}`);

// Store authentication state
await this.context.globalState.update('github.authenticated', true);
await this.context.globalState.update('github.username', session.account.label);
}
} catch (error) {
this.error('GitHub authentication failed:', error);
vscode.window.showErrorMessage('Failed to authenticate with GitHub. Please try again.');
}
}

/**
* Sign out from GitHub
*/
public async signOutFromGitHub(): Promise<void> {
try {
this.log('Signing out from GitHub...');
this.githubSession = undefined;
await this.context.globalState.update('github.authenticated', false);
await this.context.globalState.update('github.username', undefined);
this.log('✅ Successfully signed out from GitHub');
vscode.window.showInformationMessage('Signed out from GitHub successfully.');
} catch (error) {
this.error('Failed to sign out from GitHub:', error);
vscode.window.showErrorMessage('Failed to sign out from GitHub.');
}
}

/**
* Get the current GitHub authentication status
*/
public getGitHubAuthStatus(): { authenticated: boolean; username?: string } {
const authenticated = this.context.globalState.get<boolean>('github.authenticated', false);
const username = this.context.globalState.get<string>('github.username');
return { authenticated, username };
}

/**
* Check if the user is authenticated with GitHub
* Checks both in-memory session and persisted state for accuracy
*/
public isGitHubAuthenticated(): boolean {
// Primary check: in-memory session
if (this.githubSession !== undefined) {
return true;
}
// Fallback: check persisted state (session may not be restored yet)
// Note: This may be true even if the session is expired
// The restoreGitHubSession method will reconcile this on startup
return this.context.globalState.get<boolean>('github.authenticated', false);
}

/**
* Get the current GitHub session (if authenticated)
*/
public getGitHubSession(): vscode.AuthenticationSession | undefined {
return this.githubSession;
}

public async updateTokenStats(silent: boolean = false): Promise<DetailedStats | undefined> {
try {
this.log('Updating token stats...');
Expand Down Expand Up @@ -4214,6 +4331,22 @@ class CopilotTokenTracker implements vscode.Disposable {
case 'openSettings':
await vscode.commands.executeCommand('workbench.action.openSettings', 'copilotTokenTracker.backend');
break;
case 'authenticateGitHub':
this.log('authenticateGitHub message received from diagnostics webview');
await this.authenticateWithGitHub();
// Refresh the diagnostics panel to show the updated auth status
if (this.diagnosticsPanel) {
await this.loadDiagnosticDataInBackground(this.diagnosticsPanel);
}
break;
case 'signOutGitHub':
this.log('signOutGitHub message received from diagnostics webview');
await this.signOutFromGitHub();
// Refresh the diagnostics panel to show the updated auth status
if (this.diagnosticsPanel) {
await this.loadDiagnosticDataInBackground(this.diagnosticsPanel);
}
break;
}
});

Expand Down Expand Up @@ -4290,6 +4423,9 @@ class CopilotTokenTracker implements vscode.Disposable {
// Get backend storage info
const backendStorageInfo = await this.getBackendStorageInfo();

// Get GitHub authentication status
const githubAuthStatus = this.getGitHubAuthStatus();

// Check if panel is still open before updating
if (!this.isPanelOpen(panel)) {
this.log('Diagnostic panel closed during data load, aborting update');
Expand All @@ -4302,7 +4438,8 @@ class CopilotTokenTracker implements vscode.Disposable {
report,
sessionFiles: sessionFileData,
sessionFolders,
backendStorageInfo
backendStorageInfo,
githubAuth: githubAuthStatus
});

this.log('✅ Diagnostic data loaded and sent to webview');
Expand Down Expand Up @@ -4821,8 +4958,20 @@ export function activate(context: vscode.ExtensionContext) {
await tokenTracker.clearCache();
});

// Register the GitHub authentication command
const authenticateGitHubCommand = vscode.commands.registerCommand('copilot-token-tracker.authenticateGitHub', async () => {
tokenTracker.log('GitHub authentication command called');
await tokenTracker.authenticateWithGitHub();
});

// Register the GitHub sign out command
const signOutGitHubCommand = vscode.commands.registerCommand('copilot-token-tracker.signOutGitHub', async () => {
tokenTracker.log('GitHub sign out command called');
await tokenTracker.signOutFromGitHub();
});

// Add to subscriptions for proper cleanup
context.subscriptions.push(refreshCommand, showDetailsCommand, showChartCommand, showUsageAnalysisCommand, generateDiagnosticReportCommand, clearCacheCommand, tokenTracker);
context.subscriptions.push(refreshCommand, showDetailsCommand, showChartCommand, showUsageAnalysisCommand, generateDiagnosticReportCommand, clearCacheCommand, authenticateGitHubCommand, signOutGitHubCommand, tokenTracker);

tokenTracker.log('Extension activation complete');
}
Expand Down
Loading