// // BashApprovalSheet.swift // oAI // // Approval UI for AI-requested bash commands // // SPDX-License-Identifier: AGPL-3.0-or-later // Copyright (C) 2026 Rune Olsen // // This file is part of oAI. // // oAI is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // oAI is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General // Public License for more details. // // You should have received a copy of the GNU Affero General Public // License along with oAI. If not, see . import SwiftUI struct BashApprovalSheet: View { let pending: MCPService.PendingBashCommand let onApprove: (_ forSession: Bool) -> Void let onDeny: () -> Void var body: some View { VStack(alignment: .leading, spacing: 20) { // Header HStack(spacing: 12) { Image(systemName: "terminal.fill") .font(.title2) .foregroundStyle(.orange) VStack(alignment: .leading, spacing: 2) { Text("Allow Shell Command?") .font(.system(size: 17, weight: .semibold)) Text("The AI wants to run the following command") .font(.system(size: 13)) .foregroundStyle(.secondary) } Spacer() } // Command display VStack(alignment: .leading, spacing: 6) { Text("COMMAND") .font(.system(size: 11, weight: .medium)) .foregroundStyle(.secondary) ScrollView { Text(pending.command) .font(.system(size: 13, design: .monospaced)) .foregroundStyle(.primary) .frame(maxWidth: .infinity, alignment: .leading) .textSelection(.enabled) .padding(12) } .frame(maxHeight: 180) .background(Color.secondary.opacity(0.08)) .clipShape(RoundedRectangle(cornerRadius: 8)) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(Color.secondary.opacity(0.2), lineWidth: 1) ) } // Working directory HStack(spacing: 6) { Image(systemName: "folder") .font(.system(size: 12)) .foregroundStyle(.secondary) Text("Working directory:") .font(.system(size: 12)) .foregroundStyle(.secondary) Text(pending.workingDirectory) .font(.system(size: 12, design: .monospaced)) .foregroundStyle(.secondary) } // Warning banner HStack(alignment: .top, spacing: 8) { Image(systemName: "exclamationmark.triangle.fill") .foregroundStyle(.orange) .font(.system(size: 13)) .padding(.top, 1) Text("Shell commands have full access to your system. Only approve commands you understand and trust.") .font(.system(size: 12)) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } .padding(10) .background(Color.orange.opacity(0.08)) .clipShape(RoundedRectangle(cornerRadius: 8)) // Buttons HStack(spacing: 8) { Button("Deny") { onDeny() } .buttonStyle(.bordered) .tint(.red) .keyboardShortcut(.escape, modifiers: []) Spacer() Button("Allow Once") { onApprove(false) } .buttonStyle(.bordered) .tint(.orange) Button("Allow for Session") { onApprove(true) } .buttonStyle(.borderedProminent) .tint(.orange) .keyboardShortcut(.return, modifiers: []) } } .padding(24) .frame(width: 480) } }