Switches the project from AGPL to a source-available license that restricts commercial use — selling oAI or any part of it, standalone or bundled into another product/service, now requires a separate commercial license from the copyright holder. Noncommercial use, study, modification, and sharing remain fully permitted. Updates: LICENSE (canonical PolyForm Noncommercial 1.0.0 text + commercial licensing contact note), SPDX headers and file-header boilerplate across all Swift source files, the in-app About dialog's license link (+ its localization catalog entry), README.md and DEVELOPMENT.md license sections. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
127 lines
4.4 KiB
Swift
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://mac.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)
|
|
}
|
|
}
|