// // BashApprovalSheet.swift // oAI // // Approval UI for AI-requested bash commands // // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 // Copyright (C) 2026 Rune Olsen // // This file is part of oAI. // // oAI is licensed under the PolyForm Noncommercial License 1.0.0. // You may use, study, modify, and share it for any noncommercial // purpose. Commercial use — including selling oAI or any part of // it, standalone or bundled into another product or service — // requires a separate commercial license from the copyright holder. // // See the LICENSE file or // for // the full license text. For commercial licensing, contact Rune // Olsen via . 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) } }