Files
oai-swift/oAI/Views/Screens/BashApprovalSheet.swift
T
runeandClaude Sonnet 5 bd686873c4 Update commercial licensing contact URL to oai.pm
Consolidates the mac.oai.pm subdomain references (introduced in the
PolyForm Noncommercial relicense) to the root oai.pm domain, across
the LICENSE file, README, and all Swift source file headers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 11:33:53 +02:00

127 lines
4.4 KiB
Swift

//
// 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
// <https://polyformproject.org/licenses/noncommercial/1.0.0> for
// the full license text. For commercial licensing, contact Rune
// Olsen via <https://oai.pm>.
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)
}
}