Settings UI for permissions

This commit is contained in:
andrepimenta
2025-07-08 23:57:38 +01:00
parent 0b98538903
commit 3ec983188a
3 changed files with 282 additions and 0 deletions

View File

@@ -274,6 +274,12 @@ class ClaudeChatProvider {
case 'permissionResponse':
this._handlePermissionResponse(message.id, message.approved, message.alwaysAllow);
return;
case 'getPermissions':
this._sendPermissions();
return;
case 'removePermission':
this._removePermission(message.toolName, message.command);
return;
}
}
@@ -1290,6 +1296,86 @@ class ClaudeChatProvider {
return command;
}
private async _sendPermissions(): Promise<void> {
try {
const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) {
this._postMessage({
type: 'permissionsData',
data: { alwaysAllow: {} }
});
return;
}
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 or can't be read, use default permissions
}
this._postMessage({
type: 'permissionsData',
data: permissions
});
} catch (error) {
console.error('Error sending permissions:', error);
this._postMessage({
type: 'permissionsData',
data: { alwaysAllow: {} }
});
}
}
private async _removePermission(toolName: string, command: string | null): Promise<void> {
try {
const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) return;
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 or can't be read, nothing to remove
return;
}
// Remove the permission
if (command === null) {
// Remove entire tool permission
delete permissions.alwaysAllow[toolName];
} else {
// Remove specific command from tool permissions
if (Array.isArray(permissions.alwaysAllow[toolName])) {
permissions.alwaysAllow[toolName] = permissions.alwaysAllow[toolName].filter(
(cmd: string) => cmd !== command
);
// If no commands left, remove the tool entirely
if (permissions.alwaysAllow[toolName].length === 0) {
delete permissions.alwaysAllow[toolName];
}
}
}
// Save updated permissions
const permissionsContent = new TextEncoder().encode(JSON.stringify(permissions, null, 2));
await vscode.workspace.fs.writeFile(permissionsUri, permissionsContent);
// Send updated permissions to UI
this._sendPermissions();
console.log(`Removed permission for ${toolName}${command ? ` command: ${command}` : ''}`);
} catch (error) {
console.error('Error removing permission:', error);
}
}
public getMCPConfigPath(): string | undefined {
const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) {return undefined;}

View File

@@ -241,6 +241,125 @@ const styles = `
background-color: var(--vscode-inputValidation-errorBackground);
}
/* Permissions Management */
.permissions-list {
max-height: 300px;
overflow-y: auto;
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
background-color: var(--vscode-input-background);
margin-top: 8px;
}
.permission-item {
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 6px;
padding-right: 6px;
border-bottom: 1px solid var(--vscode-panel-border);
transition: background-color 0.2s ease;
min-height: 32px;
}
.permission-item:hover {
background-color: var(--vscode-list-hoverBackground);
}
.permission-item:last-child {
border-bottom: none;
}
.permission-info {
display: flex;
align-items: center;
gap: 8px;
flex-grow: 1;
min-width: 0;
}
.permission-tool {
background-color: var(--vscode-badge-background);
color: var(--vscode-badge-foreground);
padding: 3px 6px;
border-radius: 3px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
flex-shrink: 0;
height: 18px;
display: inline-flex;
align-items: center;
line-height: 1;
}
.permission-command {
font-size: 12px;
color: var(--vscode-foreground);
flex-grow: 1;
}
.permission-command code {
background-color: var(--vscode-textCodeBlock-background);
padding: 3px 6px;
border-radius: 3px;
font-family: var(--vscode-editor-font-family);
color: var(--vscode-textLink-foreground);
font-size: 11px;
height: 18px;
display: inline-flex;
align-items: center;
line-height: 1;
}
.permission-desc {
color: var(--vscode-descriptionForeground);
font-size: 11px;
font-style: italic;
flex-grow: 1;
height: 18px;
display: inline-flex;
align-items: center;
line-height: 1;
}
.permission-remove-btn {
background-color: transparent;
color: var(--vscode-descriptionForeground);
border: none;
padding: 4px 8px;
border-radius: 3px;
cursor: pointer;
font-size: 10px;
transition: all 0.2s ease;
font-weight: 500;
flex-shrink: 0;
opacity: 0.7;
}
.permission-remove-btn:hover {
background-color: rgba(231, 76, 60, 0.1);
color: var(--vscode-errorForeground);
opacity: 1;
}
.permissions-empty {
padding: 16px;
text-align: center;
color: var(--vscode-descriptionForeground);
font-style: italic;
font-size: 13px;
}
.permissions-empty::before {
content: "🔒";
display: block;
font-size: 16px;
margin-bottom: 8px;
opacity: 0.5;
}
/* WSL Alert */
.wsl-alert {
margin: 8px 12px;

View File

@@ -248,6 +248,20 @@ const html = `<!DOCTYPE html>
</div>
</div>
<h3 style="margin-top: 24px; margin-bottom: 16px; font-size: 14px; font-weight: 600;">Permissions</h3>
<div>
<p style="font-size: 11px; color: var(--vscode-descriptionForeground); margin: 0;">
Manage commands and tools that are automatically allowed without asking for permission.
</p>
</div>
<div class="settings-group">
<div id="permissionsList" class="permissions-list">
<div class="permissions-loading" style="text-align: center; padding: 20px; color: var(--vscode-descriptionForeground);">
Loading permissions...
</div>
</div>
</div>
<h3 style="margin-top: 24px; margin-bottom: 16px; font-size: 14px; font-weight: 600;">MCP Configuration (coming soon)</h3>
<div>
<p style="font-size: 11px; color: var(--vscode-descriptionForeground); margin: 0;">
@@ -2434,6 +2448,10 @@ const html = `<!DOCTYPE html>
vscode.postMessage({
type: 'getSettings'
});
// Request current permissions
vscode.postMessage({
type: 'getPermissions'
});
settingsModal.style.display = 'flex';
} else {
hideSettingsModal();
@@ -2467,6 +2485,60 @@ const html = `<!DOCTYPE html>
});
}
// Permissions management functions
function renderPermissions(permissions) {
const permissionsList = document.getElementById('permissionsList');
if (!permissions || !permissions.alwaysAllow || Object.keys(permissions.alwaysAllow).length === 0) {
permissionsList.innerHTML = \`
<div class="permissions-empty">
No always-allow permissions set
</div>
\`;
return;
}
let html = '';
for (const [toolName, permission] of Object.entries(permissions.alwaysAllow)) {
if (permission === true) {
// Tool is always allowed
html += \`
<div class="permission-item">
<div class="permission-info">
<span class="permission-tool">\${toolName}</span>
<span class="permission-desc">All</span>
</div>
<button class="permission-remove-btn" onclick="removePermission('\${toolName}', null)">Remove</button>
</div>
\`;
} else if (Array.isArray(permission)) {
// Tool has specific commands/patterns
for (const command of permission) {
const displayCommand = command.replace(' *', ''); // Remove asterisk for display
html += \`
<div class="permission-item">
<div class="permission-info">
<span class="permission-tool">\${toolName}</span>
<span class="permission-command"><code>\${displayCommand}</code></span>
</div>
<button class="permission-remove-btn" onclick="removePermission('\${toolName}', '\${escapeHtml(command)}')">Remove</button>
</div>
\`;
}
}
}
permissionsList.innerHTML = html;
}
function removePermission(toolName, command) {
vscode.postMessage({
type: 'removePermission',
toolName: toolName,
command: command
});
}
// Close settings modal when clicking outside
document.getElementById('settingsModal').addEventListener('click', (e) => {
@@ -2537,6 +2609,11 @@ const html = `<!DOCTYPE html>
}, 1000);
}
}
if (message.type === 'permissionsData') {
// Update permissions UI
renderPermissions(message.data);
}
});
</script>