mirror of
https://github.com/andrepimenta/claude-code-chat.git
synced 2026-01-28 20:37:38 +00:00
Always allow
This commit is contained in:
@@ -272,7 +272,7 @@ class ClaudeChatProvider {
|
||||
this._createImageFile(message.imageData, message.imageType);
|
||||
return;
|
||||
case 'permissionResponse':
|
||||
this._handlePermissionResponse(message.id, message.approved);
|
||||
this._handlePermissionResponse(message.id, message.approved, message.alwaysAllow);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1109,16 +1109,85 @@ class ClaudeChatProvider {
|
||||
});
|
||||
}
|
||||
|
||||
private _handlePermissionResponse(id: string, approved: boolean): void {
|
||||
private _handlePermissionResponse(id: string, approved: boolean, alwaysAllow?: boolean): void {
|
||||
if (this._pendingPermissionResolvers && this._pendingPermissionResolvers.has(id)) {
|
||||
const resolver = this._pendingPermissionResolvers.get(id);
|
||||
if (resolver) {
|
||||
resolver(approved);
|
||||
this._pendingPermissionResolvers.delete(id);
|
||||
|
||||
// Handle always allow setting
|
||||
if (alwaysAllow && approved) {
|
||||
void this._saveAlwaysAllowPermission(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _saveAlwaysAllowPermission(requestId: string): Promise<void> {
|
||||
try {
|
||||
// Read the original request to get tool name and input
|
||||
const storagePath = this._context.storageUri?.fsPath;
|
||||
if (!storagePath) return;
|
||||
|
||||
const requestFileUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', `${requestId}.request`));
|
||||
|
||||
let requestContent: Uint8Array;
|
||||
try {
|
||||
requestContent = await vscode.workspace.fs.readFile(requestFileUri);
|
||||
} catch {
|
||||
return; // Request file doesn't exist
|
||||
}
|
||||
|
||||
const request = JSON.parse(new TextDecoder().decode(requestContent));
|
||||
|
||||
// Load existing workspace permissions
|
||||
const permissionsUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', 'permissions.json'));
|
||||
let permissions: any = { alwaysAllow: {} };
|
||||
|
||||
try {
|
||||
const content = await vscode.workspace.fs.readFile(permissionsUri);
|
||||
permissions = JSON.parse(new TextDecoder().decode(content));
|
||||
} catch {
|
||||
// File doesn't exist yet, use default permissions
|
||||
}
|
||||
|
||||
// Add the new permission
|
||||
const toolName = request.tool;
|
||||
if (toolName === 'Bash' && request.input?.command) {
|
||||
// For Bash, store the specific command
|
||||
if (!permissions.alwaysAllow[toolName]) {
|
||||
permissions.alwaysAllow[toolName] = [];
|
||||
}
|
||||
if (Array.isArray(permissions.alwaysAllow[toolName])) {
|
||||
const command = request.input.command.trim();
|
||||
if (!permissions.alwaysAllow[toolName].includes(command)) {
|
||||
permissions.alwaysAllow[toolName].push(command);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For other tools, allow all instances
|
||||
permissions.alwaysAllow[toolName] = true;
|
||||
}
|
||||
|
||||
// Ensure permissions directory exists
|
||||
const permissionsDir = vscode.Uri.file(path.dirname(permissionsUri.fsPath));
|
||||
try {
|
||||
await vscode.workspace.fs.stat(permissionsDir);
|
||||
} catch {
|
||||
await vscode.workspace.fs.createDirectory(permissionsDir);
|
||||
}
|
||||
|
||||
// Save the permissions
|
||||
const permissionsContent = new TextEncoder().encode(JSON.stringify(permissions, null, 2));
|
||||
await vscode.workspace.fs.writeFile(permissionsUri, permissionsContent);
|
||||
|
||||
console.log(`Saved always-allow permission for ${toolName}`);
|
||||
} catch (error) {
|
||||
console.error('Error saving always-allow permission:', error);
|
||||
}
|
||||
}
|
||||
|
||||
public getMCPConfigPath(): string | undefined {
|
||||
const storagePath = this._context.storageUri?.fsPath;
|
||||
if (!storagePath) {return undefined;}
|
||||
|
||||
@@ -16,6 +16,61 @@ if (!PERMISSIONS_PATH) {
|
||||
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('*')) {
|
||||
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)}`;
|
||||
}
|
||||
@@ -26,6 +81,12 @@ async function requestPermission(tool_name: string, input: any): Promise<{approv
|
||||
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`);
|
||||
|
||||
@@ -113,6 +113,45 @@ const styles = `
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
.permission-command {
|
||||
margin: 8px 0;
|
||||
padding: 8px;
|
||||
background-color: var(--vscode-editor-background);
|
||||
border: 1px solid var(--vscode-panel-border);
|
||||
border-radius: 4px;
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.permission-command code {
|
||||
color: var(--vscode-editor-foreground);
|
||||
}
|
||||
|
||||
.permission-options {
|
||||
margin: 12px 0;
|
||||
padding: 8px 0;
|
||||
border-top: 1px solid var(--vscode-panel-border);
|
||||
}
|
||||
|
||||
.permission-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.permission-checkbox input[type="checkbox"] {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
color: var(--vscode-foreground);
|
||||
cursor: pointer;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.permission-tool {
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
background-color: var(--vscode-editor-background);
|
||||
|
||||
29
src/ui.ts
29
src/ui.ts
@@ -1973,6 +1973,10 @@ const html = `<!DOCTYPE html>
|
||||
|
||||
const toolName = data.tool || 'Unknown Tool';
|
||||
|
||||
// Create command display for Bash tool
|
||||
const commandDisplay = toolName === 'Bash' && data.input?.command ?
|
||||
\`<div class="permission-command"><code>\${data.input.command}</code></div>\` : '';
|
||||
|
||||
messageDiv.innerHTML = \`
|
||||
<div class="permission-header">
|
||||
<span class="icon">🔐</span>
|
||||
@@ -1980,6 +1984,13 @@ const html = `<!DOCTYPE html>
|
||||
</div>
|
||||
<div class="permission-content">
|
||||
<p>Allow <strong>\${toolName}</strong> to execute the tool call above?</p>
|
||||
\${commandDisplay}
|
||||
<div class="permission-options">
|
||||
<label class="permission-checkbox">
|
||||
<input type="checkbox" id="always-allow-\${data.id}" class="always-allow-checkbox">
|
||||
<span class="checkbox-label">Always allow \${toolName === 'Bash' ? 'this command' : 'this tool'} in this workspace</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="permission-buttons">
|
||||
<button class="btn allow" onclick="respondToPermission('\${data.id}', true)">Allow</button>
|
||||
<button class="btn deny" onclick="respondToPermission('\${data.id}', false)">Deny</button>
|
||||
@@ -1992,11 +2003,16 @@ const html = `<!DOCTYPE html>
|
||||
}
|
||||
|
||||
function respondToPermission(id, approved) {
|
||||
// Check if always-allow is checked
|
||||
const alwaysAllowCheckbox = document.getElementById(\`always-allow-\${id}\`);
|
||||
const alwaysAllow = alwaysAllowCheckbox?.checked || false;
|
||||
|
||||
// Send response back to extension
|
||||
vscode.postMessage({
|
||||
type: 'permissionResponse',
|
||||
id: id,
|
||||
approved: approved
|
||||
approved: approved,
|
||||
alwaysAllow: alwaysAllow
|
||||
});
|
||||
|
||||
// Update the UI to show the decision
|
||||
@@ -2004,12 +2020,19 @@ const html = `<!DOCTYPE html>
|
||||
if (permissionMsg) {
|
||||
const buttons = permissionMsg.querySelector('.permission-buttons');
|
||||
const permissionContent = permissionMsg.querySelector('.permission-content');
|
||||
const decision = approved ? 'You allowed this' : 'You denied this';
|
||||
const options = permissionMsg.querySelector('.permission-options');
|
||||
let decision = approved ? 'You allowed this' : 'You denied this';
|
||||
|
||||
if (alwaysAllow && approved) {
|
||||
decision = 'You allowed this and set it to always allow';
|
||||
}
|
||||
|
||||
const emoji = approved ? '✅' : '❌';
|
||||
const decisionClass = approved ? 'allowed' : 'denied';
|
||||
|
||||
// Hide buttons
|
||||
// Hide buttons and options
|
||||
buttons.style.display = 'none';
|
||||
options.style.display = 'none';
|
||||
|
||||
// Add decision div to permission-content
|
||||
const decisionDiv = document.createElement('div');
|
||||
|
||||
Reference in New Issue
Block a user