spb/zyquo-agent Public MIT
The autonomous agent that actually operates your Mac — plans, runs real commands, verifies its own work.
Swift 94.7%
Shell 4.1%
Python 0.7%
Makefile 0.5%
1//2// ProvidersSettingsTab.swift3// Zyquo Agent4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// Settings › Providers & Keys — ported from Zyquo Cloud's vault UI: one row9// per provider (glyph, name, status dot, masked key field, Test, delete),10// extended with the environment-variable fallback indicator (keys resolved11// from the environment take precedence over the vault at run time).12//1314import SwiftUI1516struct ProvidersSettingsTab: View {17 @EnvironmentObject private var vault: KeyVaultStore18 @EnvironmentObject private var catalog: ModelCatalog19 @State private var draftKeys: [ProviderID: String] = [:]2021 var body: some View {22 ScrollView {23 VStack(spacing: ZyquoSpacing.xs) {24 Text("Keys are stored in the encrypted vault shared with Zyquo Cloud (AES-256-GCM, bound to this Mac — never the Keychain). Environment variables, when set, take precedence at run time.")25 .font(ZyquoFont.caption)26 .foregroundStyle(ZyquoColor.textTertiary)27 .frame(maxWidth: .infinity, alignment: .leading)28 ForEach(ProviderID.builtIn) { provider in29 providerRow(provider)30 if provider != ProviderID.builtIn.last { ZyquoHairline() }31 }32 }33 .padding(ZyquoMetrics.contentInset)34 }35 .background(ZyquoColor.background)36 }3738 private func providerRow(_ provider: ProviderID) -> some View {39 HStack(spacing: ZyquoSpacing.sm) {40 Image(systemName: provider.symbolName)41 .font(.system(size: 14))42 .foregroundStyle(ZyquoColor.accent)43 .frame(width: 22)44 VStack(alignment: .leading, spacing: 1) {45 HStack(spacing: ZyquoSpacing.xxs) {46 Text(provider.displayName)47 .font(ZyquoFont.bodyEmphasis(size: 13))48 .foregroundStyle(ZyquoColor.textPrimary)49 statusIndicator(provider)50 if let envName = activeEnvironmentKey(provider) {51 ZyquoBadge(text: "env: \(envName)", color: ZyquoColor.success)52 .help("An environment variable provides this key; it takes precedence over the vault.")53 }54 }55 statusDetail(provider)56 }57 Spacer()58 keyField(provider)59 testButton(provider)60 if vault.hasKey(for: provider) {61 Button {62 vault.deleteKey(for: provider)63 } label: {64 Image(systemName: "trash")65 .font(.system(size: 11))66 .foregroundStyle(ZyquoColor.danger)67 }68 .buttonStyle(.plain)69 .help("Delete key from the vault")70 }71 }72 .padding(.vertical, ZyquoSpacing.xxs)73 }7475 /// The first set environment variable providing a key for this provider.76 private func activeEnvironmentKey(_ provider: ProviderID) -> String? {77 let environment = ProcessInfo.processInfo.environment78 return AgentCLI.environmentKeyNames(for: provider).first {79 !(environment[$0] ?? "").isEmpty80 }81 }8283 @ViewBuilder84 private func statusIndicator(_ provider: ProviderID) -> some View {85 switch vault.statuses[provider] ?? .unset {86 case .unset: StatusDot(status: .unset)87 case .saved: StatusDot(status: .unset).overlay(Circle().strokeBorder(ZyquoColor.textSecondary, lineWidth: 1))88 case .testing: ProgressView().controlSize(.mini)89 case .verified: StatusDot(status: .verified)90 case .failed: StatusDot(status: .failed)91 }92 }9394 @ViewBuilder95 private func statusDetail(_ provider: ProviderID) -> some View {96 switch vault.statuses[provider] ?? .unset {97 case .verified(let latency):98 Text(String(format: "Verified · %.0f ms", latency * 1000))99 .font(ZyquoFont.caption)100 .foregroundStyle(ZyquoColor.success)101 case .failed(let message):102 Text(message)103 .font(ZyquoFont.caption)104 .foregroundStyle(ZyquoColor.danger)105 .lineLimit(1)106 .help(message)107 case .saved:108 Text(vault.redactedKeys[provider] ?? "")109 .font(ZyquoFont.caption)110 .foregroundStyle(ZyquoColor.textTertiary)111 default:112 Text("No key")113 .font(ZyquoFont.caption)114 .foregroundStyle(ZyquoColor.textTertiary)115 }116 }117118 private func keyField(_ provider: ProviderID) -> some View {119 SecureField(120 vault.hasKey(for: provider) ? (vault.redactedKeys[provider] ?? "") : "API key",121 text: Binding(122 get: { draftKeys[provider] ?? "" },123 set: { draftKeys[provider] = $0 }124 )125 )126 .textFieldStyle(.roundedBorder)127 .font(ZyquoFont.code(size: 11))128 .frame(width: 200)129 .onSubmit { saveDraft(provider) }130 }131132 private func testButton(_ provider: ProviderID) -> some View {133 Button("Test") {134 saveDraft(provider)135 Task { await vault.testKey(for: provider, catalog: catalog) }136 }137 .controlSize(.small)138 .disabled(!vault.hasKey(for: provider) && (draftKeys[provider] ?? "").isEmpty)139 }140141 private func saveDraft(_ provider: ProviderID) {142 if let draft = draftKeys[provider], !draft.trimmingCharacters(in: .whitespaces).isEmpty {143 vault.setKey(draft, for: provider)144 draftKeys[provider] = ""145 }146 }147}148