Working in windows

This commit is contained in:
andrepimenta
2025-07-12 13:24:25 +01:00
parent 5c33bc94c1
commit 6e9893e3f3
8 changed files with 13996 additions and 951 deletions

View File

@@ -436,7 +436,7 @@ class ClaudeChatProvider {
// Add MCP configuration for permissions
const mcpConfigPath = this.getMCPConfigPath();
if (mcpConfigPath) {
args.push('--mcp-config', mcpConfigPath);
args.push('--mcp-config', this.convertToWSLPath(mcpConfigPath));
args.push('--allowedTools', 'mcp__claude-code-chat-permissions__approval_prompt');
args.push('--permission-prompt-tool', 'mcp__claude-code-chat-permissions__approval_prompt');
}
@@ -465,9 +465,13 @@ class ClaudeChatProvider {
let claudeProcess: cp.ChildProcess;
if (wslEnabled) {
// Use WSL
// Use WSL with bash -ic for proper environment loading
console.log('Using WSL configuration:', { wslDistro, nodePath, claudePath });
claudeProcess = cp.spawn('wsl', ['-d', wslDistro, nodePath, '--no-warnings', '--enable-source-maps', claudePath, ...args], {
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], {
cwd: cwd,
stdio: ['pipe', 'pipe', 'pipe'],
env: {
@@ -797,6 +801,9 @@ class ClaudeChatProvider {
}
public newSessionOnConfigChange() {
// Reinitialize MCP config with new WSL paths
this._initializeMCPConfig();
// Start a new session due to configuration change
this._newSession();
@@ -1034,8 +1041,8 @@ class ClaudeChatProvider {
// Create or update mcp-servers.json with permissions server, preserving existing servers
const mcpConfigPath = path.join(mcpConfigDir, 'mcp-servers.json');
const mcpPermissionsPath = path.join(this._extensionUri.fsPath, 'out', 'permissions', 'mcp-permissions.js');
const permissionRequestsPath = path.join(storagePath, 'permission-requests');
const mcpPermissionsPath = this.convertToWSLPath(path.join(this._extensionUri.fsPath, 'mcp-permissions.js'));
const permissionRequestsPath = this.convertToWSLPath(path.join(storagePath, 'permission-requests'));
// Load existing config or create new one
let mcpConfig: any = { mcpServers: {} };
@@ -1078,7 +1085,7 @@ class ClaudeChatProvider {
if (!storagePath) {return;}
// Create permission requests directory
this._permissionRequestsPath = path.join(storagePath, 'permission-requests');
this._permissionRequestsPath = path.join(path.join(storagePath, 'permission-requests'));
try {
await vscode.workspace.fs.stat(vscode.Uri.file(this._permissionRequestsPath));
} catch {
@@ -1086,12 +1093,15 @@ class ClaudeChatProvider {
console.log(`Created permission requests directory at: ${this._permissionRequestsPath}`);
}
console.log("DIRECTORY-----", this._permissionRequestsPath)
// Set up file watcher for *.request files
this._permissionWatcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(this._permissionRequestsPath, '*.request')
);
this._permissionWatcher.onDidCreate(async (uri) => {
console.log("----file", uri)
// Only handle file scheme URIs, ignore vscode-userdata scheme
if (uri.scheme === 'file') {
await this._handlePermissionRequest(uri);
@@ -1597,10 +1607,24 @@ class ClaudeChatProvider {
}
}
private convertToWSLPath(windowsPath: string): string {
const config = vscode.workspace.getConfiguration('claudeCodeChat');
const wslEnabled = config.get<boolean>('wsl.enabled', false);
if (wslEnabled && windowsPath.match(/^[a-zA-Z]:/)) {
// Convert C:\Users\... to /mnt/c/Users/...
return windowsPath.replace(/^([a-zA-Z]):/, '/mnt/$1').toLowerCase().replace(/\\/g, '/');
}
return windowsPath;
}
public getMCPConfigPath(): string | undefined {
const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) {return undefined;}
return path.join(storagePath, 'mcp', 'mcp-servers.json');
const configPath = path.join(storagePath, 'mcp', 'mcp-servers.json');
return path.join(configPath);
}
private _sendAndSaveMessage(message: { type: string, data: any }): void {

View File

@@ -1,212 +0,0 @@
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);
});

View File

@@ -1,10 +0,0 @@
{
"mcpServers": {
"permissions": {
"command": "node",
"args": [
"./out/permissions/mcp-permissions.js"
]
}
}
}