All skills

Code generator skills that produce production-ready Swift code for common app components. Use when user wants to add logging, analytics, onboarding, review prompts, networking, authentication, paywalls, settings, persistence, error monitoring, CI/CD pipelines, localization, push notifications, deep linking, testing, accessibility, widgets, feature flags, app icons, image caching, pagination, HTTP caching, share cards, social export, subscription lifecycle, referral systems, watermarks, streak tracking, milestone celebrations, what's new screens, lapsed user re-engagement, usage insights, variable rewards, consent flows, account deletion, permission priming, force updates, state restoration, debug menus, offline queues, feedback forms, announcement banners, quick win sessions, Spotlight indexing, App Clips, screenshot automation, background processing, app extensions, data export, or SwiftUI preview sample data and variant matrices.

Use this Skill: https://skilld.dev/gh/rshankras/claude-code-apple-skills/generators

This session only. Nothing lands on disk.

debug-menutemplates.md

≈7.6k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Debug Menu Code Templates

Production-ready Swift templates for a developer debug menu. All code targets iOS 17+ / macOS 14+ with @Observable and modern Swift concurrency. Every type and extension is wrapped in #if DEBUG to ensure nothing ships to production.

DebugMenuView.swift

#if DEBUG
import SwiftUI

/// Main debug menu presented as a NavigationStack with categorized sections.
///
/// Access via shake gesture or hidden tap using `.debugMenuTrigger()`.
struct DebugMenuView: View {
    @Environment(\.dismiss) private var dismiss
    @State private var environmentSwitcher = DebugEnvironmentSwitcher.shared
    @State private var showResetConfirmation = false
    @State private var showExportSheet = false
    @State private var diagnosticReport = ""

    var body: some View {
        NavigationStack {
            List {
                appInfoSection
                featureFlagsSection
                environmentSection
                cacheSection
                networkLogSection
                diagnosticsSection
                actionsSection
            }
            .navigationTitle("Debug Menu")
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Done") { dismiss() }
                }
                ToolbarItem(placement: .primaryAction) {
                    ShareLink(item: diagnosticReport) {
                        Label("Export", systemImage: "square.and.arrow.up")
                    }
                    .disabled(diagnosticReport.isEmpty)
                }
            }
            .task {
                diagnosticReport = DiagnosticInfo.collect().formattedReport
            }
        }
    }

    // MARK: - App Info

    private var appInfoSection: some View {
        Section("App Info") {
            LabeledContent("Version", value: Bundle.main.appVersion)
            LabeledContent("Build", value: Bundle.main.buildNumber)
            LabeledContent("OS", value: ProcessInfo.processInfo.operatingSystemVersionString)
            LabeledContent("Device", value: DiagnosticInfo.deviceModel)
            LabeledContent("Bundle ID", value: Bundle.main.bundleIdentifier ?? "N/A")
        }
    }

    // MARK: - Feature Flags

    private var featureFlagsSection: some View {
        Section("Feature Flags") {
            ForEach(DebugMenuView.featureFlags) { flag in
                FeatureFlagToggleRow(flag: flag)
            }

            if DebugMenuView.featureFlags.isEmpty {
                Text("No feature flags configured.")
                    .foregroundStyle(.secondary)
                    .italic()
            }

            Button("Reset All Flags", role: .destructive) {
                DebugActions.resetFeatureFlags(DebugMenuView.featureFlags)
            }
        }
    }

    /// Register your feature flags here.
    ///
    /// ```swift
    /// static let featureFlags: [FeatureFlag] = [
    ///     FeatureFlag(key: "new_onboarding", title: "New Onboarding", defaultValue: false),
    /// ]
    /// ```
    static var featureFlags: [FeatureFlag] = []

    // MARK: - Environment

    private var environmentSection: some View {
        Section("Environment") {
            Picker("API Environment", selection: $environmentSwitcher.current) {
                ForEach(AppEnvironment.allCases, id: \.self) { env in
                    Text(env.displayName).tag(env)
                }
            }
            .pickerStyle(.segmented)

            LabeledContent("Base URL", value: environmentSwitcher.baseURL.absoluteString)
                .font(.caption)
                .foregroundStyle(.secondary)

            if environmentSwitcher.needsRestart {
                Label("Restart recommended for full effect", systemImage: "exclamationmark.triangle")
                    .foregroundStyle(.orange)
                    .font(.caption)
            }
        }
    }

    // MARK: - Cache

    private var cacheSection: some View {
        Section("Cache") {
            Button("Clear Image Cache") {
                DebugActions.clearImageCache()
            }

            Button("Clear HTTP Cache") {
                DebugActions.clearHTTPCache()
            }

            Button("Clear All Caches", role: .destructive) {
                DebugActions.clearAllCaches()
            }

            LabeledContent("URL Cache Size", value: DebugActions.urlCacheSizeDescription)
        }
    }

    // MARK: - Network Log

    private var networkLogSection: some View {
        Section("Network Log") {
            NavigationLink {
                DebugNetworkLogListView()
            } label: {
                HStack {
                    Text("Recent Requests")
                    Spacer()
                    Text("\(DebugNetworkLogger.shared.entryCount)")
                        .foregroundStyle(.secondary)
                }
            }
        }
    }

    // MARK: - Diagnostics

    private var diagnosticsSection: some View {
        Section("Diagnostics") {
            let info = DiagnosticInfo.collect()
            LabeledContent("Memory Usage", value: info.formattedMemoryUsage)
            LabeledContent("Disk Free", value: info.formattedDiskFree)
            LabeledContent("Locale", value: Locale.current.identifier)
            LabeledContent("Timezone", value: TimeZone.current.identifier)
            LabeledContent("Preferred Languages", value: Locale.preferredLanguages.prefix(3).joined(separator: ", "))
        }
    }

    // MARK: - Actions

    private var actionsSection: some View {
        Section("Actions") {
            Button("Reset Onboarding") {
                DebugActions.resetOnboarding()
            }

            Button("Clear UserDefaults") {
                showResetConfirmation = true
            }
            .confirmationDialog("Clear all UserDefaults?", isPresented: $showResetConfirmation) {
                Button("Clear All", role: .destructive) {
                    DebugActions.clearUserDefaults()
                }
            } message: {
                Text("This will remove all stored preferences. The app may behave as if freshly installed.")
            }

            Button("Force Crash", role: .destructive) {
                DebugActions.simulateCrash()
            }
        }
    }
}

// MARK: - Feature Flag Model

/// A toggleable feature flag stored in UserDefaults.
struct FeatureFlag: Identifiable {
    let key: String
    let title: String
    let defaultValue: Bool

    var id: String { key }

    private var userDefaultsKey: String { "debug_flag_\(key)" }

    var isEnabled: Bool {
        get {
            if UserDefaults.standard.object(forKey: userDefaultsKey) != nil {
                return UserDefaults.standard.bool(forKey: userDefaultsKey)
            }
            return defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: userDefaultsKey)
            NotificationCenter.default.post(name: .debugFeatureFlagChanged, object: key)
        }
    }

    func reset() {
        UserDefaults.standard.removeObject(forKey: userDefaultsKey)
    }
}

extension Notification.Name {
    static let debugFeatureFlagChanged = Notification.Name("debugFeatureFlagChanged")
}

/// A row that toggles a single feature flag.
struct FeatureFlagToggleRow: View {
    let flag: FeatureFlag
    @State private var isOn: Bool

    init(flag: FeatureFlag) {
        self.flag = flag
        self._isOn = State(initialValue: flag.isEnabled)
    }

    var body: some View {
        Toggle(flag.title, isOn: $isOn)
            .onChange(of: isOn) { _, newValue in
                var mutableFlag = flag
                mutableFlag.isEnabled = newValue
            }
    }
}

// MARK: - Bundle Helpers

extension Bundle {
    var appVersion: String {
        infoDictionary?["CFBundleShortVersionString"] as? String ?? "N/A"
    }

    var buildNumber: String {
        infoDictionary?["CFBundleVersion"] as? String ?? "N/A"
    }
}

#Preview {
    DebugMenuView()
}
#endif

DebugEnvironmentSwitcher.swift

#if DEBUG
import Foundation
import Observation

/// Available API environments.
enum AppEnvironment: String, CaseIterable, Sendable {
    case development
    case staging
    case production

    var displayName: String {
        switch self {
        case .development: "Development"
        case .staging: "Staging"
        case .production: "Production"
        }
    }

    var baseURL: URL {
        switch self {
        case .development:
            URL(string: "https://dev-api.example.com/v1")!
        case .staging:
            URL(string: "https://staging-api.example.com/v1")!
        case .production:
            URL(string: "https://api.example.com/v1")!
        }
    }
}

/// Manages switching between API environments at runtime.
///
/// Persists the selected environment to UserDefaults.
/// Services should read `DebugEnvironmentSwitcher.shared.baseURL`
/// for their API base URL.
///
/// Usage:
/// ```swift
/// let baseURL = DebugEnvironmentSwitcher.shared.baseURL
/// ```
@Observable
final class DebugEnvironmentSwitcher {
    static let shared = DebugEnvironmentSwitcher()

    private static let storageKey = "debug_selected_environment"

    var current: AppEnvironment {
        didSet {
            UserDefaults.standard.set(current.rawValue, forKey: Self.storageKey)
            needsRestart = (current != initialEnvironment)
        }
    }

    var needsRestart = false

    var baseURL: URL { current.baseURL }

    private let initialEnvironment: AppEnvironment

    private init() {
        if let stored = UserDefaults.standard.string(forKey: Self.storageKey),
           let env = AppEnvironment(rawValue: stored) {
            self.current = env
            self.initialEnvironment = env
        } else {
            self.current = .development
            self.initialEnvironment = .development
        }
    }
}
#endif

DebugNetworkLogger.swift

#if DEBUG
import Foundation

/// A single logged network request/response pair.
struct NetworkLogEntry: Identifiable, Sendable {
    let id = UUID()
    let timestamp: Date
    let url: String
    let method: String
    let statusCode: Int
    let duration: TimeInterval
    let requestHeaders: [String: String]
    let responseHeaders: [String: String]
    let requestBodySize: Int
    let responseBodySize: Int
    let responseBodyPreview: String

    var isSuccess: Bool { (200...299).contains(statusCode) }

    var formattedDuration: String {
        String(format: "%.0fms", duration * 1000)
    }

    var statusEmoji: String {
        switch statusCode {
        case 200...299: return "green"
        case 300...399: return "yellow"
        case 400...499: return "orange"
        default: return "red"
        }
    }
}

/// Singleton actor that collects recent network requests.
///
/// Uses a circular buffer to store the last 100 entries.
/// Hook into your networking layer to log requests:
///
/// ```swift
/// DebugNetworkLogger.shared.log(
///     request: request,
///     response: response,
///     data: data,
///     duration: elapsed
/// )
/// ```
actor DebugNetworkLogger {
    static let shared = DebugNetworkLogger()

    private var buffer: [NetworkLogEntry] = []
    private let maxEntries = 100

    var entries: [NetworkLogEntry] {
        buffer.reversed()  // Most recent first
    }

    var entryCount: Int { buffer.count }

    /// Log a completed network request.
    func log(
        request: URLRequest,
        response: URLResponse,
        data: Data,
        duration: TimeInterval
    ) {
        let httpResponse = response as? HTTPURLResponse

        // Redact sensitive headers
        var sanitizedRequestHeaders = request.allHTTPHeaderFields ?? [:]
        sanitizedRequestHeaders["Authorization"] = sanitizedRequestHeaders["Authorization"] != nil ? "[REDACTED]" : nil
        sanitizedRequestHeaders["Cookie"] = sanitizedRequestHeaders["Cookie"] != nil ? "[REDACTED]" : nil

        let responseHeaders = httpResponse?.allHeaderFields as? [String: String] ?? [:]

        // Truncate response body preview
        let maxPreviewLength = 500
        let bodyPreview: String
        if let bodyString = String(data: data.prefix(maxPreviewLength), encoding: .utf8) {
            bodyPreview = data.count > maxPreviewLength ? bodyString + "... (\(data.count) bytes total)" : bodyString
        } else {
            bodyPreview = "[\(data.count) bytes, non-UTF8]"
        }

        let entry = NetworkLogEntry(
            timestamp: Date(),
            url: request.url?.absoluteString ?? "Unknown",
            method: request.httpMethod ?? "GET",
            statusCode: httpResponse?.statusCode ?? 0,
            duration: duration,
            requestHeaders: sanitizedRequestHeaders,
            responseHeaders: responseHeaders,
            requestBodySize: request.httpBody?.count ?? 0,
            responseBodySize: data.count,
            responseBodyPreview: bodyPreview
        )

        buffer.append(entry)

        // Circular buffer: remove oldest when exceeding limit
        if buffer.count > maxEntries {
            buffer.removeFirst(buffer.count - maxEntries)
        }
    }

    /// Clear all logged entries.
    func clear() {
        buffer.removeAll()
    }
}

// MARK: - Network Log List View

import SwiftUI

/// Displays all logged network requests in a navigable list.
struct DebugNetworkLogListView: View {
    @State private var entries: [NetworkLogEntry] = []
    @State private var searchText = ""

    var filteredEntries: [NetworkLogEntry] {
        if searchText.isEmpty { return entries }
        return entries.filter { $0.url.localizedCaseInsensitiveContains(searchText) }
    }

    var body: some View {
        List(filteredEntries) { entry in
            NavigationLink {
                NetworkLogDetailView(entry: entry)
            } label: {
                HStack {
                    Circle()
                        .fill(statusColor(for: entry.statusCode))
                        .frame(width: 8, height: 8)

                    VStack(alignment: .leading, spacing: 2) {
                        HStack {
                            Text(entry.method)
                                .font(.caption.monospaced().bold())
                            Text("\(entry.statusCode)")
                                .font(.caption.monospaced())
                                .foregroundStyle(statusColor(for: entry.statusCode))
                        }
                        Text(entry.url)
                            .font(.caption2)
                            .lineLimit(1)
                            .truncationMode(.middle)
                            .foregroundStyle(.secondary)
                    }

                    Spacer()

                    Text(entry.formattedDuration)
                        .font(.caption.monospaced())
                        .foregroundStyle(.secondary)
                }
            }
        }
        .searchable(text: $searchText, prompt: "Filter by URL")
        .navigationTitle("Network Log")
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button("Clear") {
                    Task {
                        await DebugNetworkLogger.shared.clear()
                        entries = []
                    }
                }
            }
        }
        .task {
            entries = await DebugNetworkLogger.shared.entries
        }
        .refreshable {
            entries = await DebugNetworkLogger.shared.entries
        }
    }

    private func statusColor(for code: Int) -> Color {
        switch code {
        case 200...299: .green
        case 300...399: .yellow
        case 400...499: .orange
        default: .red
        }
    }
}

/// Detail view for a single network log entry.
struct NetworkLogDetailView: View {
    let entry: NetworkLogEntry

    var body: some View {
        List {
            Section("Request") {
                LabeledContent("Method", value: entry.method)
                LabeledContent("URL", value: entry.url)
                    .textSelection(.enabled)
                LabeledContent("Body Size", value: "\(entry.requestBodySize) bytes")
            }

            Section("Response") {
                LabeledContent("Status", value: "\(entry.statusCode)")
                LabeledContent("Duration", value: entry.formattedDuration)
                LabeledContent("Body Size", value: "\(entry.responseBodySize) bytes")
            }

            if !entry.requestHeaders.isEmpty {
                Section("Request Headers") {
                    ForEach(entry.requestHeaders.sorted(by: { $0.key < $1.key }), id: \.key) { key, value in
                        LabeledContent(key, value: value)
                            .font(.caption)
                    }
                }
            }

            if !entry.responseHeaders.isEmpty {
                Section("Response Headers") {
                    ForEach(entry.responseHeaders.sorted(by: { $0.key < $1.key }), id: \.key) { key, value in
                        LabeledContent(key, value: value)
                            .font(.caption)
                    }
                }
            }

            Section("Response Body Preview") {
                Text(entry.responseBodyPreview)
                    .font(.caption.monospaced())
                    .textSelection(.enabled)
            }
        }
        .navigationTitle("\(entry.method) \(entry.statusCode)")
    }
}
#endif

DiagnosticInfo.swift

#if DEBUG
import Foundation

/// Collects device and app diagnostic information.
///
/// Usage:
/// ```swift
/// let info = DiagnosticInfo.collect()
/// print(info.formattedReport)
/// ```
struct DiagnosticInfo {
    let appVersion: String
    let buildNumber: String
    let bundleIdentifier: String
    let osVersion: String
    let deviceModel: String
    let memoryUsageMB: Double
    let totalMemoryMB: Double
    let diskFreeMB: Double
    let diskTotalMB: Double
    let locale: String
    let timezone: String
    let preferredLanguages: [String]
    let isLowPowerMode: Bool
    let thermalState: String
    let timestamp: Date

    /// Collect current diagnostic information.
    static func collect() -> DiagnosticInfo {
        let processInfo = ProcessInfo.processInfo
        let fileManager = FileManager.default

        // Memory usage
        var taskInfo = mach_task_basic_info()
        var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size) / 4
        let result = withUnsafeMutablePointer(to: &taskInfo) {
            $0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) {
                task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
            }
        }
        let memoryUsageMB = result == KERN_SUCCESS
            ? Double(taskInfo.resident_size) / (1024 * 1024)
            : 0

        // Disk space
        let homeURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first ?? URL(fileURLWithPath: NSHomeDirectory())
        let diskValues = try? homeURL.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey, .volumeTotalCapacityKey])
        let diskFreeMB = Double(diskValues?.volumeAvailableCapacityForImportantUsage ?? 0) / (1024 * 1024)
        let diskTotalMB = Double(diskValues?.volumeTotalCapacity ?? 0) / (1024 * 1024)

        // Thermal state
        let thermalState: String = switch processInfo.thermalState {
        case .nominal: "Nominal"
        case .fair: "Fair"
        case .serious: "Serious"
        case .critical: "Critical"
        @unknown default: "Unknown"
        }

        return DiagnosticInfo(
            appVersion: Bundle.main.appVersion,
            buildNumber: Bundle.main.buildNumber,
            bundleIdentifier: Bundle.main.bundleIdentifier ?? "N/A",
            osVersion: processInfo.operatingSystemVersionString,
            deviceModel: Self.deviceModel,
            memoryUsageMB: memoryUsageMB,
            totalMemoryMB: Double(processInfo.physicalMemory) / (1024 * 1024),
            diskFreeMB: diskFreeMB,
            diskTotalMB: diskTotalMB,
            locale: Locale.current.identifier,
            timezone: TimeZone.current.identifier,
            preferredLanguages: Array(Locale.preferredLanguages.prefix(5)),
            isLowPowerMode: processInfo.isLowPowerModeEnabled,
            thermalState: thermalState,
            timestamp: Date()
        )
    }

    /// Human-readable device model string.
    static var deviceModel: String {
        #if canImport(UIKit)
        var systemInfo = utsname()
        uname(&systemInfo)
        let machineMirror = Mirror(reflecting: systemInfo.machine)
        let identifier = machineMirror.children.reduce("") { identifier, element in
            guard let value = element.value as? Int8, value != 0 else { return identifier }
            return identifier + String(UnicodeScalar(UInt8(value)))
        }
        return identifier
        #elseif canImport(AppKit)
        var size = 0
        sysctlbyname("hw.model", nil, &size, nil, 0)
        var model = [CChar](repeating: 0, count: size)
        sysctlbyname("hw.model", &model, &size, nil, 0)
        return String(cString: model)
        #endif
    }

    // MARK: - Formatted Output

    var formattedMemoryUsage: String {
        String(format: "%.0f MB / %.0f MB", memoryUsageMB, totalMemoryMB)
    }

    var formattedDiskFree: String {
        if diskFreeMB > 1024 {
            return String(format: "%.1f GB free", diskFreeMB / 1024)
        }
        return String(format: "%.0f MB free", diskFreeMB)
    }

    /// Full diagnostic report formatted for sharing.
    var formattedReport: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .medium
        dateFormatter.timeStyle = .long

        return """
        === Diagnostic Report ===
        Generated: \(dateFormatter.string(from: timestamp))

        -- App --
        Version: \(appVersion) (\(buildNumber))
        Bundle ID: \(bundleIdentifier)

        -- Device --
        Model: \(deviceModel)
        OS: \(osVersion)
        Thermal State: \(thermalState)
        Low Power Mode: \(isLowPowerMode ? "Yes" : "No")

        -- Memory --
        App Usage: \(String(format: "%.0f MB", memoryUsageMB))
        Total: \(String(format: "%.0f MB", totalMemoryMB))

        -- Disk --
        Free: \(formattedDiskFree)
        Total: \(String(format: "%.1f GB", diskTotalMB / 1024))

        -- Locale --
        Current: \(locale)
        Timezone: \(timezone)
        Languages: \(preferredLanguages.joined(separator: ", "))

        -- Debug State --
        Environment: \(DebugEnvironmentSwitcher.shared.current.displayName)
        Base URL: \(DebugEnvironmentSwitcher.shared.baseURL.absoluteString)
        ===========================
        """
    }
}
#endif

DebugMenuTrigger.swift

#if DEBUG
import SwiftUI

// MARK: - Trigger Method

/// How the debug menu is activated.
enum DebugTriggerMethod {
    /// Shake the device to open.
    case shakeGesture
    /// Tap a hidden area 5 times to open.
    case hiddenTap
    /// Both shake and hidden tap.
    case both
}

// MARK: - Shake Detection (iOS)

#if canImport(UIKit)
import UIKit

/// Custom UIWindow subclass that detects shake gestures.
///
/// Set your app's window to this class to enable shake-to-debug.
/// The window posts a notification when a shake is detected.
class ShakeDetectingWindow: UIWindow {
    override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
        super.motionEnded(motion, with: event)
        if motion == .motionShake {
            NotificationCenter.default.post(name: .debugShakeDetected, object: nil)
        }
    }
}

extension Notification.Name {
    static let debugShakeDetected = Notification.Name("debugShakeDetected")
}
#endif

// MARK: - View Modifier

/// ViewModifier that presents the debug menu via shake gesture and/or hidden tap.
///
/// Usage:
/// ```swift
/// ContentView()
///     .debugMenuTrigger()                    // Both shake + tap
///     .debugMenuTrigger(method: .shakeGesture) // Shake only
///     .debugMenuTrigger(method: .hiddenTap)    // Hidden tap only
/// ```
struct DebugMenuTriggerModifier: ViewModifier {
    let method: DebugTriggerMethod
    @State private var showDebugMenu = false
    @State private var tapCount = 0
    private let requiredTaps = 5
    private let tapResetDelay: TimeInterval = 2.0

    func body(content: Content) -> some View {
        content
            .overlay(alignment: .topTrailing) {
                if method == .hiddenTap || method == .both {
                    hiddenTapArea
                }
            }
            .sheet(isPresented: $showDebugMenu) {
                DebugMenuView()
            }
            #if canImport(UIKit)
            .onReceive(NotificationCenter.default.publisher(for: .debugShakeDetected)) { _ in
                if method == .shakeGesture || method == .both {
                    showDebugMenu = true
                }
            }
            #endif
    }

    /// Invisible tap target in the top-right corner.
    private var hiddenTapArea: some View {
        Color.clear
            .frame(width: 44, height: 44)
            .contentShape(Rectangle())
            .onTapGesture {
                tapCount += 1
                if tapCount >= requiredTaps {
                    tapCount = 0
                    showDebugMenu = true
                }

                // Reset tap count after delay
                Task {
                    try? await Task.sleep(for: .seconds(tapResetDelay))
                    tapCount = 0
                }
            }
            .accessibilityHidden(true)
    }
}

// MARK: - View Extension

extension View {
    /// Adds a debug menu trigger to this view.
    ///
    /// - Parameter method: How the debug menu is activated. Default is `.both`.
    /// - Returns: A view with the debug menu trigger attached.
    ///
    /// ```swift
    /// ContentView()
    ///     .debugMenuTrigger()
    /// ```
    func debugMenuTrigger(method: DebugTriggerMethod = .both) -> some View {
        modifier(DebugMenuTriggerModifier(method: method))
    }
}
#endif

DebugActions.swift

#if DEBUG
import Foundation

/// Collection of debug utility actions.
///
/// Each action is a static function that can be called from the debug menu
/// or programmatically during development.
enum DebugActions {

    // MARK: - Onboarding

    /// Reset onboarding state so the user sees onboarding again on next launch.
    static func resetOnboarding() {
        let onboardingKeys = [
            "hasCompletedOnboarding",
            "onboardingCompleted",
            "isOnboarded",
            "didFinishOnboarding",
            "hasSeenWelcome",
        ]

        for key in onboardingKeys {
            UserDefaults.standard.removeObject(forKey: key)
        }

        print("[DebugActions] Onboarding state reset")
    }

    // MARK: - UserDefaults

    /// Clear all UserDefaults for the app's bundle.
    static func clearUserDefaults() {
        guard let bundleId = Bundle.main.bundleIdentifier else { return }
        UserDefaults.standard.removePersistentDomain(forName: bundleId)
        UserDefaults.standard.synchronize()
        print("[DebugActions] UserDefaults cleared for \(bundleId)")
    }

    // MARK: - Keychain

    /// Remove all keychain items for the app.
    static func clearKeychain() {
        let secClasses = [
            kSecClassGenericPassword,
            kSecClassInternetPassword,
            kSecClassCertificate,
            kSecClassKey,
            kSecClassIdentity,
        ]

        for secClass in secClasses {
            let query: [String: Any] = [kSecClass as String: secClass]
            SecItemDelete(query as CFDictionary)
        }

        print("[DebugActions] Keychain cleared")
    }

    // MARK: - Cache

    /// Clear the shared URL cache.
    static func clearHTTPCache() {
        URLCache.shared.removeAllCachedResponses()
        print("[DebugActions] HTTP cache cleared")
    }

    /// Clear the image cache (if using ImagePipeline from image-loading generator).
    ///
    /// Override this to integrate with your image caching layer:
    /// ```swift
    /// DebugActions.clearImageCache = {
    ///     await ImagePipeline.shared.clearCache()
    /// }
    /// ```
    static func clearImageCache() {
        // Clear the shared URLCache images
        URLCache.shared.removeAllCachedResponses()

        // Clear any on-disk image cache directory
        let cacheDir = FileManager.default
            .urls(for: .cachesDirectory, in: .userDomainMask)[0]
            .appendingPathComponent("ImageCache", isDirectory: true)
        try? FileManager.default.removeItem(at: cacheDir)

        print("[DebugActions] Image cache cleared")
    }

    /// Clear all caches: URL, image, and temporary files.
    static func clearAllCaches() {
        clearHTTPCache()
        clearImageCache()

        // Clear tmp directory
        let tmpDir = FileManager.default.temporaryDirectory
        if let files = try? FileManager.default.contentsOfDirectory(at: tmpDir, includingPropertiesForKeys: nil) {
            for file in files {
                try? FileManager.default.removeItem(at: file)
            }
        }

        print("[DebugActions] All caches cleared")
    }

    /// Human-readable URL cache size.
    static var urlCacheSizeDescription: String {
        let cache = URLCache.shared
        let usedMB = Double(cache.currentDiskUsage) / (1024 * 1024)
        let totalMB = Double(cache.diskCapacity) / (1024 * 1024)
        return String(format: "%.1f MB / %.0f MB", usedMB, totalMB)
    }

    // MARK: - Feature Flags

    /// Reset all feature flags to their default values.
    static func resetFeatureFlags(_ flags: [FeatureFlag]) {
        for flag in flags {
            flag.reset()
        }
        print("[DebugActions] All feature flags reset to defaults")
    }

    // MARK: - Crash

    /// Trigger a fatal error for testing crash reporting (e.g., Crashlytics).
    ///
    /// - Warning: This will terminate the app immediately.
    static func simulateCrash() {
        fatalError("[DebugActions] Intentional crash triggered from debug menu")
    }
}
#endif

Source: SKILL.md on GitHub

1 alert16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The 'generators' skill provides a robust set of templates and workflows for generating Swift code for iOS and macOS applications. Security analysis identified a surface for indirect prompt injection because the skill reads local project files to adapt its generation logic. It also utilizes dynamic execution by generating and running local Swift scripts to create application icons and uses shell commands to process images and audit project code. All external references are to well-known, established services.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: MEDIUM · 1 issue

  • Runlayer7mo

    59/179 files flagged

  • ZeroLeaks5mo

    Scan incomplete

Signed by skilld at 30e898f. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 2 months ago
What it can do
Reads files Edits files Runs commands
last_verified
2026-07-16
review_by
2027-06-22
os_version
iOS 27 / macOS 27
All 7 allowed tools
ReadWriteEditGlobGrepBashAskUserQuestion

README badge

README badge for rshankras/claude-code-apple-skills/generators