native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160import ComposableArchitectureimport ProwlCLISharedimport SwiftUI
/// Settings → Agents → CLI & Skills: install/status for the bundled `prowl`/// CLI, the socket it reaches the app through, and the bundled agent skills/// (`AgentSkillsFeature`). Installation behavior stays in the reducers; this view/// only presents it.struct CommandLineToolSettingsView: View { @Bindable var store: StoreOf<SettingsFeature>
var body: some View { Form { Section("Installation") { VStack(alignment: .leading, spacing: 8) { HStack(spacing: 6) { switch store.cliInstallStatus { case .installed(let path): Image(systemName: "checkmark.circle.fill") .foregroundStyle(.green) .accessibilityLabel("Installed") Text("Installed at \(path)") case .installedDifferentSource(let path, _): Image(systemName: "exclamationmark.triangle.fill") .foregroundStyle(.yellow) .accessibilityLabel("Different version") Text("A different version exists at \(path)") case .broken(let path, _): Image(systemName: "exclamationmark.triangle.fill") .foregroundStyle(.yellow) .accessibilityLabel("Broken link") Text("A broken link exists at \(path)") case .notInstalled: Image(systemName: "xmark.circle") .foregroundStyle(.secondary) .accessibilityLabel("Not installed") Text("Not installed") } } .font(.callout)
Text("Install the prowl command to let terminals and coding agents control Prowl.") .foregroundStyle(.secondary) .font(.callout)
HStack(spacing: 8) { switch store.cliInstallStatus { case .notInstalled: Button("Install") { store.send(.installCLIButtonTapped()) } .help("Install prowl command line tool to /usr/local/bin") .buttonStyle(.bordered) case .installed: Button("Uninstall") { store.send(.uninstallCLIButtonTapped) } .help("Remove prowl command line tool from /usr/local/bin") .buttonStyle(.bordered) case .installedDifferentSource: Button("Reinstall") { store.send(.installCLIButtonTapped()) } .help("Replace the existing prowl command with the version bundled in this app") .buttonStyle(.bordered) case .broken: Button("Repair") { store.send(.installCLIButtonTapped()) } .help("Replace the broken prowl link with the version bundled in this app") .buttonStyle(.bordered) } } } .frame(maxWidth: .infinity, alignment: .leading) .onAppear { store.send(.refreshCLIInstallStatus) store.send(.refreshCLIServiceStatus) } }
Section("Connection") { LabeledContent("Socket") { Text(socketPath) .font(.callout.monospaced()) .foregroundStyle(.secondary) .textSelection(.enabled) .lineLimit(1) .truncationMode(.middle) }
LabeledContent("Status") { HStack(spacing: 6) { connectionStatusIcon Text(connectionStatusText) } .font(.callout) }
if let failure = store.cliServiceStatus.failureDescription { Text(failure) .foregroundStyle(.secondary) .font(.callout) .fixedSize(horizontal: false, vertical: true) }
Text( """ prowl reaches the running app through this local Unix socket. \ Set PROWL_CLI_SOCKET for both Prowl and prowl to use a different path. """ ) .foregroundStyle(.secondary) .font(.callout) }
if let agentSkillsStore = store.scope(state: \.agentSkills, action: \.agentSkills) { AgentSkillsSectionView(store: agentSkillsStore) } } .formStyle(.grouped) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) }
/// The path the server actually bound (or failed to bind), falling back to the default /// before the first status arrives. private var socketPath: String { switch store.cliServiceStatus { case .listening(let path), .failed(_, let path): path case .stopped: ProwlSocket.defaultPath } }
@ViewBuilder private var connectionStatusIcon: some View { switch store.cliServiceStatus { case .listening: Image(systemName: "checkmark.circle.fill") .foregroundStyle(.green) .accessibilityLabel("Listening") case .failed: Image(systemName: "exclamationmark.triangle.fill") .foregroundStyle(.yellow) .accessibilityLabel("Not listening") case .stopped: Image(systemName: "xmark.circle") .foregroundStyle(.secondary) .accessibilityLabel("Not running") } }
private var connectionStatusText: String { switch store.cliServiceStatus { case .listening: String(localized: "Listening") case .failed: String(localized: "Not listening") case .stopped: String(localized: "Not running") } }}