27 Commits

Author SHA1 Message Date
andrepimenta
d891070d9e Modify icon and name 2025-10-14 17:23:49 +01:00
andrepimenta
1be89d43a4 Added more built-in commands 2025-10-01 23:20:26 +01:00
andrepimenta
0abfab72a8 Add changelog 2025-08-26 23:48:38 +01:00
andrepimenta
1eacc6ff74 Remove priority 2025-08-26 23:44:29 +01:00
andrepimenta
031a2c5fc3 Fix typo 2025-07-31 00:03:16 +01:00
andrepimenta
73c4a38da1 Move script to another file 2025-07-30 23:27:24 +01:00
andrepimenta
53acc0a79f changelog 2025-07-30 03:36:58 +01:00
andrepimenta
62163dbc32 respect telemetry settings 2025-07-30 03:36:37 +01:00
andrepimenta
d225ff2596 Fix permission dialog when closing and opening 2025-07-30 03:26:59 +01:00
Andre Pimenta
ab5c393253 Merge pull request #87 from horatio-sans-serif/main
Remove maxlength limit for custom command prompt textarea
2025-07-29 01:03:06 +01:00
andrepimenta
d6a73a1a7f Add claude-code-chat-permissions-mcp folder 2025-07-29 00:58:20 +01:00
andrepimenta
5abb1fedd9 Save message in text box 2025-07-28 23:45:42 +01:00
andrepimenta
3b534cfce2 Always display history and new chat 2025-07-28 23:37:31 +01:00
andrepimenta
6bd906981b Fix new chat 2025-07-28 23:33:17 +01:00
andrepimenta
4f126641e4 Fix request start time isProcessing 2025-07-28 22:31:30 +01:00
andrepimenta
2d63eaac58 Fix close and open conversation 2025-07-28 22:09:22 +01:00
Old Yeller
f44dc28763 Remove maxlength limit for custom command prompt textarea
This fixes issue #86 by removing the maxlength attribute from the custom command prompt textarea in the slash commands modal. Users can now add much longer markdown content when creating custom slash commands, resolving the previous truncation at ~500 characters.
2025-07-28 20:14:13 +00:00
andrepimenta
2c47349282 fix: input overflow and flexible panel positioning 2025-07-22 21:50:56 +01:00
andrepimenta
43c1c85efb Fix permissions and add windows support 2025-07-20 00:07:02 +01:00
Andre Pimenta
b07857bf57 Update README.md 2025-07-16 00:00:06 +01:00
andrepimenta
c9677b6185 Changelog and readme 2025-07-15 22:58:59 +01:00
andrepimenta
2053e768a8 Yolo mode warning less scary 2025-07-14 23:59:22 +01:00
andrepimenta
980d19bcb2 Yolo mode a bit less subtle 2025-07-14 23:55:13 +01:00
andrepimenta
8a581908e3 Yolo mode 2025-07-14 23:46:03 +01:00
andrepimenta
2feaed600d Fix permissions on side panel 2025-07-14 23:15:40 +01:00
andrepimenta
826c25bdd6 Remove custom commands from settings 2025-07-14 22:18:29 +01:00
Andre Pimenta
3e8a9630bd Merge pull request #49 from andrepimenta/feature/mcp-support
Feature/mcp support
2025-07-14 22:12:13 +01:00
16 changed files with 18914 additions and 3052 deletions

View File

@@ -11,3 +11,4 @@ vsc-extension-quickstart.md
**/.vscode-test.* **/.vscode-test.*
backup backup
.claude .claude
claude-code-chat-permissions-mcp/**

View File

@@ -4,6 +4,175 @@ 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.0.7] - 2025-10-01
### 🚀 Features Added
- **Slash Commands Update**: Added 4 new slash commands to the commands modal
- `/add-dir` - Add additional working directories
- `/agents` - Manage custom AI subagents for specialized tasks
- `/rewind` - Rewind the conversation and/or code
- `/usage` - Show plan usage limits and rate limit status (subscription plans only)
### 📚 Documentation Updates
- Updated slash commands count from 19+ to 23+ built-in commands
- Enhanced command descriptions for better clarity:
- `/config` - Now specifies "Open the Settings interface (Config tab)"
- `/cost` - Added note about cost tracking guide for subscription-specific details
- `/status` - Expanded description to mention version, model, account, and connectivity
- `/terminal-setup` - Added clarification about iTerm2 and VSCode only support
## [1.0.6] - 2025-08-26
### 🐛 Bug Fixes
- Fixed typo in codebase
- Removed priority settings that were no longer needed
### 🔧 Technical Improvements
- Moved script to separate file for better code organization
## [1.0.5] - 2025-07-30
### 🚀 Features Added
- **MCP Integration**: Added claude-code-chat-permissions-mcp folder for enhanced permission management
- **Message Persistence**: Save message in text box for better user experience
- **UI Improvements**: Always display history and new chat options
- **Input Enhancement**: Removed maxlength limit for custom command prompt textarea
### 🐛 Bug Fixes
- Fixed new chat functionality
- Fixed request start time isProcessing issue
- Fixed close and open conversation behavior
### 🔄 Merged Pull Requests
- Merged PR #87 from horatio-sans-serif/main
## [1.0.4] - 2025-01-22
### 🐛 Bug Fixes
- Fixed input text area overflow issue by adding `box-sizing: border-box` to prevent padding from extending beyond container width
- Fixed command parameter handling for `claude-code-chat.openChat` to properly handle both ViewColumn and Uri parameters from different invocation contexts
### 🔧 Technical Improvements
- Enhanced `show()` method to accept optional ViewColumn parameter with ViewColumn.Two as default
- Added proper type checking for command parameters to handle context menu invocations
- Improved webview panel positioning with flexible column parameter support
### 🎨 UI/UX Improvements
- Resolved text input container sizing issues that caused visual overflow
- Better input field styling consistency across different VS Code themes
## [1.0.0] - 2025-01-15
### 🚀 Major Features Added
#### **Advanced Permissions Management System**
- Complete permissions framework with MCP integration for secure tool execution
- Interactive permission dialogs with detailed tool information and command previews
- "Always Allow" functionality with smart command pattern matching for common tools (npm, git, docker, etc.)
- YOLO mode for power users to skip all permission checks
- Comprehensive permissions settings UI with ability to add/remove specific permissions
- File system watcher for real-time permission request handling
- Workspace-specific permission storage and management
#### **MCP (Model Context Protocol) Server Management**
- Complete MCP server configuration interface
- Popular MCP servers gallery with one-click installation
- Custom MCP server creation with validation
- Server management (edit, delete, enable/disable)
- Automatic permissions server integration
- WSL path conversion for cross-platform compatibility
#### **Sidebar Integration & Multi-Panel Support**
- Native VS Code sidebar view with full chat functionality
- Smart panel management (auto-close main panel when sidebar opens)
- Persistent session state across panel switches
- Proper webview lifecycle management
- Activity bar integration with custom icon
#### **Image & Clipboard Enhancements**
- Drag-and-drop image support directly into chat
- Clipboard image paste functionality (Ctrl+V for screenshots)
- Multiple image selection with VS Code's native file picker
- Automatic image organization in `.claude/claude-code-chat-images/` folder
- Automatic `.gitignore` creation for image folders
- Support for PNG, JPG, JPEG, GIF, SVG, WebP, BMP formats
#### **Code Block & Syntax Improvements**
- Enhanced markdown parsing with proper code block detection
- Syntax highlighting for code blocks with language detection
- Copy-to-clipboard functionality for code blocks
- Improved inline code rendering
- Better handling of technical identifiers and underscores
### 🎨 UI/UX Improvements
#### **Settings & Configuration**
- Comprehensive settings modal with organized sections
- YOLO mode toggle with visual warnings and explanations
- Real-time settings synchronization between UI and VS Code config
- Better visual hierarchy and professional styling
- Smart configuration validation and error handling
#### **Message & Chat Interface**
- Improved message spacing and visual consistency
- Enhanced tool result display with better formatting
- Smart scrolling behavior (only auto-scroll if user is at bottom)
- Loading indicators and processing states
- Better error handling and user feedback
#### **YOLO Mode Visual Design**
- Less subtle YOLO mode section (increased opacity and size)
- Changed warning icon from ⚠️ to 🚀 for less intimidating appearance
- Soft tomato red styling that's noticeable but not scary
- Clear explanation of YOLO mode functionality
### 🔧 Technical Enhancements
#### **Session & State Management**
- Persistent session state across VS Code restarts
- Proper cleanup of resources and event listeners
- Better error handling for failed operations
- Improved memory management for large conversations
#### **Cross-Platform Compatibility**
- Enhanced WSL support with proper path conversion
- Windows-specific improvements and fixes
- Better handling of different operating system environments
- Improved subprocess management and cleanup
#### **Performance Optimizations**
- Reduced context usage with more efficient tool operations
- Better file search and workspace integration
- Optimized message handling and UI updates
- Improved extension activation and initialization times
#### **Developer Experience**
- Better error messages and debugging information
- Improved extension logging and troubleshooting
- Enhanced development workflow support
- Better TypeScript integration and type safety
### 🐛 Bug Fixes
- Fixed multiple permission prompts being sent simultaneously
- Resolved panel management issues with multiple webviews
- Fixed expand/collapse functionality for long content
- Corrected Unix timestamp parsing for usage limit messages
- Fixed WSL integration on Windows systems
- Resolved markdown parsing issues with underscores in code
- Fixed copy-paste functionality for images and code blocks
- Corrected file path handling across different platforms
### 🔄 Breaking Changes
- Permission system now requires explicit approval for tool execution (unless YOLO mode is enabled)
- Image files are now stored in `.claude/claude-code-chat-images/` instead of root directory
- MCP configuration moved to extension storage instead of global config
### 📚 Documentation & Community
- Updated README with comprehensive feature documentation
- Fixed GitHub issues link in repository
- Enhanced examples and usage guides
- Better onboarding experience for new users
## [0.1.3] - 2025-06-24 ## [0.1.3] - 2025-06-24
### 🚀 Features Added ### 🚀 Features Added

View File

@@ -16,14 +16,15 @@ Ditch the command line and experience Claude Code like never before. This extens
🖥️ **No Terminal Required** - Beautiful chat interface replaces command-line interactions 🖥️ **No Terminal Required** - Beautiful chat interface replaces command-line interactions
**Restore Checkpoints** - Undo changes and restore code to any previous state **Restore Checkpoints** - Undo changes and restore code to any previous state
🔌 **MCP Server Support** - Complete Model Context Protocol server management
💾 **Conversation History** - Automatic conversation history and session management 💾 **Conversation History** - Automatic conversation history and session management
🎨 **VS Code Native** - Claude Code integrated directly into VS Code with native theming 🎨 **VS Code Native** - Claude Code integrated directly into VS Code with native theming and sidebar support
🧠 **Plan and Thinking modes** - Plan First and configurable Thinking modes for better results 🧠 **Plan and Thinking modes** - Plan First and configurable Thinking modes for better results
**Smart File Context and Commands** - Reference any file with simple @ mentions and / for commands **Smart File/Image Context and Custom Commands** - Reference any file, paste images or screenshots and create custom commands
🤖 **Model Selection** - Choose between Opus, Sonnet, or Default based on your needs 🤖 **Model Selection** - Choose between Opus, Sonnet, or Default based on your needs
🐧 **WSL Support** - Full Windows Subsystem for Linux integration and compatibility 🐧 **Windows/WSL Support** - Full native Windows and WSL support
![Claude Code Chat Cut](https://github.com/user-attachments/assets/d4ded28f-a4ed-4862-9766-c1ff89947775) ![Claude Code Chat 1 0 0](https://github.com/user-attachments/assets/5954a74c-eff7-4205-8482-6a1c9de6e102)
--- ---
@@ -34,8 +35,9 @@ Ditch the command line and experience Claude Code like never before. This extens
- No terminal required - everything through the UI - No terminal required - everything through the UI
- Real-time streaming responses with typing indicators - Real-time streaming responses with typing indicators
- One-click message copying with visual feedback - One-click message copying with visual feedback
- Rich markdown support for code blocks and formatting - Enhanced markdown support with syntax highlighting
- Auto-resizing input that grows with your content - Auto-resizing input that grows with your content
- Copy-to-clipboard for code blocks
### ⏪ **Checkpoint & Session Management** ### ⏪ **Checkpoint & Session Management**
- **Restore Checkpoints** - Instantly undo changes and restore to any previous state - **Restore Checkpoints** - Instantly undo changes and restore to any previous state
@@ -45,9 +47,36 @@ 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
### 🔌 **MCP Server Management** ⭐ **NEW IN V1.0**
- **Popular Servers Gallery** - One-click installation of common MCP servers
- **Custom Server Creation** - Build and configure your own MCP servers
- **Server Management** - Edit, delete, enable/disable servers through UI
- **Automatic Integration** - Seamless permissions and tool integration
- **Cross-platform Support** - Full WSL compatibility with path conversion
### 🔒 **Advanced Permissions System** ⭐ **NEW IN V1.0**
- **Interactive Permission Dialogs** - Detailed tool information with command previews
- **Always Allow Functionality** - Smart command pattern matching for common tools (npm, git, docker)
- **YOLO Mode** - Skip all permission checks for power users
- **Workspace Permissions** - Granular control over what tools can execute
- **Real-time Permission Management** - Add/remove permissions through intuitive UI
### 🖼️ **Image & Clipboard Support** ⭐ **NEW IN V1.0**
- **Drag & Drop Images** - Simply drag images directly into the chat
- **Clipboard Paste** - Press Ctrl+V to paste screenshots and copied images
- **Multiple Image Selection** - Choose multiple images through VS Code's file picker
- **Organized Storage** - Automatic organization in `.claude/claude-code-chat-images/`
- **Format Support** - PNG, JPG, JPEG, GIF, SVG, WebP, BMP formats
### 📱 **Sidebar Integration** ⭐ **NEW IN V1.0**
- **Native VS Code Sidebar** - Full chat functionality in the sidebar panel
- **Smart Panel Management** - Automatic switching between main and sidebar views
- **Persistent Sessions** - State maintained across panel switches
- **Activity Bar Integration** - Quick access from VS Code's activity bar
### 📁 **Smart File Integration** ### 📁 **Smart File Integration**
- Type `@` to instantly search and reference workspace files - Type `@` to instantly search and reference workspace files
- Image attachments via file browser - Image attachments via file browser and copy-paste screeshots
- Lightning-fast file search across your entire project - Lightning-fast file search across your entire project
- Seamless context preservation for multi-file discussions - Seamless context preservation for multi-file discussions
@@ -55,6 +84,7 @@ Ditch the command line and experience Claude Code like never before. This extens
- Visual dashboard showing all available Claude Code tools - Visual dashboard showing all available Claude Code tools
- Real-time tool execution with formatted results - Real-time tool execution with formatted results
- Process control - start, stop, and monitor operations - Process control - start, stop, and monitor operations
- Smart permission system for secure tool execution
### 🎨 **VS Code Integration** ### 🎨 **VS Code Integration**
- Native theming that matches your editor - Native theming that matches your editor
@@ -73,7 +103,7 @@ Ditch the command line and experience Claude Code like never before. This extens
### ⚡ **Slash Commands Integration** ### ⚡ **Slash Commands Integration**
- **Slash Commands Modal** - Type "/" to access all Claude Code commands instantly - **Slash Commands Modal** - Type "/" to access all Claude Code commands instantly
- **19+ Built-in Commands** - /cost, /status, /config, /help, /memory, /review, and more - **23+ Built-in Commands** - /agents, /cost, /config, /memory, /review, and more
- **Custom Command Support** - Execute any Claude Code command with session context - **Custom Command Support** - Execute any Claude Code command with session context
- **Session-Aware Execution** - All commands run with current conversation context - **Session-Aware Execution** - All commands run with current conversation context
- **Terminal Integration** - Commands open directly in VS Code terminal with WSL support - **Terminal Integration** - Commands open directly in VS Code terminal with WSL support
@@ -214,31 +244,39 @@ Example configuration in `settings.json`:
- Type `@` followed by your search term to quickly reference files - Type `@` followed by your search term to quickly reference files
- Use `@src/` to narrow down to specific directories - Use `@src/` to narrow down to specific directories
- Reference multiple files in one message for cross-file analysis - Reference multiple files in one message for cross-file analysis
- **NEW**: Copy-paste images directly into chat for visual context
- **NEW**: Paste screenshots with Ctrl+V for instant visual communication
### ⚡ **Productivity Boosters** ### ⚡ **Productivity Boosters**
- **Creates checkpoints automatically** before changes for safe experimentation - **Creates checkpoints automatically** before changes for safe experimentation
- **Restore instantly** if changes don't work out as expected - **Restore instantly** if changes don't work out as expected
- **NEW**: Permission system prevents accidental tool execution
- **NEW**: YOLO mode for power users who want speed over safety
- Use the stop button to cancel long-running operations - Use the stop button to cancel long-running operations
- Copy message contents to reuse Claude's responses - Copy message contents to reuse Claude's responses
- Open history panel to reference previous conversations - Open history panel to reference previous conversations
- **NEW**: Sidebar integration for multi-panel workflow
### 🎨 **Interface Customization** ### 🎨 **Interface Customization**
- The UI automatically adapts to your VS Code theme - The UI automatically adapts to your VS Code theme
- Messages are color-coded: Green for you, Blue for Claude - Messages are color-coded: Green for you, Blue for Claude
- Hover over messages to reveal the copy button - Hover over messages to reveal the copy button
- **NEW**: Enhanced code block rendering with syntax highlighting
- **NEW**: Copy-to-clipboard functionality for code blocks
--- ---
## 🔧 **Advanced Features** ## 🔧 **Advanced Features**
### 🛠️ **Tool Integration** ### 🛠️ **Tool Integration**
Claude Code Chat provides full access to all Claude Code tools: Claude Code Chat provides secure access to all Claude Code tools:
- **Bash** - Execute shell commands - **Bash** - Execute shell commands with permission controls
- **File Operations** - Read, write, and edit files - **File Operations** - Read, write, and edit files
- **Search** - Grep and glob pattern matching - **Search** - Grep and glob pattern matching across workspace
- **Web** - Fetch and search web content - **Web** - Fetch and search web content
- **Multi-edit** - Batch file modifications - **Multi-edit** - Batch file modifications
- **While in Beta, all tools are enabled by default, use at your own risk!** - **MCP Servers** - Extend functionality with Model Context Protocol servers
- **Permissions System** - Granular control over tool execution for security
### 📊 **Analytics & Monitoring** ### 📊 **Analytics & Monitoring**
- **Real-time cost tracking** - Monitor your API usage - **Real-time cost tracking** - Monitor your API usage

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,212 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import * as fs from "fs";
import * as path from "path";
const server = new McpServer({
name: "Claude Code Permissions MCP Server",
version: "0.0.1",
});
// Get permissions directory from environment
const PERMISSIONS_PATH = process.env.CLAUDE_PERMISSIONS_PATH;
if (!PERMISSIONS_PATH) {
console.error("CLAUDE_PERMISSIONS_PATH environment variable not set");
process.exit(1);
}
interface WorkspacePermissions {
alwaysAllow: {
[toolName: string]: boolean | string[]; // true for all, or array of allowed commands/patterns
};
}
function getWorkspacePermissionsPath(): string | null {
if (!PERMISSIONS_PATH) return null;
return path.join(PERMISSIONS_PATH, 'permissions.json');
}
function loadWorkspacePermissions(): WorkspacePermissions {
const permissionsPath = getWorkspacePermissionsPath();
if (!permissionsPath || !fs.existsSync(permissionsPath)) {
return { alwaysAllow: {} };
}
try {
const content = fs.readFileSync(permissionsPath, 'utf8');
return JSON.parse(content);
} catch (error) {
console.error(`Error loading workspace permissions: ${error}`);
return { alwaysAllow: {} };
}
}
function isAlwaysAllowed(toolName: string, input: any): boolean {
const permissions = loadWorkspacePermissions();
const toolPermission = permissions.alwaysAllow[toolName];
if (!toolPermission) return false;
// If it's true, always allow
if (toolPermission === true) return true;
// If it's an array, check for specific commands (mainly for Bash)
if (Array.isArray(toolPermission)) {
if (toolName === 'Bash' && input.command) {
const command = input.command.trim();
return toolPermission.some(allowedCmd => {
// Support exact match or pattern matching
if (allowedCmd.includes('*')) {
// Handle patterns like "npm i *" to match both "npm i" and "npm i something"
const baseCommand = allowedCmd.replace(' *', '');
if (command === baseCommand) {
return true; // Exact match for base command
}
// Pattern match for command with arguments
const pattern = allowedCmd.replace(/\*/g, '.*');
return new RegExp(`^${pattern}$`).test(command);
}
return command.startsWith(allowedCmd);
});
}
}
return false;
}
function generateRequestId(): string {
return `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
}
async function requestPermission(tool_name: string, input: any): Promise<{approved: boolean, reason?: string}> {
if (!PERMISSIONS_PATH) {
console.error("Permissions path not available");
return { approved: false, reason: "Permissions path not configured" };
}
// Check if this tool/command is always allowed for this workspace
if (isAlwaysAllowed(tool_name, input)) {
console.error(`Tool ${tool_name} is always allowed for this workspace`);
return { approved: true };
}
const requestId = generateRequestId();
const requestFile = path.join(PERMISSIONS_PATH, `${requestId}.request`);
const responseFile = path.join(PERMISSIONS_PATH, `${requestId}.response`);
// Write request file
const request = {
id: requestId,
tool: tool_name,
input: input,
timestamp: new Date().toISOString()
};
try {
fs.writeFileSync(requestFile, JSON.stringify(request, null, 2));
// Use fs.watch to wait for response file
return new Promise<{approved: boolean, reason?: string}>((resolve) => {
const timeout = setTimeout(() => {
watcher.close();
// Clean up request file on timeout
if (fs.existsSync(requestFile)) {
fs.unlinkSync(requestFile);
}
console.error(`Permission request ${requestId} timed out`);
resolve({ approved: false, reason: "Permission request timed out" });
}, 3600000); // 1 hour timeout
const watcher = fs.watch(PERMISSIONS_PATH, (eventType, filename) => {
if (eventType === 'rename' && filename === path.basename(responseFile)) {
// Check if file exists (rename event can be for creation or deletion)
if (fs.existsSync(responseFile)) {
try {
const responseContent = fs.readFileSync(responseFile, 'utf8');
const response = JSON.parse(responseContent);
// Clean up response file
fs.unlinkSync(responseFile);
// Clear timeout and close watcher
clearTimeout(timeout);
watcher.close();
resolve({
approved: response.approved,
reason: response.approved ? undefined : "User rejected the request"
});
} catch (error) {
console.error(`Error reading response file: ${error}`);
// Continue watching in case of read error
}
}
}
});
// Handle watcher errors
watcher.on('error', (error) => {
console.error(`File watcher error: ${error}`);
clearTimeout(timeout);
watcher.close();
resolve({ approved: false, reason: "File watcher error" });
});
});
} catch (error) {
console.error(`Error requesting permission: ${error}`);
return { approved: false, reason: `Error processing permission request: ${error}` };
}
}
server.tool(
"approval_prompt",
'Request user permission to execute a tool via VS Code dialog',
{
tool_name: z.string().describe("The name of the tool requesting permission"),
input: z.object({}).passthrough().describe("The input for the tool"),
tool_use_id: z.string().optional().describe("The unique tool use request ID"),
},
async ({ tool_name, input }) => {
console.error(`Requesting permission for tool: ${tool_name}`);
const permissionResult = await requestPermission(tool_name, input);
const behavior = permissionResult.approved ? "allow" : "deny";
console.error(`Permission ${behavior}ed for tool: ${tool_name}`);
return {
content: [
{
type: "text",
text: behavior === "allow" ?
JSON.stringify({
behavior: behavior,
updatedInput: input,
})
:
JSON.stringify({
behavior: behavior,
message: permissionResult.reason || "Permission denied",
})
,
},
],
};
}
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error(`Permissions MCP Server running on stdio`);
console.error(`Using permissions directory: ${PERMISSIONS_PATH}`);
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
{
"name": "claude-code-chat-permissions-mcp",
"version": "1.0.0",
"main": "dist/mcp-permissions.js",
"scripts": {
"start": "tsc && node dist/mcp-permissions.js",
"lint": "eslint . --ext .ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"@types/node": "^24.0.13",
"typescript": "^5.8.3"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.15.1",
"zod": "^3.25.76"
}
}

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist"
},
"lib": ["es2015"]
}

BIN
icon-bubble.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 KiB

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 KiB

After

Width:  |  Height:  |  Size: 689 KiB

View File

@@ -1,8 +1,8 @@
{ {
"name": "claude-code-chat", "name": "claude-code-chat",
"displayName": "Claude Code Chat", "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.0", "version": "1.0.7",
"publisher": "AndrePimenta", "publisher": "AndrePimenta",
"author": "Andre Pimenta", "author": "Andre Pimenta",
"repository": { "repository": {
@@ -56,7 +56,7 @@
"command": "claude-code-chat.openChat", "command": "claude-code-chat.openChat",
"title": "Open Claude Code Chat", "title": "Open Claude Code Chat",
"category": "Claude Code Chat", "category": "Claude Code Chat",
"icon": "icon.png" "icon": "icon-bubble.png"
} }
], ],
"keybindings": [ "keybindings": [
@@ -133,7 +133,7 @@
"type": "webview", "type": "webview",
"name": "Claude Code Chat", "name": "Claude Code Chat",
"when": "true", "when": "true",
"icon": "icon.png", "icon": "icon-bubble.png",
"contextualTitle": "Claude Code Chat" "contextualTitle": "Claude Code Chat"
} }
] ]
@@ -143,7 +143,7 @@
{ {
"id": "claude-code-chat", "id": "claude-code-chat",
"title": "Claude Code Chat", "title": "Claude Code Chat",
"icon": "icon.png" "icon": "icon-bubble.png"
} }
] ]
}, },

View File

@@ -2,7 +2,7 @@ import * as vscode from 'vscode';
import * as cp from 'child_process'; import * as cp from 'child_process';
import * as util from 'util'; import * as util from 'util';
import * as path from 'path'; import * as path from 'path';
import html from './ui'; import getHtml from './ui';
const exec = util.promisify(cp.exec); const exec = util.promisify(cp.exec);
@@ -10,9 +10,9 @@ export function activate(context: vscode.ExtensionContext) {
console.log('Claude Code Chat extension is being activated!'); console.log('Claude Code Chat extension is being activated!');
const provider = new ClaudeChatProvider(context.extensionUri, context); const provider = new ClaudeChatProvider(context.extensionUri, context);
const disposable = vscode.commands.registerCommand('claude-code-chat.openChat', () => { const disposable = vscode.commands.registerCommand('claude-code-chat.openChat', (column?: vscode.ViewColumn) => {
console.log('Claude Code Chat command executed!'); console.log('Claude Code Chat command executed!');
provider.show(); provider.show(column);
}); });
const loadConversationDisposable = vscode.commands.registerCommand('claude-code-chat.loadConversation', (filename: string) => { const loadConversationDisposable = vscode.commands.registerCommand('claude-code-chat.loadConversation', (filename: string) => {
@@ -44,12 +44,26 @@ export function activate(context: vscode.ExtensionContext) {
export function deactivate() { } export function deactivate() { }
interface ConversationData {
sessionId: string;
startTime: string | undefined;
endTime: string;
messageCount: number;
totalCost: number;
totalTokens: {
input: number;
output: number;
};
messages: Array<{ timestamp: string, messageType: string, data: any }>;
filename: string;
}
class ClaudeChatWebviewProvider implements vscode.WebviewViewProvider { class ClaudeChatWebviewProvider implements vscode.WebviewViewProvider {
constructor( constructor(
private readonly _extensionUri: vscode.Uri, private readonly _extensionUri: vscode.Uri,
private readonly _context: vscode.ExtensionContext, private readonly _context: vscode.ExtensionContext,
private readonly _chatProvider: ClaudeChatProvider private readonly _chatProvider: ClaudeChatProvider
) {} ) { }
public resolveWebviewView( public resolveWebviewView(
webviewView: vscode.WebviewView, webviewView: vscode.WebviewView,
@@ -112,6 +126,8 @@ class ClaudeChatProvider {
}> = []; }> = [];
private _currentClaudeProcess: cp.ChildProcess | undefined; private _currentClaudeProcess: cp.ChildProcess | undefined;
private _selectedModel: string = 'default'; // Default model private _selectedModel: string = 'default'; // Default model
private _isProcessing: boolean | undefined;
private _draftMessage: string = '';
constructor( constructor(
private readonly _extensionUri: vscode.Uri, private readonly _extensionUri: vscode.Uri,
@@ -122,7 +138,6 @@ class ClaudeChatProvider {
this._initializeBackupRepo(); this._initializeBackupRepo();
this._initializeConversations(); this._initializeConversations();
this._initializeMCPConfig(); this._initializeMCPConfig();
this._initializePermissions();
// Load conversation index from workspace state // Load conversation index from workspace state
this._conversationIndex = this._context.workspaceState.get('claude.conversationIndex', []); this._conversationIndex = this._context.workspaceState.get('claude.conversationIndex', []);
@@ -135,21 +150,22 @@ class ClaudeChatProvider {
this._currentSessionId = latestConversation?.sessionId; this._currentSessionId = latestConversation?.sessionId;
} }
public show() { public show(column: vscode.ViewColumn | vscode.Uri = vscode.ViewColumn.Two) {
const column = vscode.ViewColumn.Two; // Handle case where a URI is passed instead of ViewColumn
const actualColumn = column instanceof vscode.Uri ? vscode.ViewColumn.Two : column;
// Close sidebar if it's open // Close sidebar if it's open
this._closeSidebar(); this._closeSidebar();
if (this._panel) { if (this._panel) {
this._panel.reveal(column); this._panel.reveal(actualColumn);
return; return;
} }
this._panel = vscode.window.createWebviewPanel( this._panel = vscode.window.createWebviewPanel(
'claudeChat', 'claudeChat',
'Claude Code Chat', 'Claude Code Chat',
column, actualColumn,
{ {
enableScripts: true, enableScripts: true,
retainContextWhenHidden: true, retainContextWhenHidden: true,
@@ -158,7 +174,7 @@ class ClaudeChatProvider {
); );
// Set icon for the webview tab using URI path // Set icon for the webview tab using URI path
const iconPath = vscode.Uri.joinPath(this._extensionUri, 'icon.png'); const iconPath = vscode.Uri.joinPath(this._extensionUri, 'icon-bubble.png');
this._panel.iconPath = iconPath; this._panel.iconPath = iconPath;
this._panel.webview.html = this._getHtmlForWebview(); this._panel.webview.html = this._getHtmlForWebview();
@@ -166,6 +182,7 @@ class ClaudeChatProvider {
this._panel.onDidDispose(() => this.dispose(), null, this._disposables); this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
this._setupWebviewMessageHandler(this._panel.webview); this._setupWebviewMessageHandler(this._panel.webview);
this._initializePermissions();
// Resume session from latest conversation // Resume session from latest conversation
const latestConversation = this._getLatestConversation(); const latestConversation = this._getLatestConversation();
@@ -195,18 +212,18 @@ class ClaudeChatProvider {
private _sendReadyMessage() { private _sendReadyMessage() {
// Send current session info if available // Send current session info if available
if (this._currentSessionId) { /*if (this._currentSessionId) {
this._postMessage({ this._postMessage({
type: 'sessionResumed', type: 'sessionResumed',
data: { data: {
sessionId: this._currentSessionId sessionId: this._currentSessionId
} }
}); });
} }*/
this._postMessage({ this._postMessage({
type: 'ready', type: 'ready',
data: 'Ready to chat with Claude Code! Type your message below.' data: this._isProcessing ? 'Claude is working...' : 'Ready to chat with Claude Code! Type your message below.'
}); });
// Send current model to webview // Send current model to webview
@@ -220,6 +237,14 @@ class ClaudeChatProvider {
// Send current settings to webview // Send current settings to webview
this._sendCurrentSettings(); this._sendCurrentSettings();
// Send saved draft message if any
if (this._draftMessage) {
this._postMessage({
type: 'restoreInputText',
data: this._draftMessage
});
}
} }
private _handleWebviewMessage(message: any) { private _handleWebviewMessage(message: any) {
@@ -305,6 +330,12 @@ class ClaudeChatProvider {
case 'deleteCustomSnippet': case 'deleteCustomSnippet':
this._deleteCustomSnippet(message.snippetId); this._deleteCustomSnippet(message.snippetId);
return; return;
case 'enableYoloMode':
this._enableYoloMode();
return;
case 'saveInputText':
this._saveInputText(message.text);
return;
} }
} }
@@ -342,6 +373,7 @@ class ClaudeChatProvider {
this._webview.html = this._getHtmlForWebview(); this._webview.html = this._getHtmlForWebview();
this._setupWebviewMessageHandler(this._webview); this._setupWebviewMessageHandler(this._webview);
this._initializePermissions();
// Initialize the webview // Initialize the webview
this._initializeWebview(); this._initializeWebview();
@@ -366,6 +398,7 @@ class ClaudeChatProvider {
public reinitializeWebview() { public reinitializeWebview() {
// Only reinitialize if we have a webview (sidebar) // Only reinitialize if we have a webview (sidebar)
if (this._webview) { if (this._webview) {
this._initializePermissions();
this._initializeWebview(); this._initializeWebview();
// Set up message handler for the webview // Set up message handler for the webview
this._setupWebviewMessageHandler(this._webview); this._setupWebviewMessageHandler(this._webview);
@@ -407,16 +440,21 @@ class ClaudeChatProvider {
actualMessage = thinkingPrompt + thinkingMesssage + actualMessage; actualMessage = thinkingPrompt + thinkingMesssage + actualMessage;
} }
this._isProcessing = true;
// Clear draft message since we're sending it
this._draftMessage = '';
// Show original user input in chat and save to conversation (without mode prefixes) // Show original user input in chat and save to conversation (without mode prefixes)
this._sendAndSaveMessage({ this._sendAndSaveMessage({
type: 'userInput', type: 'userInput',
data: message data: message
}); });
// Set processing state // Set processing state to true
this._postMessage({ this._postMessage({
type: 'setProcessing', type: 'setProcessing',
data: true data: { isProcessing: true }
}); });
// Create backup commit before Claude makes changes // Create backup commit before Claude makes changes
@@ -433,9 +471,6 @@ class ClaudeChatProvider {
data: 'Claude is working...' data: 'Claude is working...'
}); });
// Call claude with the message via stdin using stream-json format
console.log('Calling Claude with message via stdin:', message);
// Build command arguments with session management // Build command arguments with session management
const args = [ const args = [
'-p', '-p',
@@ -462,7 +497,6 @@ class ClaudeChatProvider {
// 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);
console.log('Using model:', this._selectedModel);
} }
// Add session resume if we have a current session // Add session resume if we have a current session
@@ -486,8 +520,6 @@ class ClaudeChatProvider {
console.log('Using WSL configuration:', { wslDistro, nodePath, claudePath }); console.log('Using WSL configuration:', { wslDistro, nodePath, claudePath });
const wslCommand = `"${nodePath}" --no-warnings --enable-source-maps "${claudePath}" ${args.join(' ')}`; const wslCommand = `"${nodePath}" --no-warnings --enable-source-maps "${claudePath}" ${args.join(' ')}`;
console.log('wsl', ['-d', wslDistro, 'bash', '-ic', wslCommand].join(" "))
claudeProcess = cp.spawn('wsl', ['-d', wslDistro, 'bash', '-ic', wslCommand], { claudeProcess = cp.spawn('wsl', ['-d', wslDistro, 'bash', '-ic', wslCommand], {
cwd: cwd, cwd: cwd,
stdio: ['pipe', 'pipe', 'pipe'], stdio: ['pipe', 'pipe', 'pipe'],
@@ -501,6 +533,7 @@ class ClaudeChatProvider {
// Use native claude command // Use native claude command
console.log('Using native Claude command'); console.log('Using native Claude command');
claudeProcess = cp.spawn('claude', args, { claudeProcess = cp.spawn('claude', args, {
shell: process.platform === 'win32',
cwd: cwd, cwd: cwd,
stdio: ['pipe', 'pipe', 'pipe'], stdio: ['pipe', 'pipe', 'pipe'],
env: { env: {
@@ -554,14 +587,27 @@ class ClaudeChatProvider {
console.log('Claude process closed with code:', code); console.log('Claude process closed with code:', code);
console.log('Claude stderr output:', errorOutput); console.log('Claude stderr output:', errorOutput);
if (!this._currentClaudeProcess) {
return;
}
// Clear process reference // Clear process reference
this._currentClaudeProcess = undefined; this._currentClaudeProcess = undefined;
// Clear loading indicator // Clear loading indicator and set processing to false
this._postMessage({ this._postMessage({
type: 'clearLoading' type: 'clearLoading'
}); });
// Reset processing state
this._isProcessing = false;
// Clear processing state
this._postMessage({
type: 'setProcessing',
data: { isProcessing: false }
});
if (code !== 0 && errorOutput.trim()) { if (code !== 0 && errorOutput.trim()) {
// Error with output // Error with output
this._sendAndSaveMessage({ this._sendAndSaveMessage({
@@ -574,6 +620,10 @@ class ClaudeChatProvider {
claudeProcess.on('error', (error) => { claudeProcess.on('error', (error) => {
console.log('Claude process error:', error.message); console.log('Claude process error:', error.message);
if (!this._currentClaudeProcess) {
return;
}
// Clear process reference // Clear process reference
this._currentClaudeProcess = undefined; this._currentClaudeProcess = undefined;
@@ -581,6 +631,14 @@ class ClaudeChatProvider {
type: 'clearLoading' type: 'clearLoading'
}); });
this._isProcessing = false;
// Clear processing state
this._postMessage({
type: 'setProcessing',
data: { isProcessing: false }
});
// 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._sendAndSaveMessage({
@@ -597,13 +655,23 @@ class ClaudeChatProvider {
} }
private _processJsonStreamData(jsonData: any) { private _processJsonStreamData(jsonData: any) {
console.log('Received JSON data:', jsonData);
switch (jsonData.type) { switch (jsonData.type) {
case 'system': case 'system':
if (jsonData.subtype === 'init') { if (jsonData.subtype === 'init') {
// System initialization message - session ID will be captured from final result // System initialization message - session ID will be captured from final result
console.log('System initialized'); console.log('System initialized');
this._currentSessionId = jsonData.session_id;
//this._sendAndSaveMessage({ type: 'init', data: { sessionId: jsonData.session_id; } })
// Show session info in UI
this._sendAndSaveMessage({
type: 'sessionInfo',
data: {
sessionId: jsonData.session_id,
tools: jsonData.tools || [],
mcpServers: jsonData.mcp_servers || []
}
});
} }
break; break;
@@ -692,7 +760,7 @@ class ClaudeChatProvider {
const isError = content.is_error || false; const isError = content.is_error || false;
// Find the last tool use to get the tool name // Find the last tool use to get the tool name
const lastToolUse = this._currentConversation[this._currentConversation.length-1] const lastToolUse = this._currentConversation[this._currentConversation.length - 1]
const toolName = lastToolUse?.data?.toolName; const toolName = lastToolUse?.data?.toolName;
@@ -734,6 +802,8 @@ class ClaudeChatProvider {
return; return;
} }
this._isProcessing = false;
// Capture session ID from final result // Capture session ID from final result
if (jsonData.session_id) { if (jsonData.session_id) {
const isNewSession = !this._currentSessionId; const isNewSession = !this._currentSessionId;
@@ -762,7 +832,7 @@ class ClaudeChatProvider {
// Clear processing state // Clear processing state
this._postMessage({ this._postMessage({
type: 'setProcessing', type: 'setProcessing',
data: false data: { isProcessing: false }
}); });
// Update cumulative tracking // Update cumulative tracking
@@ -797,6 +867,22 @@ class ClaudeChatProvider {
private _newSession() { private _newSession() {
this._isProcessing = false
// Update UI state
this._postMessage({
type: 'setProcessing',
data: { isProcessing: false }
});
// Try graceful termination first
if (this._currentClaudeProcess) {
const processToKill = this._currentClaudeProcess;
this._currentClaudeProcess = undefined;
processToKill.kill('SIGTERM');
}
// Clear current session // Clear current session
this._currentSessionId = undefined; this._currentSessionId = undefined;
@@ -838,10 +924,13 @@ class ClaudeChatProvider {
} }
private _handleLoginRequired() { private _handleLoginRequired() {
this._isProcessing = false;
// Clear processing state // Clear processing state
this._postMessage({ this._postMessage({
type: 'setProcessing', type: 'setProcessing',
data: false data: { isProcessing: false }
}); });
// Show login required message // Show login required message
@@ -871,7 +960,7 @@ class ClaudeChatProvider {
'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 to Claude in the terminal, then come back to this chat to continue.`,
@@ -881,7 +970,7 @@ class ClaudeChatProvider {
private async _initializeBackupRepo(): Promise<void> { private async _initializeBackupRepo(): Promise<void> {
try { try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {return;} if (!workspaceFolder) { return; }
const storagePath = this._context.storageUri?.fsPath; const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) { if (!storagePath) {
@@ -914,7 +1003,7 @@ class ClaudeChatProvider {
private async _createBackupCommit(userMessage: string): Promise<void> { private async _createBackupCommit(userMessage: string): Promise<void> {
try { try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder || !this._backupRepoPath) {return;} if (!workspaceFolder || !this._backupRepoPath) { return; }
const workspacePath = workspaceFolder.uri.fsPath; const workspacePath = workspaceFolder.uri.fsPath;
const now = new Date(); const now = new Date();
@@ -1023,10 +1112,10 @@ class ClaudeChatProvider {
private async _initializeConversations(): Promise<void> { private async _initializeConversations(): Promise<void> {
try { try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {return;} if (!workspaceFolder) { return; }
const storagePath = this._context.storageUri?.fsPath; const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) {return;} if (!storagePath) { return; }
this._conversationsPath = path.join(storagePath, 'conversations'); this._conversationsPath = path.join(storagePath, 'conversations');
@@ -1045,7 +1134,7 @@ class ClaudeChatProvider {
private async _initializeMCPConfig(): Promise<void> { private async _initializeMCPConfig(): Promise<void> {
try { try {
const storagePath = this._context.storageUri?.fsPath; const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) {return;} if (!storagePath) { return; }
// Create MCP config directory // Create MCP config directory
const mcpConfigDir = path.join(storagePath, 'mcp'); const mcpConfigDir = path.join(storagePath, 'mcp');
@@ -1098,8 +1187,14 @@ class ClaudeChatProvider {
private async _initializePermissions(): Promise<void> { private async _initializePermissions(): Promise<void> {
try { try {
if (this._permissionWatcher) {
this._permissionWatcher.dispose();
this._permissionWatcher = undefined;
}
const storagePath = this._context.storageUri?.fsPath; const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) {return;} if (!storagePath) { return; }
// Create permission requests directory // Create permission requests directory
this._permissionRequestsPath = path.join(path.join(storagePath, 'permission-requests')); this._permissionRequestsPath = path.join(path.join(storagePath, 'permission-requests'));
@@ -1118,7 +1213,6 @@ class ClaudeChatProvider {
); );
this._permissionWatcher.onDidCreate(async (uri) => { this._permissionWatcher.onDidCreate(async (uri) => {
console.log("----file", uri)
// Only handle file scheme URIs, ignore vscode-userdata scheme // Only handle file scheme URIs, ignore vscode-userdata scheme
if (uri.scheme === 'file') { if (uri.scheme === 'file') {
await this._handlePermissionRequest(uri); await this._handlePermissionRequest(uri);
@@ -1170,7 +1264,7 @@ class ClaudeChatProvider {
} }
// Send permission request to the UI // Send permission request to the UI
this._postMessage({ this._sendAndSaveMessage({
type: 'permissionRequest', type: 'permissionRequest',
data: { data: {
id: request.id, id: request.id,
@@ -1527,10 +1621,10 @@ class ClaudeChatProvider {
} }
// Filter out internal servers before sending to UI // Filter out internal servers before sending to UI
const filteredServers = Object.fromEntries( const filteredServers = Object.fromEntries(
Object.entries(mcpConfig.mcpServers || {}).filter(([name]) => name !== 'claude-code-chat-permissions') Object.entries(mcpConfig.mcpServers || {}).filter(([name]) => name !== 'claude-code-chat-permissions')
); );
this._postMessage({ type: 'mcpServers', data: filteredServers }); this._postMessage({ type: 'mcpServers', data: filteredServers });
} catch (error) { } catch (error) {
console.error('Error loading MCP servers:', error); console.error('Error loading MCP servers:', error);
this._postMessage({ type: 'mcpServerError', data: { error: 'Failed to load MCP servers' } }); this._postMessage({ type: 'mcpServerError', data: { error: 'Failed to load MCP servers' } });
@@ -1626,7 +1720,7 @@ class ClaudeChatProvider {
private async _sendCustomSnippets(): Promise<void> { private async _sendCustomSnippets(): Promise<void> {
try { try {
const customSnippets = this._context.globalState.get<{[key: string]: any}>('customPromptSnippets', {}); const customSnippets = this._context.globalState.get<{ [key: string]: any }>('customPromptSnippets', {});
this._postMessage({ this._postMessage({
type: 'customSnippetsData', type: 'customSnippetsData',
data: customSnippets data: customSnippets
@@ -1642,7 +1736,7 @@ class ClaudeChatProvider {
private async _saveCustomSnippet(snippet: any): Promise<void> { private async _saveCustomSnippet(snippet: any): Promise<void> {
try { try {
const customSnippets = this._context.globalState.get<{[key: string]: any}>('customPromptSnippets', {}); const customSnippets = this._context.globalState.get<{ [key: string]: any }>('customPromptSnippets', {});
customSnippets[snippet.id] = snippet; customSnippets[snippet.id] = snippet;
await this._context.globalState.update('customPromptSnippets', customSnippets); await this._context.globalState.update('customPromptSnippets', customSnippets);
@@ -1664,7 +1758,7 @@ class ClaudeChatProvider {
private async _deleteCustomSnippet(snippetId: string): Promise<void> { private async _deleteCustomSnippet(snippetId: string): Promise<void> {
try { try {
const customSnippets = this._context.globalState.get<{[key: string]: any}>('customPromptSnippets', {}); const customSnippets = this._context.globalState.get<{ [key: string]: any }>('customPromptSnippets', {});
if (customSnippets[snippetId]) { if (customSnippets[snippetId]) {
delete customSnippets[snippetId]; delete customSnippets[snippetId];
@@ -1705,22 +1799,19 @@ class ClaudeChatProvider {
public getMCPConfigPath(): string | undefined { public getMCPConfigPath(): string | undefined {
const storagePath = this._context.storageUri?.fsPath; const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) {return undefined;} if (!storagePath) { return undefined; }
const configPath = path.join(storagePath, 'mcp', 'mcp-servers.json'); const configPath = path.join(storagePath, 'mcp', 'mcp-servers.json');
return path.join(configPath); return path.join(configPath);
} }
private _sendAndSaveMessage(message: { type: string, data: any }): void { private _sendAndSaveMessage(message: { type: string, data: any }): void {
// Initialize conversation if this is the first message // Initialize conversation if this is the first message
if (this._currentConversation.length === 0) { if (this._currentConversation.length === 0) {
this._conversationStartTime = new Date().toISOString(); this._conversationStartTime = new Date().toISOString();
} }
if (message.type === 'sessionInfo') {
message.data.sessionId;
}
// Send to UI using the helper method // Send to UI using the helper method
this._postMessage(message); this._postMessage(message);
@@ -1736,8 +1827,8 @@ class ClaudeChatProvider {
} }
private async _saveCurrentConversation(): Promise<void> { private async _saveCurrentConversation(): Promise<void> {
if (!this._conversationsPath || this._currentConversation.length === 0) {return;} if (!this._conversationsPath || this._currentConversation.length === 0) { return; }
if(!this._currentSessionId) {return;} if (!this._currentSessionId) { return; }
try { try {
// Create filename from first user message and timestamp // Create filename from first user message and timestamp
@@ -1756,7 +1847,7 @@ class ClaudeChatProvider {
const datePrefix = startTime.substring(0, 16).replace('T', '_').replace(/:/g, '-'); const datePrefix = startTime.substring(0, 16).replace('T', '_').replace(/:/g, '-');
const filename = `${datePrefix}_${cleanMessage}.json`; const filename = `${datePrefix}_${cleanMessage}.json`;
const conversationData = { const conversationData: ConversationData = {
sessionId: sessionId, sessionId: sessionId,
startTime: this._conversationStartTime, startTime: this._conversationStartTime,
endTime: new Date().toISOString(), endTime: new Date().toISOString(),
@@ -1823,8 +1914,8 @@ class ClaudeChatProvider {
// Check if term matches filename or any part of the path // Check if term matches filename or any part of the path
return fileName.includes(term) || return fileName.includes(term) ||
filePath.includes(term) || filePath.includes(term) ||
filePath.split('/').some(segment => segment.includes(term)); filePath.split('/').some(segment => segment.includes(term));
}); });
} }
@@ -1877,6 +1968,14 @@ class ClaudeChatProvider {
private _stopClaudeProcess(): void { private _stopClaudeProcess(): void {
console.log('Stop request received'); console.log('Stop request received');
this._isProcessing = false
// Update UI state
this._postMessage({
type: 'setProcessing',
data: { isProcessing: false }
});
if (this._currentClaudeProcess) { if (this._currentClaudeProcess) {
console.log('Terminating Claude process...'); console.log('Terminating Claude process...');
@@ -1894,12 +1993,6 @@ class ClaudeChatProvider {
// Clear process reference // Clear process reference
this._currentClaudeProcess = undefined; this._currentClaudeProcess = undefined;
// Update UI state
this._postMessage({
type: 'setProcessing',
data: false
});
this._postMessage({ this._postMessage({
type: 'clearLoading' type: 'clearLoading'
}); });
@@ -1916,7 +2009,7 @@ class ClaudeChatProvider {
} }
} }
private _updateConversationIndex(filename: string, conversationData: any): void { private _updateConversationIndex(filename: string, conversationData: ConversationData): void {
// Extract first and last user messages // Extract first and last user messages
const userMessages = conversationData.messages.filter((m: any) => m.messageType === 'userInput'); const userMessages = conversationData.messages.filter((m: any) => m.messageType === 'userInput');
const firstUserMessage = userMessages.length > 0 ? userMessages[0].data : 'No user message'; const firstUserMessage = userMessages.length > 0 ? userMessages[0].data : 'No user message';
@@ -1926,7 +2019,7 @@ class ClaudeChatProvider {
const indexEntry = { const indexEntry = {
filename: filename, filename: filename,
sessionId: conversationData.sessionId, sessionId: conversationData.sessionId,
startTime: conversationData.startTime, startTime: conversationData.startTime || '',
endTime: conversationData.endTime, endTime: conversationData.endTime,
messageCount: conversationData.messageCount, messageCount: conversationData.messageCount,
totalCost: conversationData.totalCost, totalCost: conversationData.totalCost,
@@ -1955,13 +2048,13 @@ class ClaudeChatProvider {
private async _loadConversationHistory(filename: string): Promise<void> { private async _loadConversationHistory(filename: string): Promise<void> {
console.log("_loadConversationHistory"); console.log("_loadConversationHistory");
if (!this._conversationsPath) {return;} if (!this._conversationsPath) { return; }
try { try {
const filePath = path.join(this._conversationsPath, filename); const filePath = path.join(this._conversationsPath, filename);
console.log("filePath", filePath); console.log("filePath", filePath);
let conversationData; let conversationData: ConversationData;
try { try {
const fileUri = vscode.Uri.file(filePath); const fileUri = vscode.Uri.file(filePath);
const content = await vscode.workspace.fs.readFile(fileUri); const content = await vscode.workspace.fs.readFile(fileUri);
@@ -1970,7 +2063,6 @@ class ClaudeChatProvider {
return; return;
} }
console.log("conversationData", conversationData);
// Load conversation into current state // Load conversation into current state
this._currentConversation = conversationData.messages || []; this._currentConversation = conversationData.messages || [];
this._conversationStartTime = conversationData.startTime; this._conversationStartTime = conversationData.startTime;
@@ -1985,13 +2077,33 @@ class ClaudeChatProvider {
type: 'sessionCleared' type: 'sessionCleared'
}); });
let requestStartTime: number
// Small delay to ensure messages are cleared before loading new ones // Small delay to ensure messages are cleared before loading new ones
setTimeout(() => { setTimeout(() => {
for (const message of this._currentConversation) { const messages = this._currentConversation;
for (let i = 0; i < messages.length; i++) {
const message = messages[i];
if(message.messageType === 'permissionRequest'){
const isLast = i === messages.length - 1;
if(!isLast){
continue;
}
}
this._postMessage({ this._postMessage({
type: message.messageType, type: message.messageType,
data: message.data data: message.data
}); });
if (message.messageType === 'userInput') {
try {
requestStartTime = new Date(message.timestamp).getTime()
} catch (e) {
console.log(e)
}
}
} }
// Send updated totals // Send updated totals
@@ -2005,6 +2117,13 @@ class ClaudeChatProvider {
} }
}); });
// Restore processing state if the conversation was saved while processing
if (this._isProcessing) {
this._postMessage({
type: 'setProcessing',
data: { isProcessing: this._isProcessing, requestStartTime }
});
}
// Send ready message after conversation is loaded // Send ready message after conversation is loaded
this._sendReadyMessage(); this._sendReadyMessage();
}, 50); }, 50);
@@ -2017,7 +2136,7 @@ class ClaudeChatProvider {
} }
private _getHtmlForWebview(): string { private _getHtmlForWebview(): string {
return html; return getHtml(vscode.env?.isTelemetryEnabled);
} }
private _sendCurrentSettings(): void { private _sendCurrentSettings(): void {
@@ -2037,15 +2156,43 @@ class ClaudeChatProvider {
}); });
} }
private async _enableYoloMode(): Promise<void> {
try {
// Update VS Code configuration to enable YOLO mode
const config = vscode.workspace.getConfiguration('claudeCodeChat');
// Clear any global setting and set workspace setting
await config.update('permissions.yoloMode', true, vscode.ConfigurationTarget.Workspace);
console.log('YOLO Mode enabled - all future permissions will be skipped');
// Send updated settings to UI
this._sendCurrentSettings();
} catch (error) {
console.error('Error enabling YOLO mode:', error);
}
}
private _saveInputText(text: string): void {
this._draftMessage = text || '';
}
private async _updateSettings(settings: { [key: string]: any }): Promise<void> { private async _updateSettings(settings: { [key: string]: any }): Promise<void> {
const config = vscode.workspace.getConfiguration('claudeCodeChat'); const config = vscode.workspace.getConfiguration('claudeCodeChat');
try { try {
for (const [key, value] of Object.entries(settings)) { for (const [key, value] of Object.entries(settings)) {
await config.update(key, value, vscode.ConfigurationTarget.Global); if (key === 'permissions.yoloMode') {
// YOLO mode is workspace-specific
await config.update(key, value, vscode.ConfigurationTarget.Workspace);
} else {
// Other settings are global (user-wide)
await config.update(key, value, vscode.ConfigurationTarget.Global);
}
} }
vscode.window.showInformationMessage('Settings updated successfully'); console.log('Settings updated:', settings);
} catch (error) { } catch (error) {
console.error('Failed to update settings:', error); console.error('Failed to update settings:', error);
vscode.window.showErrorMessage('Failed to update settings'); vscode.window.showErrorMessage('Failed to update settings');
@@ -2193,7 +2340,7 @@ class ClaudeChatProvider {
private async _createImageFile(imageData: string, imageType: string) { private async _createImageFile(imageData: string, imageType: string) {
try { try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {return;} if (!workspaceFolder) { return; }
// Extract base64 data from data URL // Extract base64 data from data URL
const base64Data = imageData.split(',')[1]; const base64Data = imageData.split(',')[1];

2964
src/script.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -86,8 +86,8 @@ const styles = `
/* Permission Request */ /* Permission Request */
.permission-request { .permission-request {
margin: 4px 12px 20px 12px; margin: 4px 12px 20px 12px;
background-color: var(--vscode-inputValidation-warningBackground); background-color: rgba(252, 188, 0, 0.1);
border: 1px solid var(--vscode-inputValidation-warningBorder); border: 1px solid rgba(252, 188, 0, 0.3);
border-radius: 8px; border-radius: 8px;
padding: 16px; padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
@@ -107,6 +107,86 @@ const styles = `
font-size: 16px; font-size: 16px;
} }
.permission-menu {
position: relative;
margin-left: auto;
}
.permission-menu-btn {
background: none;
border: none;
color: var(--vscode-descriptionForeground);
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
font-size: 16px;
font-weight: bold;
transition: all 0.2s ease;
line-height: 1;
}
.permission-menu-btn:hover {
background-color: var(--vscode-list-hoverBackground);
color: var(--vscode-foreground);
}
.permission-menu-dropdown {
position: absolute;
top: 100%;
right: 0;
background-color: var(--vscode-menu-background);
border: 1px solid var(--vscode-menu-border);
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
min-width: 220px;
padding: 4px 0;
margin-top: 4px;
}
.permission-menu-item {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 12px 16px;
background: none;
border: none;
width: 100%;
text-align: left;
cursor: pointer;
color: var(--vscode-foreground);
transition: background-color 0.2s ease;
}
.permission-menu-item:hover {
background-color: var(--vscode-list-hoverBackground);
}
.permission-menu-item .menu-icon {
font-size: 16px;
margin-top: 1px;
flex-shrink: 0;
}
.permission-menu-item .menu-content {
display: flex;
flex-direction: column;
gap: 2px;
}
.permission-menu-item .menu-title {
font-weight: 500;
font-size: 13px;
line-height: 1.2;
}
.permission-menu-item .menu-subtitle {
font-size: 11px;
color: var(--vscode-descriptionForeground);
opacity: 0.8;
line-height: 1.2;
}
.permission-content { .permission-content {
font-size: 13px; font-size: 13px;
line-height: 1.4; line-height: 1.4;
@@ -493,7 +573,7 @@ const styles = `
align-items: center; align-items: center;
gap: 6px; gap: 6px;
margin-top: 12px; margin-top: 12px;
opacity: 0.8; opacity: 1;
transition: opacity 0.2s ease; transition: opacity 0.2s ease;
} }
@@ -502,12 +582,12 @@ const styles = `
} }
.yolo-mode-section input[type="checkbox"] { .yolo-mode-section input[type="checkbox"] {
transform: scale(0.8); transform: scale(0.9);
margin: 0; margin: 0;
} }
.yolo-mode-section label { .yolo-mode-section label {
font-size: 10px; font-size: 12px;
color: var(--vscode-descriptionForeground); color: var(--vscode-descriptionForeground);
cursor: pointer; cursor: pointer;
font-weight: 400; font-weight: 400;
@@ -1270,13 +1350,14 @@ const styles = `
.input-field { .input-field {
width: 100%; width: 100%;
box-sizing: border-box;
background-color: transparent; background-color: transparent;
color: var(--vscode-input-foreground); color: var(--vscode-input-foreground);
border: none; border: none;
padding: 12px; padding: 12px;
outline: none; outline: none;
font-family: var(--vscode-editor-font-family); font-family: var(--vscode-editor-font-family);
min-height: 20px; min-height: 68px;
line-height: 1.4; line-height: 1.4;
overflow-y: hidden; overflow-y: hidden;
resize: none; resize: none;
@@ -1447,13 +1528,13 @@ const styles = `
.yolo-warning { .yolo-warning {
font-size: 12px; font-size: 12px;
color: var(--vscode-inputValidation-errorForeground); color: var(--vscode-foreground);
text-align: center; text-align: center;
font-weight: 500; font-weight: 500;
background-color: var(--vscode-inputValidation-errorBackground); background-color: rgba(255, 99, 71, 0.08);
border: 1px solid var(--vscode-inputValidation-errorBorder); border: 1px solid rgba(255, 99, 71, 0.2);
padding: 8px 12px; padding: 8px 12px;
margin: 4px 12px; margin: 4px 4px;
border-radius: 4px; border-radius: 4px;
animation: slideDown 0.3s ease; animation: slideDown 0.3s ease;
} }

2925
src/ui.ts

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@
// "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */
}, },
"exclude": [ "exclude": [
"mcp-permissions.js" "mcp-permissions.js",
"claude-code-chat-permissions-mcp"
] ]
} }