Add subscription detection and usage badge to status bar

- Detect user subscription type (Pro/Max) via CLI initialize request
- Cache subscription type in globalState for persistence
- Show clickable usage badge with chart icon in status bar
- Plan users: show "Max Plan" badge, opens live usage view
- API users: show cost badge, opens recent usage history
- Badge opens terminal in editor with ccusage command

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
andrepimenta
2025-12-03 00:29:08 +00:00
parent 14ac46018f
commit 63299008d0
3 changed files with 167 additions and 10 deletions

View File

@@ -120,6 +120,8 @@ class ClaudeChatProvider {
private _totalTokensInput: number = 0;
private _totalTokensOutput: number = 0;
private _requestCount: number = 0;
private _subscriptionType: string | undefined; // 'pro', 'max', or undefined for API users
private _accountInfoFetchedThisSession: boolean = false; // Track if we fetched account info this session
private _currentSessionId: string | undefined;
private _backupRepoPath: string | undefined;
private _commits: Array<{ id: string, sha: string, message: string, timestamp: string }> = [];
@@ -168,6 +170,9 @@ class ClaudeChatProvider {
// Load saved model preference
this._selectedModel = this._context.workspaceState.get('claude.selectedModel', 'default');
// Load cached subscription type (will be refreshed on first message)
this._subscriptionType = this._context.globalState.get('claude.subscriptionType');
// Resume session from latest conversation
const latestConversation = this._getLatestConversation();
this._currentSessionId = latestConversation?.sessionId;
@@ -255,6 +260,16 @@ class ClaudeChatProvider {
model: this._selectedModel
});
// Send cached subscription type to webview (will be refreshed on first message)
if (this._subscriptionType) {
this._postMessage({
type: 'accountInfo',
data: {
subscriptionType: this._subscriptionType
}
});
}
// Send platform information to webview
this._sendPlatformInfo();
@@ -311,6 +326,9 @@ class ClaudeChatProvider {
case 'openModelTerminal':
this._openModelTerminal();
return;
case 'viewUsage':
this._openUsageTerminal(message.usageType);
return;
case 'executeSlashCommand':
this._executeSlashCommand(message.command);
return;
@@ -597,6 +615,19 @@ class ClaudeChatProvider {
// Send the message to Claude's stdin as JSON (stream-json input format)
// Don't end stdin yet - we need to keep it open for permission responses
if (claudeProcess.stdin) {
// First, send an initialize request to get account info (once per session)
if (!this._accountInfoFetchedThisSession) {
this._accountInfoFetchedThisSession = true;
const initRequest = {
type: 'control_request',
request_id: 'init-' + Date.now(),
request: {
subtype: 'initialize'
}
};
claudeProcess.stdin.write(JSON.stringify(initRequest) + '\n');
}
const userMessage = {
type: 'user',
session_id: this._currentSessionId || '',
@@ -633,6 +664,12 @@ class ClaudeChatProvider {
continue;
}
// Handle control_response messages (responses to our initialize request)
if (jsonData.type === 'control_response') {
this._handleControlResponse(jsonData);
continue;
}
// Handle result message - end stdin when done
if (jsonData.type === 'result') {
if (claudeProcess.stdin && !claudeProcess.stdin.destroyed) {
@@ -1404,6 +1441,38 @@ class ClaudeChatProvider {
return false;
}
/**
* Handle control_response messages from Claude CLI via stdio
* This is used to get account info from the initialize request
*/
private _handleControlResponse(controlResponse: any): void {
// Structure: controlResponse.response.response.account
// The outer response has subtype/request_id, inner response has the actual data
const innerResponse = controlResponse.response?.response;
// Check if this is an initialize response with account info
if (innerResponse?.account) {
const account = innerResponse.account;
this._subscriptionType = account.subscriptionType;
console.log('Account info received:', {
subscriptionType: account.subscriptionType,
email: account.email
});
// Save to globalState for persistence
this._context.globalState.update('claude.subscriptionType', this._subscriptionType);
// Send subscription type to UI
this._postMessage({
type: 'accountInfo',
data: {
subscriptionType: this._subscriptionType
}
});
}
}
/**
* Handle control_request messages from Claude CLI via stdio
* This is the new permission flow that replaces the MCP file-based approach
@@ -2636,6 +2705,23 @@ class ClaudeChatProvider {
});
}
private _openUsageTerminal(usageType: string): void {
const terminal = vscode.window.createTerminal({
name: 'Claude Usage',
location: vscode.TerminalLocation.Editor
});
if (usageType === 'plan') {
// Plan users get live usage view
terminal.sendText('npx -y ccusage blocks --live');
} else {
// API users get recent usage history
terminal.sendText('npx -y ccusage blocks --recent --order desc');
}
terminal.show();
}
private _executeSlashCommand(command: string): void {
// Handle /compact in chat instead of spawning a terminal
if (command === 'compact') {