8 Commits

Author SHA1 Message Date
andrepimenta
2f792e7158 Bump version to 1.1.0
- Update changelog with all new features since 1.0.7
- Add Inline Diff Viewer section to README
- Update package.json version

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 12:56:15 +00:00
andrepimenta
97920395d1 Add WSL support for usage terminal commands
Run ccusage commands through bash -ic in WSL to properly load shell environment.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 12:55:44 +00:00
andrepimenta
2e640fa20a Use --permission-mode plan flag for plan mode
Replace message prepending with native CLI flag for cleaner implementation.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 12:32:25 +00:00
andrepimenta
5136985474 Add Umami analytics events to install flow
Track user journey through installation:
- Install modal shown
- Install started
- Install success/failed

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 11:19:23 +00:00
andrepimenta
683148c4cf Add install modal for users without Claude Code CLI
Shows a clean modal when Claude Code is not installed, with one-click
installation that auto-detects platform (npm if node>=18, otherwise
curl/PowerShell). Runs silently in background with progress indicator.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 10:58:44 +00:00
andrepimenta
e18fa5e261 Improve terminal and UI experience
- Open all terminals in editor area (first column) instead of panel
- Update login message to mention Claude plan (Pro/Max) and API key
- Hide "Claude Code Chat" title when window is very small (<350px)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 00:41:53 +00:00
andrepimenta
63299008d0 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>
2025-12-03 00:29:08 +00:00
andrepimenta
14ac46018f Run /compact command in chat instead of spawning terminal
The /compact command now executes through the chat interface using
the existing Claude process, providing a seamless user experience
instead of opening a separate terminal window.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 18:12:46 +00:00
7 changed files with 637 additions and 31 deletions

View File

@@ -4,6 +4,38 @@ All notable changes to the "claude-code-chat" extension will be documented in th
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## [1.1.0] - 2025-12-06
### 🚀 Features Added
- **Install Modal**: Added installation flow for users without Claude Code CLI
- Auto-detects when Claude Code is not installed
- One-click installation with progress indicator
- Platform-specific installation commands
- **Diff Viewer Improvements**:
- Show full diff in Edit, MultiEdit, and Write tool use messages
- Add "Open Diff" button to open VS Code's native side-by-side diff editor
- Add truncation with expand button for long diffs
- Optimize diff storage and improve Open Diff button behavior
- **Processing Indicator**: New morphing orange dot animation while Claude is working
- **Subscription Detection**: Added usage badge to status bar showing plan type (Pro, Max) or API cost
- **Conversation Compacting**: Handle `/compact` command in chat with status messages and token reset
- **Permission System**: Migrated from MCP file-based to stdio-based permission prompts
- **Plan Mode**: Now uses native `--permission-mode plan` CLI flag for cleaner implementation
### 🐛 Bug Fixes
- Fixed diff line alignment by removing ::before pseudo-elements
- Fixed auto-scroll for diff tool results
- Strip tool_use_error tags from error messages
- Improved process termination handling
### 🔧 Technical Improvements
- Run /compact command in chat instead of spawning terminal
- Improved terminal and UI experience
- Updated diff icon colors
### 📊 Analytics
- Added Umami analytics events to track install flow (modal shown, started, success/failed)
## [1.0.7] - 2025-10-01 ## [1.0.7] - 2025-10-01
### 🚀 Features Added ### 🚀 Features Added

View File

@@ -47,6 +47,13 @@ Ditch the command line and experience Claude Code like never before. This extens
- Real-time cost and token tracking - Real-time cost and token tracking
- Session statistics and performance metrics - Session statistics and performance metrics
### 📝 **Inline Diff Viewer** ⭐ **NEW IN V1.1**
- **Full Diff Display** - See complete file changes directly in Edit, MultiEdit, and Write messages
- **Open in VS Code Diff** - One-click button to open VS Code's native side-by-side diff editor
- **Smart Truncation** - Long diffs are truncated with an expand button for better readability
- **Syntax Highlighting** - Proper code highlighting in diff views
- **Visual Change Indicators** - Clear green/red highlighting for additions and deletions
### 🔌 **MCP Server Management** ⭐ **NEW IN V1.0** ### 🔌 **MCP Server Management** ⭐ **NEW IN V1.0**
- **Popular Servers Gallery** - One-click installation of common MCP servers - **Popular Servers Gallery** - One-click installation of common MCP servers
- **Custom Server Creation** - Build and configure your own MCP servers - **Custom Server Creation** - Build and configure your own MCP servers

View File

@@ -2,7 +2,7 @@
"name": "claude-code-chat", "name": "claude-code-chat",
"displayName": "Chat for Claude Code", "displayName": "Chat for Claude Code",
"description": "Beautiful Claude Code Chat Interface for VS Code", "description": "Beautiful Claude Code Chat Interface for VS Code",
"version": "1.0.7", "version": "1.1.0",
"publisher": "AndrePimenta", "publisher": "AndrePimenta",
"author": "Andre Pimenta", "author": "Andre Pimenta",
"repository": { "repository": {

View File

@@ -120,6 +120,8 @@ class ClaudeChatProvider {
private _totalTokensInput: number = 0; private _totalTokensInput: number = 0;
private _totalTokensOutput: number = 0; private _totalTokensOutput: number = 0;
private _requestCount: 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 _currentSessionId: string | undefined;
private _backupRepoPath: string | undefined; private _backupRepoPath: string | undefined;
private _commits: Array<{ id: string, sha: string, message: string, timestamp: string }> = []; private _commits: Array<{ id: string, sha: string, message: string, timestamp: string }> = [];
@@ -168,6 +170,9 @@ class ClaudeChatProvider {
// Load saved model preference // Load saved model preference
this._selectedModel = this._context.workspaceState.get('claude.selectedModel', 'default'); 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 // Resume session from latest conversation
const latestConversation = this._getLatestConversation(); const latestConversation = this._getLatestConversation();
this._currentSessionId = latestConversation?.sessionId; this._currentSessionId = latestConversation?.sessionId;
@@ -255,6 +260,16 @@ class ClaudeChatProvider {
model: this._selectedModel 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 // Send platform information to webview
this._sendPlatformInfo(); this._sendPlatformInfo();
@@ -311,12 +326,18 @@ class ClaudeChatProvider {
case 'openModelTerminal': case 'openModelTerminal':
this._openModelTerminal(); this._openModelTerminal();
return; return;
case 'viewUsage':
this._openUsageTerminal(message.usageType);
return;
case 'executeSlashCommand': case 'executeSlashCommand':
this._executeSlashCommand(message.command); this._executeSlashCommand(message.command);
return; return;
case 'dismissWSLAlert': case 'dismissWSLAlert':
this._dismissWSLAlert(); this._dismissWSLAlert();
return; return;
case 'runInstallCommand':
this._runInstallCommand();
return;
case 'openFile': case 'openFile':
this._openFileInEditor(message.filePath); this._openFileInEditor(message.filePath);
return; return;
@@ -442,11 +463,8 @@ class ClaudeChatProvider {
const configThink = vscode.workspace.getConfiguration('claudeCodeChat'); const configThink = vscode.workspace.getConfiguration('claudeCodeChat');
const thinkingIntensity = configThink.get<string>('thinking.intensity', 'think'); const thinkingIntensity = configThink.get<string>('thinking.intensity', 'think');
// Prepend mode instructions if enabled // Prepend thinking mode instructions if enabled
let actualMessage = message; let actualMessage = message;
if (planMode) {
actualMessage = 'PLAN FIRST FOR THIS MESSAGE ONLY: Plan first before making any changes. Show me in detail what you will change and wait for my explicit approval in a separate message before proceeding. Do not implement anything until I confirm. This planning requirement applies ONLY to this current message. \n\n' + message;
}
if (thinkingMode) { if (thinkingMode) {
let thinkingPrompt = ''; let thinkingPrompt = '';
const thinkingMesssage = ' THROUGH THIS STEP BY STEP: \n' const thinkingMesssage = ' THROUGH THIS STEP BY STEP: \n'
@@ -527,6 +545,11 @@ class ClaudeChatProvider {
args.push('--mcp-config', this.convertToWSLPath(mcpConfigPath)); args.push('--mcp-config', this.convertToWSLPath(mcpConfigPath));
} }
// Add plan mode if enabled
if (planMode) {
args.push('--permission-mode', 'plan');
}
// Add model selection if not using default // Add model selection if not using default
if (this._selectedModel && this._selectedModel !== 'default') { if (this._selectedModel && this._selectedModel !== 'default') {
args.push('--model', this._selectedModel); args.push('--model', this._selectedModel);
@@ -597,6 +620,19 @@ class ClaudeChatProvider {
// Send the message to Claude's stdin as JSON (stream-json input format) // 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 // Don't end stdin yet - we need to keep it open for permission responses
if (claudeProcess.stdin) { 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 = { const userMessage = {
type: 'user', type: 'user',
session_id: this._currentSessionId || '', session_id: this._currentSessionId || '',
@@ -633,6 +669,12 @@ class ClaudeChatProvider {
continue; 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 // Handle result message - end stdin when done
if (jsonData.type === 'result') { if (jsonData.type === 'result') {
if (claudeProcess.stdin && !claudeProcess.stdin.destroyed) { if (claudeProcess.stdin && !claudeProcess.stdin.destroyed) {
@@ -719,9 +761,8 @@ class ClaudeChatProvider {
// Check if claude command is not installed // Check if claude command is not installed
if (error.message.includes('ENOENT') || error.message.includes('command not found')) { if (error.message.includes('ENOENT') || error.message.includes('command not found')) {
this._sendAndSaveMessage({ this._postMessage({
type: 'error', type: 'showInstallModal'
data: 'Install claude code first: https://www.anthropic.com/claude-code'
}); });
} else { } else {
this._sendAndSaveMessage({ this._sendAndSaveMessage({
@@ -1111,7 +1152,10 @@ class ClaudeChatProvider {
const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude'); const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude');
// Open terminal and run claude login // Open terminal and run claude login
const terminal = vscode.window.createTerminal('Claude Login'); const terminal = vscode.window.createTerminal({
name: 'Claude Login',
location: { viewColumn: vscode.ViewColumn.One }
});
if (wslEnabled) { if (wslEnabled) {
terminal.sendText(`wsl -d ${wslDistro} ${nodePath} --no-warnings --enable-source-maps ${claudePath}`); terminal.sendText(`wsl -d ${wslDistro} ${nodePath} --no-warnings --enable-source-maps ${claudePath}`);
} else { } else {
@@ -1121,14 +1165,14 @@ class ClaudeChatProvider {
// Show info message // Show info message
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
'Please login to Claude in the terminal, then come back to this chat to continue.', 'Please login with your Claude plan or API key in the terminal, then come back to this chat.',
'OK' 'OK'
); );
// Send message to UI about terminal // Send message to UI about terminal
this._postMessage({ this._postMessage({
type: 'terminalOpened', type: 'terminalOpened',
data: `Please login to Claude in the terminal, then come back to this chat to continue.`, data: `Please login with your Claude plan or API key in the terminal, then come back to this chat.`,
}); });
} }
@@ -1404,6 +1448,38 @@ class ClaudeChatProvider {
return false; 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 * Handle control_request messages from Claude CLI via stdio
* This is the new permission flow that replaces the MCP file-based approach * This is the new permission flow that replaces the MCP file-based approach
@@ -2615,7 +2691,10 @@ class ClaudeChatProvider {
} }
// Create terminal with the claude /model command // Create terminal with the claude /model command
const terminal = vscode.window.createTerminal('Claude Model Selection'); const terminal = vscode.window.createTerminal({
name: 'Claude Model Selection',
location: { viewColumn: vscode.ViewColumn.One }
});
if (wslEnabled) { if (wslEnabled) {
terminal.sendText(`wsl -d ${wslDistro} ${nodePath} --no-warnings --enable-source-maps ${claudePath} ${args.join(' ')}`); terminal.sendText(`wsl -d ${wslDistro} ${nodePath} --no-warnings --enable-source-maps ${claudePath} ${args.join(' ')}`);
} else { } else {
@@ -2636,7 +2715,84 @@ class ClaudeChatProvider {
}); });
} }
private _openUsageTerminal(usageType: string): void {
// Get WSL configuration
const config = vscode.workspace.getConfiguration('claudeCodeChat');
const wslEnabled = config.get<boolean>('wsl.enabled', false);
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
const terminal = vscode.window.createTerminal({
name: 'Claude Usage',
location: { viewColumn: vscode.ViewColumn.One }
});
let command: string;
if (usageType === 'plan') {
// Plan users get live usage view
command = 'npx -y ccusage blocks --live';
} else {
// API users get recent usage history
command = 'npx -y ccusage blocks --recent --order desc';
}
if (wslEnabled) {
terminal.sendText(`wsl -d ${wslDistro} bash -ic "${command}"`);
} else {
terminal.sendText(command);
}
terminal.show();
}
private _runInstallCommand(): void {
const { exec } = require('child_process');
// Check if npm exists and node >= 18
exec('node --version', { shell: true }, (nodeErr: Error | null, nodeStdout: string) => {
let useNpm = false;
if (!nodeErr && nodeStdout) {
// Parse version (e.g., "v18.17.0" -> 18)
const match = nodeStdout.trim().match(/^v(\d+)/);
if (match && parseInt(match[1], 10) >= 18) {
useNpm = true;
}
}
let command: string;
if (useNpm) {
command = 'npm install -g @anthropic-ai/claude-code';
} else if (process.platform === 'win32') {
command = 'irm https://claude.ai/install.ps1 | iex';
} else {
command = 'curl -fsSL https://claude.ai/install.sh | sh';
}
// Run installation silently in the background
exec(command, { shell: true }, (error: Error | null, stdout: string, stderr: string) => {
if (error) {
this._postMessage({
type: 'installComplete',
success: false,
error: stderr || error.message
});
} else {
this._postMessage({
type: 'installComplete',
success: true
});
}
});
});
}
private _executeSlashCommand(command: string): void { private _executeSlashCommand(command: string): void {
// Handle /compact in chat instead of spawning a terminal
if (command === 'compact') {
this._sendMessageToClaude(`/${command}`);
return;
}
const config = vscode.workspace.getConfiguration('claudeCodeChat'); const config = vscode.workspace.getConfiguration('claudeCodeChat');
const wslEnabled = config.get<boolean>('wsl.enabled', false); const wslEnabled = config.get<boolean>('wsl.enabled', false);
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu'); const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
@@ -2652,7 +2808,10 @@ class ClaudeChatProvider {
} }
// Create terminal with the claude command // Create terminal with the claude command
const terminal = vscode.window.createTerminal(`Claude /${command}`); const terminal = vscode.window.createTerminal({
name: `Claude /${command}`,
location: { viewColumn: vscode.ViewColumn.One }
});
if (wslEnabled) { if (wslEnabled) {
terminal.sendText(`wsl -d ${wslDistro} ${nodePath} --no-warnings --enable-source-maps ${claudePath} ${args.join(' ')}`); terminal.sendText(`wsl -d ${wslDistro} ${nodePath} --no-warnings --enable-source-maps ${claudePath} ${args.join(' ')}`);
} else { } else {

View File

@@ -890,6 +890,7 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
let isProcessing = false; let isProcessing = false;
let requestStartTime = null; let requestStartTime = null;
let requestTimer = null; let requestTimer = null;
let subscriptionType = null; // 'pro', 'max', or null for API users
// Send usage statistics // Send usage statistics
function sendStats(eventName) { function sendStats(eventName) {
@@ -909,6 +910,15 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
statusDiv.className = \`status \${state}\`; statusDiv.className = \`status \${state}\`;
} }
function updateStatusHtml(html, state = 'ready') {
statusTextDiv.innerHTML = html;
statusDiv.className = \`status \${state}\`;
}
function viewUsage(usageType) {
vscode.postMessage({ type: 'viewUsage', usageType: usageType });
}
function updateStatusWithTotals() { function updateStatusWithTotals() {
if (isProcessing) { if (isProcessing) {
// While processing, show tokens and elapsed time // While processing, show tokens and elapsed time
@@ -926,14 +936,29 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
updateStatus(statusText, 'processing'); updateStatus(statusText, 'processing');
} else { } else {
// When ready, show full info // When ready, show full info
const costStr = totalCost > 0 ? \`$\${totalCost.toFixed(4)}\` : '$0.00'; // Show plan type for subscription users, cost for API users
let usageStr;
const usageIcon = \`<svg class="usage-icon" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="8" width="3" height="6" rx="0.5" fill="currentColor" opacity="0.5"/>
<rect x="5.5" y="5" width="3" height="9" rx="0.5" fill="currentColor" opacity="0.7"/>
<rect x="10" y="2" width="3" height="12" rx="0.5" fill="currentColor"/>
</svg>\`;
if (subscriptionType) {
// Extract just the plan type (e.g., "Claude Max" -> "Max", "pro" -> "Pro")
let planName = subscriptionType.replace(/^claude\\s*/i, '').trim();
planName = planName.charAt(0).toUpperCase() + planName.slice(1);
usageStr = \`<a href="#" onclick="event.preventDefault(); viewUsage('plan');" class="usage-badge" title="View live usage">\${planName} Plan\${usageIcon}</a>\`;
} else {
const costStr = totalCost > 0 ? \`$\${totalCost.toFixed(4)}\` : '$0.00';
usageStr = \`<a href="#" onclick="event.preventDefault(); viewUsage('api');" class="usage-badge" title="View usage">\${costStr}\${usageIcon}</a>\`;
}
const totalTokens = totalTokensInput + totalTokensOutput; const totalTokens = totalTokensInput + totalTokensOutput;
const tokensStr = totalTokens > 0 ? const tokensStr = totalTokens > 0 ?
\`\${totalTokens.toLocaleString()} tokens\` : '0 tokens'; \`\${totalTokens.toLocaleString()} tokens\` : '0 tokens';
const requestStr = requestCount > 0 ? \`\${requestCount} requests\` : ''; const requestStr = requestCount > 0 ? \`\${requestCount} requests\` : '';
const statusText = \`Ready • \${costStr}\${tokensStr}\${requestStr ? \`\${requestStr}\` : ''}\`; const statusText = \`Ready • \${tokensStr}\${requestStr ? \`\${requestStr}\` : ''}\${usageStr}\`;
updateStatus(statusText, 'ready'); updateStatusHtml(statusText, 'ready');
} }
} }
@@ -1577,6 +1602,57 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
document.getElementById('slashCommandsModal').style.display = 'none'; document.getElementById('slashCommandsModal').style.display = 'none';
} }
// Install modal functions
function showInstallModal() {
const modal = document.getElementById('installModal');
const main = document.getElementById('installMain');
const progress = document.getElementById('installProgress');
const success = document.getElementById('installSuccess');
if (modal) modal.style.display = 'flex';
if (main) main.style.display = 'flex';
if (progress) progress.style.display = 'none';
if (success) success.style.display = 'none';
sendStats('Install modal shown');
}
function hideInstallModal() {
document.getElementById('installModal').style.display = 'none';
}
function startInstallation() {
sendStats('Install started');
// Hide main content, show progress
document.getElementById('installMain').style.display = 'none';
document.getElementById('installProgress').style.display = 'flex';
// Extension handles platform detection and command selection
vscode.postMessage({
type: 'runInstallCommand'
});
}
function handleInstallComplete(success, error) {
document.getElementById('installProgress').style.display = 'none';
const successEl = document.getElementById('installSuccess');
successEl.style.display = 'flex';
if (success) {
sendStats('Install success');
successEl.querySelector('.install-success-text').textContent = 'Installed';
successEl.querySelector('.install-success-hint').textContent = 'Send a message to get started';
} else {
sendStats('Install failed');
// Show error state
successEl.querySelector('.install-check').style.display = 'none';
successEl.querySelector('.install-success-text').textContent = 'Installation failed';
successEl.querySelector('.install-success-hint').textContent = error || 'Try installing manually from claude.ai/download';
}
}
// Thinking intensity modal functions // Thinking intensity modal functions
function showThinkingIntensityModal() { function showThinkingIntensityModal() {
// Request current settings from VS Code first // Request current settings from VS Code first
@@ -1679,18 +1755,22 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
function executeSlashCommand(command) { function executeSlashCommand(command) {
// Hide the modal // Hide the modal
hideSlashCommandsModal(); hideSlashCommandsModal();
// Clear the input since user selected a command // Clear the input since user selected a command
messageInput.value = ''; messageInput.value = '';
// Send command to VS Code to execute in terminal // Send command to VS Code to execute
vscode.postMessage({ vscode.postMessage({
type: 'executeSlashCommand', type: 'executeSlashCommand',
command: command command: command
}); });
// Show user feedback // Show user feedback - /compact runs in chat, others in terminal
addMessage('user', \`Executing /\${command} command in terminal. Check the terminal output and return when ready.\`, 'assistant'); if (command === 'compact') {
// No message needed - compact runs in chat and shows its own status
} else {
addMessage('user', \`Executing /\${command} command in terminal. Check the terminal output and return when ready.\`, 'assistant');
}
} }
function handleCustomCommandKeydown(event) { function handleCustomCommandKeydown(event) {
@@ -2161,17 +2241,25 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
totalTokensInput = message.data.totalTokensInput || 0; totalTokensInput = message.data.totalTokensInput || 0;
totalTokensOutput = message.data.totalTokensOutput || 0; totalTokensOutput = message.data.totalTokensOutput || 0;
requestCount = message.data.requestCount || 0; requestCount = message.data.requestCount || 0;
// Update status bar with new totals // Update status bar with new totals
updateStatusWithTotals(); updateStatusWithTotals();
// Show current request info if available // Show current request info if available (only for API users)
if (message.data.currentCost || message.data.currentDuration) { if (!subscriptionType && (message.data.currentCost || message.data.currentDuration)) {
const currentCostStr = message.data.currentCost ? \`$\${message.data.currentCost.toFixed(4)}\` : 'N/A'; const currentCostStr = message.data.currentCost ? \`$\${message.data.currentCost.toFixed(4)}\` : 'N/A';
const currentDurationStr = message.data.currentDuration ? \`\${message.data.currentDuration}ms\` : 'N/A'; const currentDurationStr = message.data.currentDuration ? \`\${message.data.currentDuration}ms\` : 'N/A';
addMessage(\`Request completed - Cost: \${currentCostStr}, Duration: \${currentDurationStr}\`, 'system'); addMessage(\`Request completed - Cost: \${currentCostStr}, Duration: \${currentDurationStr}\`, 'system');
} }
break; break;
case 'accountInfo':
// Store subscription type to determine cost vs plan display
subscriptionType = message.data.subscriptionType || null;
console.log('Account info received:', subscriptionType);
// Update status bar to reflect plan type
updateStatusWithTotals();
break;
case 'sessionResumed': case 'sessionResumed':
console.log('Session resumed:', message.data); console.log('Session resumed:', message.data);
@@ -2211,10 +2299,23 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
case 'loginRequired': case 'loginRequired':
sendStats('Login required'); sendStats('Login required');
addMessage('🔐 Login Required\\n\\nYour Claude API key is invalid or expired.\\nA terminal has been opened - please run the login process there.\\n\\nAfter logging in, come back to this chat to continue.', 'error'); addMessage('🔐 Login Required\\n\\nPlease login with your Claude plan (Pro/Max) or API key.\\nA terminal has been opened - follow the login process there.\\n\\nAfter logging in, come back to this chat to continue.', 'error');
updateStatus('Login Required', 'error'); updateStatus('Login Required', 'error');
break; break;
case 'showInstallModal':
sendStats('Claude not installed');
showInstallModal();
updateStatus('Claude Code not installed', 'error');
break;
case 'installComplete':
handleInstallComplete(message.success, message.error);
if (message.success) {
updateStatus('Ready', 'success');
}
break;
case 'showRestoreOption': case 'showRestoreOption':
showRestoreContainer(message.data); showRestoreContainer(message.data);
break; break;
@@ -2915,9 +3016,19 @@ const getScript = (isTelemetryEnabled: boolean) => `<script>
const date = new Date(conv.startTime).toLocaleDateString(); const date = new Date(conv.startTime).toLocaleDateString();
const time = new Date(conv.startTime).toLocaleTimeString(); const time = new Date(conv.startTime).toLocaleTimeString();
// Show plan type or cost based on subscription
let usageStr;
if (subscriptionType) {
let planName = subscriptionType.replace(/^claude\\s*/i, '').trim();
planName = planName.charAt(0).toUpperCase() + planName.slice(1);
usageStr = planName;
} else {
usageStr = \`$\${conv.totalCost.toFixed(3)}\`;
}
item.innerHTML = \` item.innerHTML = \`
<div class="conversation-title">\${conv.firstUserMessage.substring(0, 60)}\${conv.firstUserMessage.length > 60 ? '...' : ''}</div> <div class="conversation-title">\${conv.firstUserMessage.substring(0, 60)}\${conv.firstUserMessage.length > 60 ? '...' : ''}</div>
<div class="conversation-meta">\${date} at \${time}\${conv.messageCount} messages • $\${conv.totalCost.toFixed(3)}</div> <div class="conversation-meta">\${date} at \${time}\${conv.messageCount} messages • \${usageStr}</div>
<div class="conversation-preview">Last: \${conv.lastUserMessage.substring(0, 80)}\${conv.lastUserMessage.length > 80 ? '...' : ''}</div> <div class="conversation-preview">Last: \${conv.lastUserMessage.substring(0, 80)}\${conv.lastUserMessage.length > 80 ? '...' : ''}</div>
\`; \`;

View File

@@ -28,6 +28,12 @@ const styles = `
letter-spacing: -0.3px; letter-spacing: -0.3px;
} }
@media (max-width: 385px) {
.header h2 {
display: none;
}
}
.controls { .controls {
display: flex; display: flex;
gap: 6px; gap: 6px;
@@ -2432,6 +2438,34 @@ const styles = `
flex: 1; flex: 1;
} }
.status-text .usage-badge {
display: inline-flex;
align-items: center;
gap: 4px;
color: inherit;
text-decoration: none;
background: rgba(255, 255, 255, 0.08);
padding: 2px 8px 2px 8px;
border-radius: 10px;
cursor: pointer;
transition: background 0.15s, transform 0.1s;
}
.status-text .usage-badge:hover {
background: rgba(255, 255, 255, 0.15);
transform: translateY(-1px);
}
.status-text .usage-badge:active {
transform: translateY(0);
}
.status-text .usage-icon {
width: 12px;
height: 12px;
flex-shrink: 0;
}
pre { pre {
white-space: pre-wrap; white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
@@ -2992,6 +3026,218 @@ const styles = `
} }
} }
/* Install Modal Styles */
.install-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.install-modal-backdrop {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(2px);
}
.install-modal-content {
position: relative;
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-widget-border, var(--vscode-panel-border));
border-radius: 12px;
width: 320px;
padding: 32px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
animation: installFadeIn 0.2s ease-out;
}
@keyframes installFadeIn {
from { opacity: 0; transform: scale(0.95) translateY(-8px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.install-close-btn {
position: absolute;
top: 16px;
right: 16px;
width: 28px;
height: 28px;
background: none;
border: none;
color: var(--vscode-descriptionForeground);
cursor: pointer;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.6;
transition: all 0.15s;
}
.install-close-btn:hover {
background: var(--vscode-toolbar-hoverBackground);
opacity: 1;
}
.install-body {
text-align: center;
}
.install-main {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.install-icon-wrapper {
width: 64px;
height: 64px;
border-radius: 16px;
background: var(--vscode-button-background);
display: flex;
align-items: center;
justify-content: center;
}
.install-icon {
color: var(--vscode-button-foreground);
}
.install-text {
display: flex;
flex-direction: column;
gap: 6px;
}
.install-title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--vscode-foreground);
}
.install-desc {
margin: 0;
font-size: 13px;
color: var(--vscode-descriptionForeground);
line-height: 1.4;
}
.install-btn {
width: 100%;
padding: 12px 24px;
font-size: 14px;
font-weight: 500;
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.15s;
}
.install-btn:hover {
background: var(--vscode-button-hoverBackground);
transform: translateY(-1px);
}
.install-btn:active {
transform: translateY(0);
}
.install-link {
font-size: 13px;
color: var(--vscode-textLink-foreground);
text-decoration: none;
opacity: 0.9;
}
.install-link:hover {
text-decoration: underline;
opacity: 1;
}
.install-progress {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 20px 0;
}
.install-spinner {
width: 32px;
height: 32px;
border: 2.5px solid var(--vscode-widget-border, var(--vscode-panel-border));
border-top-color: var(--vscode-button-background);
border-radius: 50%;
animation: installSpin 0.8s linear infinite;
}
@keyframes installSpin {
to { transform: rotate(360deg); }
}
.install-progress-text {
margin: 0;
font-size: 14px;
font-weight: 500;
color: var(--vscode-foreground);
}
.install-progress-hint {
margin: 0;
font-size: 12px;
color: var(--vscode-descriptionForeground);
}
.install-success {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 20px 0;
}
.install-success-icon {
width: 56px;
height: 56px;
border-radius: 50%;
background: rgba(78, 201, 176, 0.15);
display: flex;
align-items: center;
justify-content: center;
}
.install-check {
width: 28px;
height: 28px;
color: var(--vscode-testing-iconPassed, #4ec9b0);
}
.install-success-text {
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--vscode-foreground);
}
.install-success-hint {
margin: 0;
font-size: 13px;
color: var(--vscode-descriptionForeground);
}
</style>` </style>`
export default styles export default styles

View File

@@ -399,6 +399,57 @@ const getHtml = (isTelemetryEnabled: boolean) => `<!DOCTYPE html>
</div> </div>
</div> </div>
<!-- Install Claude Code modal -->
<div id="installModal" class="install-modal" style="display: none;">
<div class="install-modal-backdrop" onclick="hideInstallModal()"></div>
<div class="install-modal-content">
<button class="install-close-btn" onclick="hideInstallModal()">
<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
<path d="M1.5 1.5L10.5 10.5M1.5 10.5L10.5 1.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</button>
<div class="install-body" id="installBody">
<div class="install-main" id="installMain">
<div class="install-icon-wrapper">
<svg class="install-icon" width="40" height="40" viewBox="0 0 24 24" fill="none">
<path d="M21 15V19C21 20.1 20.1 21 19 21H5C3.9 21 3 20.1 3 19V15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 3V15M12 15L7 10M12 15L17 10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="install-text">
<h2 class="install-title">Install Claude Code</h2>
<p class="install-desc">The CLI is required to use this extension</p>
</div>
<button class="install-btn" id="installMainBtn" onclick="startInstallation()">
Install Now
</button>
<a href="https://docs.anthropic.com/en/docs/claude-code" target="_blank" class="install-link">
View documentation
</a>
</div>
<div class="install-progress" id="installProgress" style="display: none;">
<div class="install-spinner"></div>
<p class="install-progress-text">Installing Claude Code...</p>
<p class="install-progress-hint">This may take a minute</p>
</div>
<div class="install-success" id="installSuccess" style="display: none;">
<div class="install-success-icon">
<svg class="install-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
</div>
<p class="install-success-text">Installation Complete</p>
<p class="install-success-hint">Send a message to get started</p>
</div>
</div>
</div>
</div>
<!-- Thinking intensity modal --> <!-- Thinking intensity modal -->
<div id="thinkingIntensityModal" class="tools-modal" style="display: none;"> <div id="thinkingIntensityModal" class="tools-modal" style="display: none;">
<div class="tools-modal-content" style="width: 450px;"> <div class="tools-modal-content" style="width: 450px;">