mirror of
https://github.com/andrepimenta/claude-code-chat.git
synced 2025-12-08 18:49:43 +00:00
Settings UI for permissions
This commit is contained in:
@@ -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;}
|
||||
|
||||
119
src/ui-styles.ts
119
src/ui-styles.ts
@@ -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;
|
||||
|
||||
77
src/ui.ts
77
src/ui.ts
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user