diff --git a/Info.plist b/Info.plist index af1e52e..b9e4465 100644 --- a/Info.plist +++ b/Info.plist @@ -2,9 +2,7 @@ - LSUIElement - - NSFaceIDUsageDescription - nbox uses Touch ID to protect your secrets. + LSApplicationCategoryType + public.app-category.utilities diff --git a/NBox.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/NBox.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/NBox.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/NBox.xcodeproj/xcuserdata/jaspermiddendorp.xcuserdatad/xcschemes/xcschememanagement.plist b/NBox.xcodeproj/xcuserdata/jaspermiddendorp.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 092d39c..0000000 --- a/NBox.xcodeproj/xcuserdata/jaspermiddendorp.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - SchemeUserState - - NBox.xcscheme_^#shared#^_ - - orderHint - 0 - - - - diff --git a/NBox/AppState.swift b/NBox/AppState.swift index 6450646..ac37d7d 100644 --- a/NBox/AppState.swift +++ b/NBox/AppState.swift @@ -69,7 +69,7 @@ class AppState { } func revealSecret(account: String) -> Result { - KeychainManager().get(account: account) + KeychainManager().get(account: account, reason: "Reveal value for \"\(account)\"") } private var pendingDeletes: [String: DispatchWorkItem] = [:] @@ -77,7 +77,7 @@ class AppState { /// Encrypts secret to a temp file, copies a `source <(openssl ...)` command to clipboard. /// File auto-deletes after 60s. Clipboard auto-clears after 30s. func shareSecret(account: String) -> Result { - switch KeychainManager().get(account: account) { + switch KeychainManager().get(account: account, reason: "Share \"\(account)\" (encrypted to temp file)") { case .success(let value): // Derive env var name from the last path component let envVar = account.split(separator: "/").last.map(String.init) ?? account diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/Contents.json b/NBox/Assets.xcassets/AppIcon.appiconset/Contents.json index 3f00db4..64dc11e 100644 --- a/NBox/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/NBox/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,51 +1,61 @@ { "images" : [ { + "filename" : "icon_16x16.png", "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { + "filename" : "icon_16x16@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { + "filename" : "icon_32x32.png", "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { + "filename" : "icon_32x32@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { + "filename" : "icon_128x128.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { + "filename" : "icon_128x128@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { + "filename" : "icon_256x256.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { + "filename" : "icon_256x256@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { + "filename" : "icon_512x512.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { + "filename" : "icon_512x512@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/icon_128x128.png b/NBox/Assets.xcassets/AppIcon.appiconset/icon_128x128.png new file mode 100644 index 0000000..94a1d8c Binary files /dev/null and b/NBox/Assets.xcassets/AppIcon.appiconset/icon_128x128.png differ diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/NBox/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png new file mode 100644 index 0000000..0630bb9 Binary files /dev/null and b/NBox/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png differ diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/icon_16x16.png b/NBox/Assets.xcassets/AppIcon.appiconset/icon_16x16.png new file mode 100644 index 0000000..c89af1f Binary files /dev/null and b/NBox/Assets.xcassets/AppIcon.appiconset/icon_16x16.png differ diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/NBox/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png new file mode 100644 index 0000000..81b9090 Binary files /dev/null and b/NBox/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png differ diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/icon_256x256.png b/NBox/Assets.xcassets/AppIcon.appiconset/icon_256x256.png new file mode 100644 index 0000000..0630bb9 Binary files /dev/null and b/NBox/Assets.xcassets/AppIcon.appiconset/icon_256x256.png differ diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/NBox/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png new file mode 100644 index 0000000..3b55a80 Binary files /dev/null and b/NBox/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png differ diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/icon_32x32.png b/NBox/Assets.xcassets/AppIcon.appiconset/icon_32x32.png new file mode 100644 index 0000000..81b9090 Binary files /dev/null and b/NBox/Assets.xcassets/AppIcon.appiconset/icon_32x32.png differ diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/NBox/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png new file mode 100644 index 0000000..1074000 Binary files /dev/null and b/NBox/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png differ diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/icon_512x512.png b/NBox/Assets.xcassets/AppIcon.appiconset/icon_512x512.png new file mode 100644 index 0000000..3b55a80 Binary files /dev/null and b/NBox/Assets.xcassets/AppIcon.appiconset/icon_512x512.png differ diff --git a/NBox/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/NBox/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png new file mode 100644 index 0000000..c8affbe Binary files /dev/null and b/NBox/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png differ diff --git a/NBox/ContentView.swift b/NBox/ContentView.swift deleted file mode 100644 index 837d544..0000000 --- a/NBox/ContentView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ContentView.swift -// NBox -// -// Created by Jasper Middendorp on 06/03/2026. -// - -import SwiftUI - -struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - } - .padding() - } -} - -#Preview { - ContentView() -} diff --git a/NBox/HotkeyManager.swift b/NBox/HotkeyManager.swift new file mode 100644 index 0000000..238331b --- /dev/null +++ b/NBox/HotkeyManager.swift @@ -0,0 +1,55 @@ +import Carbon +import AppKit + +/// Registers a global hotkey (Cmd+Shift+K) using the Carbon API. +/// Works in sandboxed apps without Accessibility permissions. +final class HotkeyManager { + private var hotkeyRef: EventHotKeyRef? + private var eventHandler: EventHandlerRef? + var onTrigger: (() -> Void)? + + func register() { + let hotkeyID = EventHotKeyID(signature: OSType(0x4E424F58), // "NBOX" + id: 1) + + // Cmd+Shift+K — kVK_ANSI_K = 0x28 + let modifiers: UInt32 = UInt32(cmdKey | shiftKey) + let status = RegisterEventHotKey(UInt32(kVK_ANSI_K), modifiers, hotkeyID, + GetApplicationEventTarget(), 0, &hotkeyRef) + guard status == noErr else { + print("nbox: failed to register hotkey: \(status)") + return + } + + // Install Carbon event handler for hotkey events + var eventType = EventTypeSpec(eventClass: OSType(kEventClassKeyboard), + eventKind: UInt32(kEventHotKeyPressed)) + + let handlerBlock: EventHandlerUPP = { _, event, userData -> OSStatus in + guard let userData else { return OSStatus(eventNotHandledErr) } + let manager = Unmanaged.fromOpaque(userData).takeUnretainedValue() + DispatchQueue.main.async { + manager.onTrigger?() + } + return noErr + } + + let selfPtr = Unmanaged.passUnretained(self).toOpaque() + InstallEventHandler(GetApplicationEventTarget(), handlerBlock, 1, &eventType, selfPtr, &eventHandler) + } + + func unregister() { + if let hotkeyRef { + UnregisterEventHotKey(hotkeyRef) + self.hotkeyRef = nil + } + if let eventHandler { + RemoveEventHandler(eventHandler) + self.eventHandler = nil + } + } + + deinit { + unregister() + } +} diff --git a/NBox/KeychainManager.swift b/NBox/KeychainManager.swift index d07d488..d0259e5 100644 --- a/NBox/KeychainManager.swift +++ b/NBox/KeychainManager.swift @@ -116,15 +116,15 @@ struct KeychainManager { return .success(()) } - func get(account: String) -> Result { + func get(account: String, reason: String? = nil) -> Result { let context = LAContext() - context.localizedReason = "Read value for \"\(account)\"" - return get(account: account, context: context) + let prompt = reason ?? "Read value for \"\(account)\"" + return get(account: account, context: context, reason: prompt) } /// Read a secret using a pre-authenticated LAContext (avoids repeated Touch ID). - func get(account: String, context: LAContext) -> Result { - let query: [String: Any] = [ + func get(account: String, context: LAContext, reason: String? = nil) -> Result { + var query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, @@ -132,6 +132,9 @@ struct KeychainManager { kSecUseAuthenticationContext as String: context, kSecUseDataProtectionKeychain as String: true, ] + if let reason { + query[kSecUseOperationPrompt as String] = reason + } var result: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &result) diff --git a/NBox/NBox.entitlements b/NBox/NBox.entitlements index 9b70eed..de4bbf4 100644 --- a/NBox/NBox.entitlements +++ b/NBox/NBox.entitlements @@ -3,7 +3,11 @@ com.apple.security.app-sandbox - + + com.apple.security.network.client + + com.apple.security.network.server + keychain-access-groups $(AppIdentifierPrefix)dev.nobox.nbox diff --git a/NBox/NBoxApp.swift b/NBox/NBoxApp.swift index 1951856..33f820b 100644 --- a/NBox/NBoxApp.swift +++ b/NBox/NBoxApp.swift @@ -16,11 +16,18 @@ struct NBoxApp: App { class AppDelegate: NSObject, NSApplicationDelegate { private var socketServer: SocketServer? + private let hotkeyManager = HotkeyManager() + private lazy var quickAccessPanel = QuickAccessPanel(appState: AppState.shared) func applicationDidFinishLaunching(_ notification: Notification) { socketServer = SocketServer(keychain: KeychainManager()) socketServer?.start() installCLI() + + hotkeyManager.onTrigger = { [weak self] in + self?.quickAccessPanel.toggle() + } + hotkeyManager.register() } /// Copy the bundled nbox-cli to ~/.local/bin/nbox on every launch, @@ -46,6 +53,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func applicationWillTerminate(_ notification: Notification) { + hotkeyManager.unregister() socketServer?.stop() AppState.shared.cleanupSessionDir() } diff --git a/NBox/QuickAccessPanel.swift b/NBox/QuickAccessPanel.swift new file mode 100644 index 0000000..73eb20e --- /dev/null +++ b/NBox/QuickAccessPanel.swift @@ -0,0 +1,249 @@ +import AppKit +import SwiftUI + +/// A floating panel that appears near the cursor for quick secret access. +final class QuickAccessPanel { + private var panel: NSPanel? + private let appState: AppState + + init(appState: AppState) { + self.appState = appState + } + + var isVisible: Bool { panel?.isVisible ?? false } + + func toggle() { + if let panel, panel.isVisible { + dismiss() + } else { + show() + } + } + + func show() { + dismiss() + + let view = QuickAccessView(appState: appState, onDismiss: { [weak self] in + self?.dismiss() + }) + + let hostingView = NSHostingView(rootView: view) + hostingView.frame = NSRect(x: 0, y: 0, width: 300, height: 360) + + let panel = NSPanel( + contentRect: hostingView.frame, + styleMask: [.nonactivatingPanel, .titled, .fullSizeContentView], + backing: .buffered, + defer: false + ) + panel.isFloatingPanel = true + panel.level = .floating + panel.titleVisibility = .hidden + panel.titlebarAppearsTransparent = true + panel.isMovableByWindowBackground = true + panel.contentView = hostingView + panel.backgroundColor = .clear + panel.isOpaque = false + panel.hasShadow = true + panel.hidesOnDeactivate = false + + // Position near mouse cursor + let mouseLocation = NSEvent.mouseLocation + let screenFrame = NSScreen.main?.visibleFrame ?? .zero + var origin = NSPoint(x: mouseLocation.x - 150, y: mouseLocation.y - 380) + + // Keep on screen + origin.x = max(screenFrame.minX, min(origin.x, screenFrame.maxX - 300)) + origin.y = max(screenFrame.minY, min(origin.y, screenFrame.maxY - 360)) + + panel.setFrameOrigin(origin) + panel.makeKeyAndOrderFront(nil) + + // Activate the app so the panel can receive keyboard input + NSApp.activate(ignoringOtherApps: true) + + self.panel = panel + } + + func dismiss() { + panel?.orderOut(nil) + panel = nil + } +} + +struct QuickAccessView: View { + let appState: AppState + let onDismiss: () -> Void + @State private var search = "" + @State private var error: String? + @State private var copiedAccount: String? + @FocusState private var searchFocused: Bool + + private var filtered: [SecretInfo] { + if search.isEmpty { return appState.accounts } + return appState.accounts.filter { $0.account.localizedCaseInsensitiveContains(search) } + } + + var body: some View { + VStack(spacing: 0) { + // Search bar + HStack(spacing: 6) { + Image(systemName: "magnifyingglass") + .foregroundStyle(.secondary) + .font(.system(size: 13)) + TextField("Search secrets...", text: $search) + .textFieldStyle(.plain) + .font(.system(size: 14)) + .focused($searchFocused) + Button(action: onDismiss) { + Image(systemName: "xmark.circle.fill") + .foregroundStyle(.tertiary) + } + .buttonStyle(.plain) + } + .padding(10) + + Divider() + + // Results + if filtered.isEmpty { + Spacer() + Text(search.isEmpty ? "No secrets stored" : "No matches") + .foregroundStyle(.secondary) + .font(.system(size: 13)) + Spacer() + } else { + ScrollView { + LazyVStack(alignment: .leading, spacing: 0) { + ForEach(filtered, id: \.account) { info in + QuickAccessRow( + info: info, + isCopied: copiedAccount == info.account, + onCopy: { copySecret(info.account) } + ) + } + } + .padding(.vertical, 4) + } + } + + if let error { + Divider() + Text(error) + .font(.caption) + .foregroundStyle(.red) + .padding(.horizontal, 10) + .padding(.vertical, 4) + } + + Divider() + + HStack { + Text("\u{2318}\u{21E7}K to toggle") + .font(.system(size: 10)) + .foregroundStyle(.tertiary) + Spacer() + Text("Touch ID to copy") + .font(.system(size: 10)) + .foregroundStyle(.tertiary) + } + .padding(.horizontal, 10) + .padding(.vertical, 5) + } + .frame(width: 300, height: 360) + .background(.ultraThinMaterial) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .onAppear { + searchFocused = true + appState.refresh() + } + .onExitCommand { + onDismiss() + } + } + + private func copySecret(_ account: String) { + error = nil + switch appState.revealSecret(account: account) { + case .success(let value): + // For structured types, extract the most useful value + let meta = appState.metadataFor(account: account) + let copyValue: String + if meta?.type == "login", let decoded = CredentialValue.decodeLogin(value) { + // Copy password (most common need); username is usually not secret + copyValue = decoded.password + } else if meta?.type == "api_key", let decoded = CredentialValue.decodeAPIKey(value) { + copyValue = decoded.primary + } else if meta?.type == "recovery_code", let codes = CredentialValue.decodeRecoveryCodes(value) { + copyValue = codes.joined(separator: "\n") + } else { + copyValue = value + } + + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(copyValue, forType: .string) + copiedAccount = account + + // Auto-clear clipboard after 30s + let changeCount = NSPasteboard.general.changeCount + DispatchQueue.main.asyncAfter(deadline: .now() + 30) { + if NSPasteboard.general.changeCount == changeCount { + NSPasteboard.general.clearContents() + } + } + + // Dismiss after brief visual feedback + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + onDismiss() + } + case .failure(let err): + if case .authCanceled = err { + // User canceled Touch ID — just stay open + } else { + error = err.description + } + } + } +} + +private struct QuickAccessRow: View { + let info: SecretInfo + let isCopied: Bool + let onCopy: () -> Void + + var body: some View { + Button(action: onCopy) { + HStack { + VStack(alignment: .leading, spacing: 2) { + Text(info.account) + .font(.system(size: 12, design: .monospaced)) + .lineLimit(1) + .truncationMode(.middle) + if let peek = info.metadata?.peek { + Text("\(peek)...") + .font(.system(size: 10, design: .monospaced)) + .foregroundStyle(.tertiary) + } + } + Spacer() + if isCopied { + Image(systemName: "checkmark.circle.fill") + .foregroundStyle(.green) + .font(.system(size: 14)) + } else { + if let type = info.metadata?.type { + TypeBadge(type: type) + } + Image(systemName: "key.fill") + .foregroundStyle(.secondary) + .font(.system(size: 11)) + } + } + .padding(.horizontal, 10) + .padding(.vertical, 6) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .background(isCopied ? Color.green.opacity(0.1) : Color.clear) + } +} diff --git a/NBox/SecretListView.swift b/NBox/SecretListView.swift index a230e12..193a512 100644 --- a/NBox/SecretListView.swift +++ b/NBox/SecretListView.swift @@ -99,6 +99,9 @@ struct SecretListView: View { .font(.caption) .buttonStyle(.plain) .foregroundStyle(copiedInstructions ? .green : Color(red: 0xFE/255, green: 0x79/255, blue: 0x5D/255)) + Link("No-Box-Dev", destination: URL(string: "https://noboxdev.com")!) + .font(.caption) + .foregroundStyle(.secondary) Button("Quit") { NSApplication.shared.terminate(nil) } @@ -539,11 +542,16 @@ struct OnboardingView: View { Divider() - HStack { - Spacer() - Button("Got it") { onDismiss() } - .keyboardShortcut(.defaultAction) - Spacer() + VStack(spacing: 4) { + HStack { + Spacer() + Button("Got it") { onDismiss() } + .keyboardShortcut(.defaultAction) + Spacer() + } + Link("noboxdev.com", destination: URL(string: "https://noboxdev.com")!) + .font(.caption2) + .foregroundStyle(.tertiary) } .padding(.vertical, 8) } diff --git a/NBox/SocketServer.swift b/NBox/SocketServer.swift index 62227c2..0e8b5aa 100644 --- a/NBox/SocketServer.swift +++ b/NBox/SocketServer.swift @@ -287,7 +287,7 @@ class SocketServer { sendResponse(clientSocket, ok: true, value: cached) return } - switch keychain.get(account: account) { + switch keychain.get(account: account, reason: "CLI: read value for \"\(account)\"") { case .success(let value): sendResponse(clientSocket, ok: true, value: value) case .failure(let err): diff --git a/NBox.xcodeproj/project.pbxproj b/Noxkey.xcodeproj/project.pbxproj similarity index 89% rename from NBox.xcodeproj/project.pbxproj rename to Noxkey.xcodeproj/project.pbxproj index 9fc8e88..7eae64c 100644 --- a/NBox.xcodeproj/project.pbxproj +++ b/Noxkey.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXFileReference section */ - 0228B8292F5B61030021FBA2 /* NBox.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NBox.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 0228B8292F5B61030021FBA2 /* Noxkey.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Noxkey.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -40,7 +40,7 @@ 0228B82A2F5B61030021FBA2 /* Products */ = { isa = PBXGroup; children = ( - 0228B8292F5B61030021FBA2 /* NBox.app */, + 0228B8292F5B61030021FBA2 /* Noxkey.app */, ); name = Products; sourceTree = ""; @@ -48,9 +48,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 0228B8282F5B61030021FBA2 /* NBox */ = { + 0228B8282F5B61030021FBA2 /* Noxkey */ = { isa = PBXNativeTarget; - buildConfigurationList = 0228B8342F5B61050021FBA2 /* Build configuration list for PBXNativeTarget "NBox" */; + buildConfigurationList = 0228B8342F5B61050021FBA2 /* Build configuration list for PBXNativeTarget "Noxkey" */; buildPhases = ( 0228B8252F5B61030021FBA2 /* Sources */, 0228B8262F5B61030021FBA2 /* Frameworks */, @@ -63,11 +63,11 @@ fileSystemSynchronizedGroups = ( 0228B82B2F5B61030021FBA2 /* NBox */, ); - name = NBox; + name = Noxkey; packageProductDependencies = ( ); productName = NBox; - productReference = 0228B8292F5B61030021FBA2 /* NBox.app */; + productReference = 0228B8292F5B61030021FBA2 /* Noxkey.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -86,7 +86,7 @@ }; }; }; - buildConfigurationList = 0228B8242F5B61030021FBA2 /* Build configuration list for PBXProject "NBox" */; + buildConfigurationList = 0228B8242F5B61030021FBA2 /* Build configuration list for PBXProject "Noxkey" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -100,7 +100,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 0228B8282F5B61030021FBA2 /* NBox */, + 0228B8282F5B61030021FBA2 /* Noxkey */, ); }; /* End PBXProject section */ @@ -262,13 +262,17 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Noxkey; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + INFOPLIST_KEY_LSUIElement = YES; + INFOPLIST_KEY_NSFaceIDUsageDescription = "nbox uses Touch ID to protect your secrets."; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.nobox.nbox; + PRODUCT_BUNDLE_IDENTIFIER = dev.noboxdev.Noxkey; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -293,13 +297,17 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Noxkey; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + INFOPLIST_KEY_LSUIElement = YES; + INFOPLIST_KEY_NSFaceIDUsageDescription = "nbox uses Touch ID to protect your secrets."; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.nobox.nbox; + PRODUCT_BUNDLE_IDENTIFIER = dev.noboxdev.Noxkey; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -310,7 +318,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 0228B8242F5B61030021FBA2 /* Build configuration list for PBXProject "NBox" */ = { + 0228B8242F5B61030021FBA2 /* Build configuration list for PBXProject "Noxkey" */ = { isa = XCConfigurationList; buildConfigurations = ( 0228B8322F5B61050021FBA2 /* Debug */, @@ -319,7 +327,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 0228B8342F5B61050021FBA2 /* Build configuration list for PBXNativeTarget "NBox" */ = { + 0228B8342F5B61050021FBA2 /* Build configuration list for PBXNativeTarget "Noxkey" */ = { isa = XCConfigurationList; buildConfigurations = ( 0228B8352F5B61050021FBA2 /* Debug */,