Public

Zero to app: Poof

I don't feel snippets belong in launchers like Tuna. Also for something that reads all keyboard input, should probably be Open Source.

Stats
Prompts
12
Tool calls
171
Messages
507
Assets
1
Meta
Author
@mikker
Created At
February 18, 2026
Source
Codex
Share
Permalink
Thought 0 times. Used 0 tool calls. +0 -0 [Expand]
System prompt
System prompt

AGENTS.md instructions for ~/dev/Poof

  • We want the simplest change possible. We don't care about migration. Code readability matters most, and we're happy to make bigger changes to achieve it.
  • If I pass you URLs, fetch them directly. You may also use the web command line tool (~/.dotfiles/bin/web) for a text-only version.
  • mise is installed and generally used to manage language versions. Don't force it everywhere but notice it when it's there.
  • ast-grep and ripgrep are available.
  • If I ask you to commit, you decide the message. Use format "[Imperative] [description] [optional: two newlines and an extended description"
    • Examples: "Add pricing page", "Fix whatever", "Change the universe"
  • Whenever you're done working, as the very last thing before returning to the user, run ~/.dotfiles/bin/bootledoot to notify.

Skills

A skill is a set of local instructions to follow that is stored in a SKILL.md file. Below is the list of skills that can be used. Each entry includes a name, description, and file path so you can open the source for full instructions when using a specific skill.

Available skills

  • agent-browser: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages. (file: ~/.codex/skills/agent-browser/SKILL.md)
  • agent-native-architecture: This skill should be used when building AI agents using prompt-native architecture where features are defined in prompts, not code. Use it when creating autonomous agents, designing MCP servers, implementing self-modifying systems, or adopting the "trust the agent's intelligence" philosophy. (file: ~/.codex/skills/agent-native-architecture/SKILL.md)
  • andrew-kane-gem-writer: This skill should be used when writing Ruby gems following Andrew Kane's proven patterns and philosophy. It applies when creating new Ruby gems, refactoring existing gems, designing gem APIs, or when clean, minimal, production-ready Ruby library code is needed. Triggers on requests like "create a gem", "write a Ruby library", "design a gem API", or mentions of Andrew Kane's style. (file: ~/.codex/skills/andrew-kane-gem-writer/SKILL.md)
  • ask-questions-if-underspecified: Clarify requirements before implementing. Do not use automatically, only when invoked explicitly. (file: ~/.codex/skills/ask-questions-if-underspecified/SKILL.md)
  • beads: Tracks complex, multi-session work using the Beads issue tracker and dependency graphs, and provides persistent memory that survives conversation compaction. Use when work spans multiple sessions, has complex dependencies, or needs persistent context across compaction cycles. Trigger with phrases like "create task for", "what's ready to work on", "show task", "track this work", "what's blocking", or "update status". (file: ~/.codex/skills/beads/SKILL.md)
  • beautiful-mermaid: Render Mermaid diagrams as SVG and PNG using the Beautiful Mermaid library. Use when the user asks to render a Mermaid diagram. (file: ~/.agents/skills/beautiful-mermaid/SKILL.md)
  • coding-tutor: Personalized coding tutorials that build on your existing knowledge and use your actual codebase for examples. Creates a persistent learning trail that compounds over time using the power of AI, spaced repetition and quizes. (file: ~/.codex/skills/coding-tutor/SKILL.md)
  • dex: Manage tasks via dex CLI. Use when breaking down complex work, tracking implementation items, or persisting context across sessions. (file: ~/.agents/skills/dex/SKILL.md)
  • dex-plan: Create dex task from markdown planning documents (plans, specs, design docs, roadmaps) (file: ~/.agents/skills/dex-plan/SKILL.md)
  • dhh-rails-style: This skill should be used when writing Ruby and Rails code in DHH's distinctive 37signals style. It applies when writing Ruby code, Rails applications, creating models, controllers, or any Ruby file. Triggers on Ruby/Rails code generation, refactoring requests, code review, or when the user mentions DHH, 37signals, Basecamp, HEY, or Campfire style. Embodies REST purity, fat models, thin controllers, Current attributes, Hotwire patterns, and the "clarity over cleverness" philosophy. (file: ~/.codex/skills/dhh-rails-style/SKILL.md)
  • dhh-ruby-style: This skill should be used when writing Ruby and Rails code in DHH's distinctive 37signals style. It applies when writing Ruby code, Rails applications, creating models, controllers, or any Ruby file. Triggers on Ruby/Rails code generation, refactoring requests, code review, or when the user mentions DHH, 37signals, Basecamp, HEY, or Campfire style. Embodies REST purity, fat models, thin controllers, Current attributes, Hotwire patterns, and the "clarity over cleverness" philosophy. (file: ~/.codex/skills/dhh-ruby-style/SKILL.md)
  • diagram-first: Diagram-first explanations using ASCII visualizations (sequence diagrams, flowcharts, component maps) to explain code, systems, request/response flows, pipelines, concurrency, and comparisons. Use when the user asks to "draw an ASCII diagram", "show the flow/logic", "visualize the architecture", "explain how it works", or when a diagram would reduce ambiguity in a technical explanation. (file: ~/.codex/skills/diagram-first/SKILL.md)
  • file-todos: This skill should be used when managing the file-based todo tracking system in the todos/ directory. It provides workflows for creating todos, managing status and dependencies, conducting triage, and integrating with slash commands and code review processes. (file: ~/.codex/skills/file-todos/SKILL.md)
  • find-skills: Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill. (file: ~/.agents/skills/find-skills/SKILL.md)
  • gemini-imagegen: This skill should be used when generating and editing images using the Gemini API (Nano Banana Pro). It applies when creating images from text prompts, editing existing images, applying style transfers, generating logos with text, creating stickers, product mockups, or any image generation/manipulation task. Supports text-to-image, image editing, multi-turn refinement, and composition from multiple reference images. (file: ~/.codex/skills/gemini-imagegen/SKILL.md)
  • git-worktree: This skill manages Git worktrees for isolated parallel development. It handles creating, listing, switching, and cleaning up worktrees with a simple interactive interface, following KISS principles. (file: ~/.codex/skills/git-worktree/SKILL.md)
  • nitro-kit-components: Build or refactor Nitro Kit-style UI components, helpers, and Stimulus behaviors in Rails apps. Use when working in the Nitro Kit repo or when creating app-specific components that should follow Nitro Kit conventions (Phlex + Tailwind + minimal Stimulus), or when reviewing for Nitro Kit style guide compliance. (file: ~/.codex/skills/nitro-kit-components/SKILL.md)
  • refactor-pass: Perform a refactor pass focused on simplicity after recent changes. Use when the user asks for a refactor/cleanup pass, simplification, or dead-code removal and expects build/tests to verify behavior. (file: ~/.agents/skills/refactor-pass/SKILL.md)
  • refactor-pass: Perform a refactor pass focused on simplicity after recent changes. Use when the user asks for a refactor/cleanup pass, simplification, or dead-code removal and expects build/tests to verify behavior. (file: ~/.codex/skills/refactor-pass/SKILL.md)
  • remotion-best-practices: Best practices for Remotion - Video creation in React (file: ~/.agents/skills/remotion-best-practices/SKILL.md)
  • solidify-codebase: Deep investigation and solidification pass on an existing codebase. Use when asked to audit, simplify, or future-proof a system; perform a deep cleanup/refactor pass; identify high-impact improvement opportunities; or present a vetted change list before implementing selected items. (file: ~/.agents/skills/solidify-codebase/SKILL.md)
  • solidify-codebase: Deep investigation and solidification pass on an existing codebase. Use when asked to audit, simplify, or future-proof a system; perform a deep cleanup/refactor pass; identify high-impact improvement opportunities; or present a vetted change list before implementing selected items. (file: ~/.codex/skills/solidify-codebase/SKILL.md)
  • steve: Use the steve CLI to automate macOS apps via Accessibility APIs. Use when you need to drive Mac UI (apps, windows, menus, elements), run UI smoke tests, or script interactions using steve commands and JSON output. Never decide to use steve on your own. Only use when told to. (file: ~/.codex/skills/steve/SKILL.md)
  • swift-concurrency-expert: Swift Concurrency review and remediation for Swift 6.2+. Use when asked to review Swift Concurrency usage, improve concurrency compliance, or fix Swift concurrency compiler errors in a feature or file. (file: ~/.agents/skills/swift-concurrency-expert/SKILL.md)
  • swiftui-skills: Apple-authored SwiftUI and platform guidance extracted from Xcode. Helps AI agents write idiomatic, Apple-native SwiftUI with reduced hallucinations. (file: ~/.agents/skills/swiftui-skills/SKILL.md)
  • swiftui-ui-patterns: Best practices and example-driven guidance for building SwiftUI views and components. Use when creating or refactoring SwiftUI UI, designing tab architecture with TabView, composing screens, or needing component-specific patterns and examples. (file: ~/.codex/skills/swiftui-ui-patterns/SKILL.md)
  • vercel-react-best-practices: React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements. (file: ~/.codex/skills/vercel-react-best-practices/SKILL.md)
  • web-design-guidelines: Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices". (file: ~/.codex/skills/web-design-guidelines/SKILL.md)
  • web-interface-guidelines: Review UI/frontend code for compliance with Vercel Web Interface Guidelines. Use when the user asks for a guideline audit, accessibility/UX compliance review, or a /web-interface-guidelines style check across files or patterns. (file: ~/.codex/skills/web-interface-guidelines/SKILL.md)
  • skill-creator: Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Codex's capabilities with specialized knowledge, workflows, or tool integrations. (file: ~/.codex/skills/.system/skill-creator/SKILL.md)
  • skill-installer: Install Codex skills into $CODEX_HOME/skills from a curated list or a GitHub repo path. Use when a user asks to list installable skills, install a curated skill, or install a skill from another repo (including private repos). (file: ~/.codex/skills/.system/skill-installer/SKILL.md)

How to use skills

  • Discovery: The list above is the skills available in this session (name + description + file path). Skill bodies live on disk at the listed paths.
  • Trigger rules: If the user names a skill (with $SkillName or plain text) OR the task clearly matches a skill's description shown above, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.
  • Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.
  • How to use a skill (progressive disclosure):
    1. After deciding to use a skill, open its SKILL.md. Read only enough to follow the workflow.
    2. When SKILL.md references relative paths (e.g., scripts/foo.py), resolve them relative to the skill directory listed above first, and only consider other paths if needed.
    3. If SKILL.md points to extra folders such as references/, load only the specific files needed for the request; don't bulk-load everything.
    4. If scripts/ exist, prefer running or patching them instead of retyping large code blocks.
    5. If assets/ or templates exist, reuse them instead of recreating from scratch.
  • Coordination and sequencing:
    • If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.
    • Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.
  • Context hygiene:
    • Keep context small: summarize long sections instead of pasting them; only load extra files when needed.
    • Avoid deep reference-chasing: prefer opening only files directly linked from SKILL.md unless you're blocked.
    • When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.
  • Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue.
System prompt

<environment_context>
~/dev/Poof
zsh
</environment_context>

System prompt

<collaboration_mode># Collaboration Mode: Default

You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active.

Your active mode changes only when new developer instructions with a different <collaboration_mode>...</collaboration_mode> change it; user requests or tool descriptions do not change mode by themselves. Known mode names are Default and Plan.

request_user_input availability

The request_user_input tool is unavailable in Default mode. If you call it while in Default mode, it will return an error.

If a decision is necessary and cannot be discovered from local context, ask the user directly. However, in Default mode you should strongly prefer executing the user's request rather than stopping to ask questions.
</collaboration_mode>

Permalink

Initialize this directory as a new macOS app. Status bar item sstyle app, see others

  • ../Leader Key
  • ../Tuna/app
  • ../Moves
  • ../Sprinkles
  • ../FlickRing
    This one should be a text snippet expander in the style of Espanso, TextExpander et al. Typing preset text snippets expands them into corresponding snippets.
    Config should be text driven. With user-selectable config dir. Configs can be multiple files. Should be in TOML. Default to User Preferences.
    Investigate referenced apps, look into how espanso (open source) does its thing. Then $ask-questions-if-underspecified
Permalink

Ask Questions If Underspecified

Goal

Ask the minimum set of clarifying questions needed to avoid wrong work; do not start implementing until the must-have questions are answered (or the user explicitly approves proceeding with stated assumptions).

Workflow

1) Decide whether the request is underspecified

Treat a request as underspecified if after exploring how to perform the work, some or all of the following are not clear:

  • Define the objective (what should change vs stay the same)
  • Define "done" (acceptance criteria, examples, edge cases)
  • Define scope (which files/components/users are in/out)
  • Define constraints (compatibility, performance, style, deps, time)
  • Identify environment (language/runtime versions, OS, build/test runner)
  • Clarify safety/reversibility (data migration, rollout/rollback, risk)

If multiple plausible interpretations exist, assume it is underspecified.

2) Ask must-have questions first (keep it small)

Ask 1-5 questions in the first pass. Prefer questions that eliminate whole branches of work.

Make questions easy to answer:

  • Optimize for scannability (short, numbered questions; avoid paragraphs)
  • Offer multiple-choice options when possible
  • Suggest reasonable defaults when appropriate (mark them clearly as the default/recommended choice; bold the recommended choice in the list, or if you present options in a code block, put a bold "Recommended" line immediately above the block and also tag defaults inside the block)
  • Include a fast-path response (e.g., reply defaults to accept all recommended/default choices)
  • Include a low-friction "not sure" option when helpful (e.g., "Not sure - use default")
  • Separate "Need to know" from "Nice to know" if that reduces friction
  • Structure options so the user can respond with compact decisions (e.g., 1b 2a 3c); restate the chosen options in plain language to confirm

3) Pause before acting

Until must-have answers arrive:

  • Do not run commands, edit files, or produce a detailed plan that depends on unknowns
  • Do perform a clearly labeled, low-risk discovery step only if it does not commit you to a direction (e.g., inspect repo structure, read relevant config files)

If the user explicitly asks you to proceed without answers:

  • State your assumptions as a short numbered list
  • Ask for confirmation; proceed only after they confirm or correct them

4) Confirm interpretation, then proceed

Once you have answers, restate the requirements in 1-3 sentences (including key constraints and what success looks like), then start work.

Question Templates

  • "Before I start, I need: (1) ..., (2) ..., (3) .... If you don't care about (2), I will assume ...."
  • "Which of these should it be? A) ... B) ... C) ... (pick one)"
  • "What would you consider 'done'? For example: ..."
  • "Any constraints I must follow (versions, performance, style, deps)? If none, I will target the existing project defaults."
  • Use numbered questions with lettered options and a clear reply format
1) Scope?
a) Minimal change (default)
b) Refactor while touching the area
c) Not sure - use default
2) Compatibility target?
a) Current project defaults (default)
b) Also support older versions: <specify>
c) Not sure - use default

Reply with: defaults (or 1a 2a)

Anti-Patterns

  • Don't ask questions you can answer with a quick, low-risk discovery read (e.g., configs, existing patterns, docs).
  • Don't ask open-ended questions if a tight multiple-choice or yes/no would eliminate ambiguity faster.
Permalink
Thought 1 time. Used 0 tool calls. +0 -0 [Expand]
Thinking

Starting investigation of referenced apps

Permalink

Using the ask-questions-if-underspecified skill here. I’ll do a quick discovery pass on the sibling apps and Espanso’s design patterns, then ask only the must-have questions before I initialize anything.

Permalink
Thought 2 times. Used 2 tool calls. +0 -0 [Expand]
Thinking

Listing sibling directories

$ pwd; ls -la ..
Output Redacted
$ for d in \
Output Redacted
Thinking

Planning architecture inspection

Permalink

I found all reference projects and confirmed they’re Swift/Xcode app structures. Next I’m extracting the common status-bar app pattern and config approach from their source files before asking implementation questions.

Permalink
Thought 3 times. Used 11 tool calls. +0 -0 [Expand]
$ rg -n \
Output Redacted
$ rg --files \
Output Redacted
Thinking

Identifying key configuration files

$ sed -n '1,220p' '../Leader Key/Leader Key/UserConfig.swift'
import Cocoa
import Combine
import CryptoKit
import Defaults

let emptyRoot = Group(key: "🚫", label: "Config error", actions: [])

class UserConfig: ObservableObject {
  @Published var root = emptyRoot {
    didSet {
      if !isLoading && root != emptyRoot && root != oldValue {
        saveConfigAsync()
      }
    }
  }
  @Published var validationErrors: [ValidationError] = []
  // O(1) lookup for row validation; keys are path strings like "1/0/3"
  @Published var validationErrorsByPath: [String: ValidationErrorType] = [:]

  let fileName = "config.json"
  private let alertHandler: AlertHandler
  private let fileManager: FileManager
  private var lastReadChecksum: String?
  private var isLoading = false
  private let configIOQueue = DispatchQueue(label: "ConfigIO", qos: .userInitiated)
  private var saveWorkItem: DispatchWorkItem?

  init(
    alertHandler: AlertHandler = DefaultAlertHandler(),
    fileManager: FileManager = .default
  ) {
    self.alertHandler = alertHandler
    self.fileManager = fileManager
  }

  // MARK: - Public Interface

  func ensureAndLoad() {
    ensureValidConfigDirectory()
    ensureConfigFileExists()
    loadConfig()
  }

  func reloadFromFile() {
    Events.send(.willReload)
    loadConfig(suppressAlerts: true)
    Events.send(.didReload)
  }

  func saveConfig() {
    // Check for file conflicts before saving
    if let lastChecksum = lastReadChecksum, exists {
      let currentChecksum = getCurrentFileChecksum()
      if currentChecksum != lastChecksum {
        let result = alertHandler.showAlert(
          style: .warning,
          message: "Configuration file changed on disk",
          informativeText:
            "The configuration file has been modified outside of the app. Choose 'Read from File' to load the external changes, or 'Overwrite' to save your current changes.",
          buttons: ["Overwrite", "Cancel", "Read from File"]
        )

        switch result {
        case .alertThirdButtonReturn:  // Read from File (rightmost, default)
          reloadFromFile()
          return
        case .alertFirstButtonReturn:  // Overwrite
          break  // Continue with save
        default:  // Cancel
          return
        }
      }
    }

    setValidationErrors(ConfigValidator.validate(group: root))

    do {
      let encoder = JSONEncoder()
      encoder.outputFormatting = [
        .prettyPrinted, .withoutEscapingSlashes, .sortedKeys,
      ]
      let jsonData = try encoder.encode(root)

      try writeFile(data: jsonData)

      // Update checksum after successful write using data directly
      lastReadChecksum = calculateChecksum(jsonData)
    } catch {
      handleError(error, critical: true)
    }
  }

  private func saveConfigAsync() {
    // Cancel any pending save
    saveWorkItem?.cancel()

    // Create a new debounced save work item
    let currentRoot = root
    let workItem = DispatchWorkItem { [weak self] in
      guard let self = self else { return }

      // Perform file I/O on background queue
      let encoder = JSONEncoder()
      encoder.outputFormatting = [.prettyPrinted, .withoutEscapingSlashes, .sortedKeys]

      do {
        let jsonData = try encoder.encode(currentRoot)

        // Check conflicts on background queue first, then switch to main for UI
        if let lastChecksum = self.lastReadChecksum, self.exists {
          let currentChecksum = self.getCurrentFileChecksum()
          if currentChecksum != lastChecksum {
            DispatchQueue.main.async {
              let result = self.alertHandler.showAlert(
                style: .warning,
                message: "Configuration file changed on disk",
                informativeText:
                  "The configuration file has been modified outside of the app. Choose 'Read from File' to load the external changes, or 'Overwrite' to save your current changes.",
                buttons: ["Overwrite", "Cancel", "Read from File"]
              )

              switch result {
              case .alertThirdButtonReturn:  // Read from File
                self.reloadFromFile()
                return
              case .alertFirstButtonReturn:  // Overwrite
                break  // Continue with save
              default:  // Cancel
                return
              }

              // Continue with save after conflict resolution
              self.performSaveWithData(jsonData, currentRoot: currentRoot)
            }
            return
          }
        }

        DispatchQueue.main.async {
          self.performSaveWithData(jsonData, currentRoot: currentRoot)
        }
      } catch {
        DispatchQueue.main.async {
          self.handleError(error, critical: true)
        }
      }
    }

    saveWorkItem = workItem

    // Execute with 300ms debounce
    configIOQueue.asyncAfter(deadline: .now() + .milliseconds(300), execute: workItem)
  }

  private func performSaveWithData(_ jsonData: Data, currentRoot: Group) {
    // Validation on main queue
    let validationErrors = ConfigValidator.validate(group: currentRoot)
    setValidationErrors(validationErrors)

    // Back to background for file write
    configIOQueue.async { [weak self] in
      guard let self = self else { return }

      do {
        try self.writeFile(data: jsonData)

        DispatchQueue.main.async {
          // Update checksum on main queue using data directly
          self.lastReadChecksum = self.calculateChecksum(jsonData)
        }
      } catch {
        DispatchQueue.main.async {
          self.handleError(error, critical: true)
        }
      }
    }
  }

  // MARK: - Directory Management

  static func defaultDirectory() -> String {
    let appSupportDir = FileManager.default.urls(
      for: .applicationSupportDirectory, in: .userDomainMask)[0]
    let path = (appSupportDir.path as NSString).appendingPathComponent(
      "Leader Key")
    do {
      try FileManager.default.createDirectory(
        atPath: path, withIntermediateDirectories: true)
    } catch {
      fatalError("Failed to create config directory")
    }
    return path
  }

  private func ensureValidConfigDirectory() {
    let dir = Defaults[.configDir]
    let defaultDir = Self.defaultDirectory()

    if !fileManager.fileExists(atPath: dir) {
      alertHandler.showAlert(
        style: .warning,
        message:
          "Config directory does not exist: \(dir)\nResetting to default location."
      )
      Defaults[.configDir] = defaultDir
    }
  }

  // MARK: - File Operations

  var path: String {
    (Defaults[.configDir] as NSString).appendingPathComponent(fileName)
  }

  var url: URL {
    URL(fileURLWithPath: path)
  }

  var exists: Bool {
    fileManager.fileExists(atPath: path)
$ sed -n '1,260p' '../Leader Key/Leader Key/AppDelegate.swift'
import Cocoa
import Defaults
import KeyboardShortcuts
import Settings
import Sparkle
import SwiftUI
import UserNotifications

let updateLocationIdentifier = "UpdateCheck"

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate,
  SPUStandardUserDriverDelegate,
  UNUserNotificationCenterDelegate,
  NSWindowDelegate
{
  var controller: Controller!

  let statusItem = StatusItem()
  let config = UserConfig()

  var state: UserState!
  @IBOutlet var updaterController: SPUStandardUpdaterController!

  lazy var settingsWindowController = SettingsWindowController(
    panes: [
      Settings.Pane(
        identifier: .general, title: "General",
        toolbarIcon: NSImage(named: NSImage.preferencesGeneralName)!,
        contentView: { GeneralPane().environmentObject(self.config) }
      ),
      Settings.Pane(
        identifier: .advanced, title: "Advanced",
        toolbarIcon: NSImage(named: NSImage.advancedName)!,
        contentView: {
          AdvancedPane().environmentObject(self.config)
        }),
    ],
    style: .segmentedControl,
  )

  func applicationDidFinishLaunching(_: Notification) {

    guard
      ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] != "1"
    else { return }
    guard !isRunningTests() else { return }

    UNUserNotificationCenter.current().delegate = self

    NSApp.mainMenu = MainMenu()

    config.ensureAndLoad()
    state = UserState(userConfig: config)
    controller = Controller(userState: state, userConfig: config)

    statusItem.handlePreferences = {
      self.showSettings()
    }
    statusItem.handleAbout = {
      NSApp.orderFrontStandardAboutPanel(nil)
    }
    statusItem.handleReloadConfig = {
      self.config.reloadFromFile()
    }
    statusItem.handleRevealConfig = {
      NSWorkspace.shared.activateFileViewerSelecting([self.config.url])
    }
    statusItem.handleCheckForUpdates = {
      self.updaterController.checkForUpdates(nil)
    }

    Task {
      for await value in Defaults.updates(.showMenuBarIcon) {
        if value {
          self.statusItem.enable()
        } else {
          self.statusItem.disable()
        }
      }
    }

    // Initialize status item according to current preference
    if Defaults[.showMenuBarIcon] {
      statusItem.enable()
    } else {
      statusItem.disable()
    }

    // Activation policy is managed solely by the Settings window

    registerGlobalShortcuts()
  }

  func activate() {
    if self.controller.window.isKeyWindow {
      switch Defaults[.reactivateBehavior] {
      case .hide:
        self.hide()
      case .reset:
        self.controller.userState.clear()
      case .nothing:
        return
      }
    } else if self.controller.window.isVisible {
      // should never happen as the window will self-hide when not key
      self.controller.window.makeKeyAndOrderFront(nil)
    } else {
      self.show()
    }
  }

  public func registerGlobalShortcuts() {
    KeyboardShortcuts.removeAllHandlers()

    KeyboardShortcuts.onKeyDown(for: .activate) {
      self.activate()
    }

    for groupKey in Defaults[.groupShortcuts] {
      print("Registering shortcut for \(groupKey)")
      KeyboardShortcuts.onKeyDown(for: KeyboardShortcuts.Name("group-\(groupKey)")) {
        if !self.controller.window.isVisible {
          self.activate()
        }
        self.processKeys([groupKey])
      }
    }
  }

  func applicationWillTerminate(_ notification: Notification) {
    // Config saves automatically on changes
  }

  @IBAction
  func settingsMenuItemActionHandler(_: NSMenuItem) {
    showSettings()
  }

  func show() {
    controller.show()
  }

  func hide() {
    controller.hide()
  }

  // MARK: - Sparkle Gentle Reminders

  @objc var supportsGentleScheduledUpdateReminders: Bool {
    return true
  }

  func standardUserDriverWillHandleShowingUpdate(
    _ handleShowingUpdate: Bool, forUpdate update: SUAppcastItem,
    state: SPUUserUpdateState
  ) {
    // Do not change activation policy here; Settings drives visibility

    if !state.userInitiated {
      NSApp.dockTile.badgeLabel = "1"

      requestNotificationsAuthorizationIfNeeded { granted in
        guard granted else { return }
        let content = UNMutableNotificationContent()
        content.title = "Leader Key Update Available"
        content.body = "Version \(update.displayVersionString) is now available"

        let request = UNNotificationRequest(
          identifier: updateLocationIdentifier, content: content,
          trigger: nil)
        UNUserNotificationCenter.current().add(request)
      }
    }
  }

  func standardUserDriverDidReceiveUserAttention(
    forUpdate update: SUAppcastItem
  ) {
    NSApp.dockTile.badgeLabel = ""

    UNUserNotificationCenter.current().removeDeliveredNotifications(
      withIdentifiers: [
        updateLocationIdentifier
      ])
  }

  func standardUserDriverWillFinishUpdateSession() {}

  // MARK: - UNUserNotificationCenter Delegate

  func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    didReceive response: UNNotificationResponse,
    withCompletionHandler completionHandler: @escaping () -> Void
  ) {
    if response.notification.request.identifier
      == updateLocationIdentifier
      && response.actionIdentifier == UNNotificationDefaultActionIdentifier
    {
      updaterController.checkForUpdates(nil)
    }
    completionHandler()
  }

  func isRunningTests() -> Bool {
    let environment = ProcessInfo.processInfo.environment
    guard environment["XCTestSessionIdentifier"] != nil else { return false }
    return true
  }

  // MARK: - URL Scheme Handling

  func application(_ application: NSApplication, open urls: [URL]) {
    for url in urls {
      handleURL(url)
    }
  }

  private func handleURL(_ url: URL) {
    guard url.scheme == "leaderkey" else { return }

    if url.host == "settings" {
      showSettings()
      return
    }
    if url.host == "about" {
      NSApp.orderFrontStandardAboutPanel(nil)
      return
    }

    show()

    if url.host == "navigate",
      let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
      let queryItems = components.queryItems,
      let keysParam = queryItems.first(where: { $0.name == "keys" })?.value
    {
      let keys = keysParam.split(separator: ",").map(String.init)
      processKeys(keys)
    }
  }

  private func processKeys(_ keys: [String]) {
    guard !keys.isEmpty else { return }

    controller.handleKey(keys[0])

    if keys.count > 1 {
      let remainingKeys = Array(keys.dropFirst())

      var delayMs = 100
      for key in remainingKeys {
        delay(delayMs) { [weak self] in
          self?.controller.handleKey(key)
        }
        delayMs += 100
      }
    }
  }
$ sed -n '1,260p' '../Leader Key/Leader Key/Settings.swift'
import Settings

extension Settings.PaneIdentifier {
  static let general = Self("general")
  static let advanced = Self("advanced")
}
Thinking

Planning status item inspection

$ sed -n '1,260p' '../Leader Key/Leader Key/StatusItem.swift'
import Cocoa
import Combine
import Sparkle

class StatusItem {
  enum Appearance {
    case normal
    case active
  }

  var appearance: Appearance = .normal {
    didSet {
      updateStatusItemAppearance()
    }
  }

  var statusItem: NSStatusItem?
  private var cancellables = Set<AnyCancellable>()

  var handlePreferences: (() -> Void)?
  var handleAbout: (() -> Void)?
  var handleReloadConfig: (() -> Void)?
  var handleRevealConfig: (() -> Void)?
  var handleCheckForUpdates: (() -> Void)?

  func enable() {
    statusItem = NSStatusBar.system.statusItem(
      withLength: NSStatusItem.squareLength)

    guard let item = statusItem else {
      print("No status item")
      return
    }

    if let menubarButton = item.button {
      menubarButton.image = NSImage(named: NSImage.Name("StatusItem"))
    }

    let menu = NSMenu()

    // About
    let aboutItem = NSMenuItem(
      title: "About Leader Key", action: #selector(showAbout),
      keyEquivalent: ""
    )
    aboutItem.target = self
    menu.addItem(aboutItem)

    menu.addItem(NSMenuItem.separator())

    // Settings
    let preferencesItem = NSMenuItem(
      title: "Settings…", action: #selector(showPreferences), keyEquivalent: ",")
    preferencesItem.target = self
    menu.addItem(preferencesItem)

    menu.addItem(NSMenuItem.separator())

    let checkForUpdatesItem = NSMenuItem(
      title: "Check for Updates...", action: #selector(checkForUpdates),
      keyEquivalent: ""
    )
    checkForUpdatesItem.target = self
    menu.addItem(checkForUpdatesItem)

    menu.addItem(NSMenuItem.separator())

    let revealConfigItem = NSMenuItem(
      title: "Show config in Finder", action: #selector(revealConfigFile),
      keyEquivalent: ""
    )
    revealConfigItem.target = self
    menu.addItem(revealConfigItem)

    let reloadConfigItem = NSMenuItem(
      title: "Reload config", action: #selector(reloadConfig), keyEquivalent: ""
    )
    reloadConfigItem.target = self
    menu.addItem(reloadConfigItem)

    menu.addItem(NSMenuItem.separator())

    menu.addItem(
      NSMenuItem(
        title: "Quit Leader Key",
        action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"
      ))

    item.menu = menu

    updateStatusItemAppearance()

    Events.sink { event in
      switch event {
      case .willActivate:
        self.appearance = .active
        break
      case .willDeactivate:
        self.appearance = .normal
        break
      default:
        break
      }
    }.store(in: &cancellables)

  }

  func disable() {
    guard let item = statusItem else { return }

    cancellables.removeAll()
    NSStatusBar.system.removeStatusItem(item)
    statusItem = nil
  }

  @objc func showPreferences() {
    handlePreferences?()
  }

  @objc func showAbout() {
    handleAbout?()
  }

  @objc func reloadConfig() {
    handleReloadConfig?()
  }

  @objc func revealConfigFile() {
    handleRevealConfig?()
  }

  @objc func checkForUpdates() {
    handleCheckForUpdates?()
  }

  private func updateStatusItemAppearance() {
    guard let button = statusItem?.button else { return }

    switch appearance {
    case .normal:
      button.image = NSImage(named: NSImage.Name("StatusItem"))
    case .active:
      button.image = NSImage(named: NSImage.Name("StatusItem-filled"))
    }
  }
}
$ sed -n '1,260p' '../Leader Key/Leader Key/Defaults+Extensions.swift' 2>/dev/null || true; rg -n \
../Leader Key/Leader Key/UserConfig.swift:196:    let dir = Defaults[.configDir]
../Leader Key/Leader Key/UserConfig.swift:205:      Defaults[.configDir] = defaultDir
../Leader Key/Leader Key/UserConfig.swift:212:    (Defaults[.configDir] as NSString).appendingPathComponent(fileName)
../Leader Key/Leader Key/AppDelegate.swift:74:      for await value in Defaults.updates(.showMenuBarIcon) {
../Leader Key/Leader Key/AppDelegate.swift:84:    if Defaults[.showMenuBarIcon] {
../Leader Key/Leader Key/Defaults.swift:9:extension Defaults.Keys {
../Leader Key/Leader Key/Defaults.swift:10:  static let configDir = Key<String>(
../Leader Key/Leader Key/Defaults.swift:11:    "configDir", default: UserConfig.defaultDirectory(), suite: defaultsSuite)
../Leader Key/Leader Key/Defaults.swift:12:  static let showMenuBarIcon = Key<Bool>(
../Leader Key/Leader Key/Settings/GeneralPane.swift:10:  @Default(.configDir) var configDir
../Leader Key/Leader Key/Settings/AdvancedPane.swift:12:  @Default(.configDir) var configDir
../Leader Key/Leader Key/Settings/AdvancedPane.swift:27:          Text(configDir).lineLimit(1).truncationMode(.middle)
../Leader Key/Leader Key/Settings/AdvancedPane.swift:37:            configDir = selectedPath.path
../Leader Key/Leader Key/Settings/AdvancedPane.swift:46:            configDir = UserConfig.defaultDirectory()
../Leader Key/Leader Key/Settings/AdvancedPane.swift:148:        Defaults.Toggle("Show Leader Key in menubar", key: .showMenuBarIcon)
$ sed -n '1,260p' '../Leader Key/Leader Key/Settings/AdvancedPane.swift'
import Defaults
import KeyboardShortcuts
import LaunchAtLogin
import Settings
import SwiftUI

struct AdvancedPane: View {
  private let contentWidth = 640.0

  @EnvironmentObject private var config: UserConfig

  @Default(.configDir) var configDir
  @Default(.modifierKeyConfiguration) var modifierKeyConfiguration
  @Default(.autoOpenCheatsheet) var autoOpenCheatsheet
  @Default(.cheatsheetDelayMS) var cheatsheetDelayMS
  @Default(.reactivateBehavior) var reactivateBehavior
  @Default(.showAppIconsInCheatsheet) var showAppIconsInCheatsheet
  @Default(.screen) var screen

  var body: some View {
    Settings.Container(contentWidth: contentWidth) {
      Settings.Section(
        title: "Config directory",
        bottomDivider: true
      ) {
        HStack {
          Text(configDir).lineLimit(1).truncationMode(.middle)
        }
        HStack {
          Button("Choose…") {
            let panel = NSOpenPanel()
            panel.allowsMultipleSelection = false
            panel.canChooseDirectories = true
            panel.canChooseFiles = false
            if panel.runModal() != .OK { return }
            guard let selectedPath = panel.url else { return }
            configDir = selectedPath.path
          }
          Button("Reveal") {
            NSWorkspace.shared.activateFileViewerSelecting([
              config.url
            ])
          }

          Button("Reset") {
            configDir = UserConfig.defaultDirectory()
          }
        }
      }

      Settings.Section(
        title: "Modifier Keys", bottomDivider: true
      ) {
        VStack(alignment: .leading, spacing: 16) {
          HStack {
            Picker("", selection: $modifierKeyConfiguration) {
              ForEach(ModifierKeyConfig.allCases) { config in
                Text(config.description).tag(config)
              }
            }
            .frame(width: 280)
            .labelsHidden()
          }

          VStack(alignment: .leading, spacing: 8) {
            Text(
              "Group Actions: When the modifier key is held while pressing a group key, it runs all actions in that group and its sub-groups."
            )
            .font(.callout)
            .foregroundColor(.secondary)
          }

          VStack(alignment: .leading, spacing: 8) {
            Text(
              "Sticky Mode: When the modifier key is held while triggering an action, Leader Key stays open after the action completes."
            )
            .font(.callout)
            .foregroundColor(.secondary)
          }
        }
        .padding(.top, 2)
      }

      Settings.Section(title: "Cheatsheet", bottomDivider: true) {
        HStack(alignment: .firstTextBaseline) {
          Picker("Show", selection: $autoOpenCheatsheet) {
            Text("Always").tag(AutoOpenCheatsheetSetting.always)
            Text("After …").tag(AutoOpenCheatsheetSetting.delay)
            Text("Never").tag(AutoOpenCheatsheetSetting.never)
          }.frame(width: 120)

          if autoOpenCheatsheet == .delay {
            TextField(
              "", value: $cheatsheetDelayMS, formatter: NumberFormatter()
            )
            .frame(width: 50)
            Text("milliseconds")
          }

          Spacer()
        }

        Text(
          "The cheatsheet can always be manually shown by \"?\" when Leader Key is activated."
        )
        .padding(.vertical, 2)

        Defaults.Toggle(
          "Show expanded groups in cheatsheet", key: .expandGroupsInCheatsheet)
        Defaults.Toggle(
          "Show icons", key: .showAppIconsInCheatsheet)
        Defaults.Toggle(
          "Use favicons for URLs", key: .showFaviconsInCheatsheet
        ).padding(.leading, 20).disabled(!showAppIconsInCheatsheet)
        Defaults.Toggle(
          "Show item details in cheatsheet", key: .showDetailsInCheatsheet)

      }

      Settings.Section(title: "Activation", bottomDivider: true) {
        VStack(alignment: .leading) {
          Text(
            "Pressing the global shortcut key while Leader Key is active should …"
          )

          Picker(
            "Reactivation behavior", selection: $reactivateBehavior
          ) {
            Text("Hide Leader Key").tag(ReactivateBehavior.hide)
            Text("Reset group selection").tag(ReactivateBehavior.reset)
            Text("Do nothing").tag(ReactivateBehavior.nothing)
          }
          .labelsHidden()
          .frame(width: 220)
        }
      }

      Settings.Section(title: "Show Leader Key on", bottomDivider: true) {
        Picker("", selection: $screen) {
          Text("Screen containing mouse").tag(Screen.mouse)
          Text("Primary screen").tag(Screen.primary)
          Text("Screen with active window").tag(Screen.activeWindow)
        }
        .labelsHidden()
        .frame(width: 220)
      }
      Settings.Section(title: "Other") {
        Defaults.Toggle("Show Leader Key in menubar", key: .showMenuBarIcon)
        VStack(alignment: .leading, spacing: 4) {
          Defaults.Toggle(
            "Force English keyboard layout", key: .forceEnglishKeyboardLayout)
          Text(
            "When enabled, letter keys are interpreted in US-English (QWERTY) regardless of your current keyboard layout."
          )
          .font(.caption)
          .foregroundColor(.secondary)
          .fixedSize(horizontal: false, vertical: true)
        }
      }
    }
  }
}

struct AdvancedPane_Previews: PreviewProvider {
  static var previews: some View {
    return AdvancedPane()
    //      .environmentObject(UserConfig())
  }
}
Thinking

Preparing to inspect config and move files

$ sed -n '1,220p' '../Moves/Moves/AppDelegate.swift'; echo '---'; sed -n '1,220p' '../Moves/Moves/StatusItem.swift'; echo '---'; sed -n '1,220p' '../Moves/Moves/Info.plist'
import AXSwift
import Cocoa
import Defaults
import Settings
import Sparkle

extension Settings.PaneIdentifier {
  static let general = Self("general")
  static let excludes = Self("excludes")
}

class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
  let sparkle = SPUStandardUpdaterController(
    startingUpdater: true,
    updaterDelegate: nil,
    userDriverDelegate: nil
  )

  let statusItem = StatusItem()
  let windowHandler = WindowHandler()

  lazy var settingsWindowController = SettingsWindowController(
    panes: [
      Settings.Pane(
        identifier: .general,
        title: "General",
        toolbarIcon: settingsIcon(named: "gearshape")
      ) {
        GeneralSettingsPane()
      },
      Settings.Pane(
        identifier: .excludes,
        title: "Excludes",
        toolbarIcon: settingsIcon(named: "nosign")
      ) {
        ExcludesSettingsPane()
      },
    ],
    style: .segmentedControl
  )

  func applicationDidFinishLaunching(_ aNotification: Notification) {
    setupMainMenu()
    setSettingsActivation(active: false)

    Task {
      for await value in Defaults.updates(.showInMenubar) {
        if value {
          self.statusItem.enable()
        } else {
          self.statusItem.disable()
        }
      }
    }

    statusItem.handleCheckForUpdates = { self.sparkle.checkForUpdates(nil) }
    statusItem.handleSettings = { self.showSettingsWindow() }

    let modifiers = Modifiers { self.windowHandler.intention = $0 }

    Task {
      for await value in Defaults.updates(.accessibilityEnabled) {
        if value {
          modifiers.observe()
        } else {
          modifiers.remove()
        }
      }
    }

    DistributedNotificationCenter.default.addObserver(
      forName: NSNotification.Name("com.apple.accessibility.api"), object: nil, queue: nil
    ) { _ in
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        Defaults[.accessibilityEnabled] = AXSwift.checkIsProcessTrusted()
      }
    }

    Defaults[.accessibilityEnabled] = AXSwift.checkIsProcessTrusted(prompt: true)

    if Defaults[.showSettingsOnLaunch] {
      showSettingsWindow()
    }
  }

  func applicationWillTerminate(_ aNotification: Notification) {
  }

  func applicationDidBecomeActive(_ notification: Notification) {
    showSettingsWindow()
  }

  func windowWillClose(_ notification: Notification) {
    guard let window = notification.object as? NSWindow else { return }
    if window == settingsWindowController.window {
      setSettingsActivation(active: false)
    }
  }

  private func showSettingsWindow() {
    guard !isRunningForPreviews else { return }
    setSettingsActivation(active: true)
    NSApp.activate(ignoringOtherApps: true)
    settingsWindowController.window?.delegate = self
    sanitizeSettingsWindowAutosave()
    settingsWindowController.show()
  }

  private func setSettingsActivation(active: Bool) {
    _ = NSApp.setActivationPolicy(active ? .regular : .accessory)
  }

  private var isRunningForPreviews: Bool {
    ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
  }

  private func sanitizeSettingsWindowAutosave() {
    let key = "NSWindow Frame com.sindresorhus.Settings.FrameAutosaveName"
    guard let stored = UserDefaults.standard.string(forKey: key) else { return }
    guard !stored.contains("{") else { return }
    UserDefaults.standard.removeObject(forKey: key)
  }

  private func settingsIcon(named systemName: String) -> NSImage {
    NSImage(systemSymbolName: systemName, accessibilityDescription: nil) ?? NSImage()
  }

  // MARK: - Main Menu

  private func setupMainMenu() {
    let mainMenu = NSMenu()

    let appMenuItem = NSMenuItem()
    mainMenu.addItem(appMenuItem)

    let appMenu = NSMenu()
    appMenuItem.submenu = appMenu

    let appName = ProcessInfo.processInfo.processName

    appMenu.addItem(
      withTitle: "About \(appName)",
      action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)),
      keyEquivalent: "")

    appMenu.addItem(.separator())

    let settingsItem = NSMenuItem(
      title: "Settings…",
      action: #selector(openSettingsFromMenu),
      keyEquivalent: ",")
    settingsItem.target = self
    appMenu.addItem(settingsItem)

    appMenu.addItem(.separator())

    appMenu.addItem(
      withTitle: "Hide \(appName)",
      action: #selector(NSApplication.hide(_:)),
      keyEquivalent: "h")

    let hideOthers = NSMenuItem(
      title: "Hide Others",
      action: #selector(NSApplication.hideOtherApplications(_:)),
      keyEquivalent: "h")
    hideOthers.keyEquivalentModifierMask = [.command, .option]
    appMenu.addItem(hideOthers)

    appMenu.addItem(
      withTitle: "Show All",
      action: #selector(NSApplication.unhideAllApplications(_:)),
      keyEquivalent: "")

    appMenu.addItem(.separator())

    appMenu.addItem(
      withTitle: "Quit \(appName)",
      action: #selector(NSApplication.terminate(_:)),
      keyEquivalent: "q")

    NSApp.mainMenu = mainMenu
  }

  @objc private func openSettingsFromMenu() {
    showSettingsWindow()
  }

  // MARK: - URLs

  func application(_ application: NSApplication, open urls: [URL]) {
    for url in urls {
      handleURL(url)
    }
  }

  private func handleURL(_ url: URL) {
    guard url.scheme == "moves" else { return }

    guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
      let host = url.host
    else { return }

    let queryItems = components.queryItems ?? []

    switch host {
    case "template":
      let templateName = url.path.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
      guard !templateName.isEmpty else { return }

      guard let templateType = TemplateType(rawValue: templateName) else { return }
      ActiveWindow.applyTemplate(templateType)

    case "custom":
      let positionString: String
      let command = url.path.trimmingCharacters(in: CharacterSet(charactersIn: "/"))

      if let positionParam = queryItems.first(where: { $0.name == "position" })?.value,
        !positionParam.isEmpty
      {
        positionString = positionParam
---
import Cocoa

class StatusItem {
  var statusItem: NSStatusItem?

  var handleSettings: (() -> Void)?
  var handleCheckForUpdates: (() -> Void)?

  func enable() {
    statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)

    guard let item = statusItem else {
      print("No status item")
      return
    }

    if let menubarButton = item.button {
      menubarButton.image = NSImage(named: NSImage.Name("Menubar Icon"))
    }

    let menu = NSMenu()

    let settingsItem = NSMenuItem(
      title: "Settings…", action: #selector(showSettings), keyEquivalent: ",")
    settingsItem.target = self
    menu.addItem(settingsItem)

    let updatesItem = NSMenuItem(
      title: "Check for updates…", action: #selector(checkForUpdates), keyEquivalent: "")
    updatesItem.target = self
    menu.addItem(updatesItem)

    menu.addItem(NSMenuItem.separator())
    menu.addItem(
      NSMenuItem(
        title: "Quit Moves", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))

    item.menu = menu
  }

  func disable() {
    guard let item = statusItem else { return }
    NSStatusBar.system.removeStatusItem(item)
  }

  @objc func showSettings() {
    handleSettings?()
  }

  @objc func checkForUpdates() {
    handleCheckForUpdates?()
  }
}
---
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIconFile</key>
	<string>AppIcon</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
	<key>CFBundleShortVersionString</key>
	<string>$(MARKETING_VERSION)</string>
	<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeRole</key>
			<string>Editor</string>
			<key>CFBundleURLName</key>
			<string>com.brnbw.moves</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>moves</string>
			</array>
		</dict>
	</array>
	<key>CFBundleVersion</key>
	<string>122</string>
	<key>LSApplicationCategoryType</key>
	<string>public.app-category.utilities</string>
	<key>LSUIElement</key>
	<true/>
	<key>LSMinimumSystemVersion</key>
	<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
	<key>NSHumanReadableCopyright</key>
	<string>Copyright © 2020 Brainbow. All rights reserved.</string>
	<key>NSPrincipalClass</key>
	<string>NSApplication</string>
	<key>NSSupportsAutomaticTermination</key>
	<false/>
	<key>NSSupportsSuddenTermination</key>
	<true/>
	<key>SUFeedURL</key>
	<string>https://mikker.github.io/Moves.app/appcast.xml</string>
	<key>SUPublicEDKey</key>
	<string>4ZfPYKvzE7b/uYW4B8f2v++7Bry/OmvYHCzl3g936KE=</string>
</dict>
</plist>
$ sed -n '1,240p' '../Sprinkles/Sprinkles/StatusItem.swift'; echo '---'; sed -n '1,240p' '../Sprinkles/Sprinkles/GeneralPreferencesController.swift'; echo '---'; sed -n '1,220p' '../Sprinkles/Sprinkles/Info.plist'
import Cocoa

class StatusItem: NSObject {
  var statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)

  override func awakeFromNib() {
    statusItem.button?.image = NSImage(named: NSImage.Name("ToolbarItemIcon"))
    statusItem.menu = buildMenu()
  }

  private func buildMenu() -> NSMenu {
    let menu = NSMenu()

    let preferencesItem = NSMenuItem(
      title: "Preferences…", action: #selector(showPreferences), keyEquivalent: ",")
    preferencesItem.target = self
    menu.addItem(preferencesItem)

    let onboardingItem = NSMenuItem(
      title: "Onboarding…", action: #selector(showOnboarding), keyEquivalent: ",")
    onboardingItem.target = self
    onboardingItem.isAlternate = true
    onboardingItem.keyEquivalentModifierMask = .option
    menu.addItem(onboardingItem)

    let directoryItem = NSMenuItem(
      title: "Open directory…", action: #selector(openDirectory), keyEquivalent: "o")
    directoryItem.target = self
    menu.addItem(directoryItem)

    menu.addItem(NSMenuItem.separator())
    menu.addItem(
      NSMenuItem(
        title: "Quit Sprinkles", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
    )

    return menu
  }

  @objc func openDirectory() {
    if let directory = store.state.directory {
      NSWorkspace.shared.open(directory)
    } else {
      showPreferences()
    }
  }

  @objc func showPreferences() {
    guard let delegate = NSApplication.shared.delegate as? AppDelegate else { return }
    delegate.showPreferences()
  }

  @objc func showOnboarding() {
    guard let delegate = NSApplication.shared.delegate as? AppDelegate else { return }
    delegate.showOnboarding()
  }
}
---
import Cocoa
import Defaults
import LaunchAtLogin
import SafariServices
import Settings

class GeneralPreferencesController: NSViewController, SettingsPane {
  var paneIdentifier = Settings.PaneIdentifier.general
  var paneTitle: String = "General"

  override var nibName: NSNib.Name? { "General" }

  @IBOutlet var statusLight: NSButton!
  @IBOutlet var directoryPathControl: NSPathControl!
  @IBOutlet var pickLocationButton: NSButton!
  @IBOutlet var revealButton: NSButton!
  @IBOutlet var showPreferencesOnLaunchCheckbox: NSButton!
  @IBOutlet var launchAtLoginCheckbox: NSButton!
  @IBOutlet var diagnosticsCheckbox: NSButton!
  @IBOutlet var safariButton: NSButton!
  @IBOutlet var firefoxButton: NSButton!
  @IBOutlet var chromeButton: NSButton!

  var unsubscribe: UnsubscribeFn?
  var dockTimer: Timer?

  deinit {
    unsubscribe?()
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    self.preferredContentSize = NSSize(width: 480, height: 403)

    unsubscribe = store.subscribe { state in
      self.directoryPathControl.url = state.directory
      self.revealButton.isEnabled = state.directory != nil

      switch state.serverState {
      case .booting:
        self.statusLight.image = NSImage(named: NSImage.statusPartiallyAvailableName)
        self.statusLight.title = "Booting…"
      case .stopped:
        self.statusLight.image = NSImage(named: NSImage.statusUnavailableName)
        self.statusLight.title = "Stopped"
      case .running:
        self.statusLight.image = NSImage(named: NSImage.statusAvailableName)
        self.statusLight.title = "Running on localhost:3133"
      }
    }

    launchAtLoginCheckbox.state = LaunchAtLogin.isEnabled ? .on : .off
  }

  @IBAction func chooseLocationPressed(_ sender: Any?) {
    OpenPanel.pick { result in
      guard let url = result else { return }

      Bookmark.url = url
      store.dispatch(.setDirectory(url))
    }
  }

  @IBAction func revealButtonPressed(_ sender: Any?) {
    guard let dir = store.state.directory else { return }
    NSWorkspace.shared.open(dir)
  }

  @IBAction func showPreferencesOnLaunchPressed(_ sender: Any?) {
    Defaults[.showPreferencesOnLaunch] = showPreferencesOnLaunchCheckbox.state == .on
  }

  @IBAction func launchAtStartupPressed(_ sender: Any?) {
    LaunchAtLogin.isEnabled = launchAtLoginCheckbox.state == .on
  }

  @IBAction func supportPressed(_ sender: Any?) {
    NSWorkspace.shared.open(URL(string: "https://getsprinkles.app/")!)
  }

  @IBAction func safariPressed(_ sender: Any?) {
    ExtensionLinks.safari()
  }

  @IBAction func firefoxPressed(_ sender: Any?) {
    ExtensionLinks.firefox()
  }

  @IBAction func chromePressed(_ sender: Any?) {
    ExtensionLinks.chrome()
  }

  @IBAction func resetCertsPressed(_ sender: Any?) {
    SprinklesCertificate.destroy()
    Defaults[.hasOnboarded] = false
    NSApp.relaunch()
  }
}
---
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>$(MARKETING_VERSION)</string>
	<key>CFBundleVersion</key>
	<string>$(CURRENT_PROJECT_VERSION)</string>
	<key>LSApplicationCategoryType</key>
	<string>public.app-category.utilities</string>
	<key>LSMinimumSystemVersion</key>
	<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
	<key>LSUIElement</key>
	<true/>
	<key>NSHumanReadableCopyright</key>
	<string>Copyright © 2019 Brainbow. All rights reserved.</string>
	<key>NSMainStoryboardFile</key>
	<string>Main</string>
	<key>NSPrincipalClass</key>
	<string>NSApplication</string>
</dict>
</plist>
$ sed -n '1,220p' '../FlickRing/FlickRing/AppDelegate.swift'; echo '---'; sed -n '1,220p' '../FlickRing/FlickRing/StatusItem.swift'; echo '---'; sed -n '1,220p' '../FlickRing/FlickRing/Info.plist'
import Cocoa
import Defaults
import Settings
import Sparkle
import SwiftUI

@main
class AppDelegate: NSObject, NSApplicationDelegate, SPUStandardUserDriverDelegate {
  @IBOutlet var updateController: SPUStandardUpdaterController!

  var statusItem: StatusItem!
  var controller: Controller!
  var userState: UserState!

  lazy var settingsWindowController = SettingsWindowController(
    panes: [
      Settings.Pane(
        identifier: .general, title: "General",
        toolbarIcon: NSImage(named: NSImage.preferencesGeneralName)!,
        contentView: { GeneralPane() }
      )
    ]
  )

  func applicationDidFinishLaunching(_ aNotification: Notification) {
    // Check if running in preview mode
    #if DEBUG
      if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
        return  // Exit early if running in preview
      }
    #endif

    userState = UserState()
    controller = Controller(userState: userState)
    statusItem = StatusItem()

    statusItem.handlePreferences = {
      self.settingsWindowController.show()
    }
    statusItem.handleUpdates = {
      self.updateController.checkForUpdates(nil)
    }

    statusItem.enable()

    if AXIsProcessTrusted() {
      startListeningForMouseEvents()
    } else {
      requestAccessibilityPermission()
    }

    if Defaults[.showPreferencesOnLaunch] {
      settingsWindowController.show()
    }

    NotificationCenter.default.addObserver(
      self,
      selector: #selector(handleConfigurationStateChange),
      name: .configurationStateChanged,
      object: nil
    )
  }

  func startListeningForMouseEvents() {
    controller.startListeningForMouseEvents()
  }

  func requestAccessibilityPermission() {
    let promptKey = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String
    let opts = [promptKey: true] as CFDictionary
    let accessibilityEnabled = AXIsProcessTrustedWithOptions(opts)

    if accessibilityEnabled {
      startListeningForMouseEvents()
    } else {
      showAlertForAccessibilityPermission()
    }
  }

  func showAlertForAccessibilityPermission() {
    let alert = NSAlert()
    alert.messageText = "Accessibility Permission Required"
    alert.informativeText = "Please enable accessibility permissions in System Preferences."
    alert.alertStyle = .warning
    alert.addButton(withTitle: "OK")
    alert.addButton(withTitle: "Open System Preferences")

    let response = alert.runModal()
    if response == .alertSecondButtonReturn {
      openSystemPreferences()
    }
  }

  func openSystemPreferences() {
    if let url = URL(
      string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility")
    {
      NSWorkspace.shared.open(url)
    }
  }

  @IBAction
  func settingsMenuItemActionHandler(_: NSMenuItem) {
    settingsWindowController.show()
  }

  @objc func handleConfigurationStateChange(_ notification: Notification) {
    if let isConfiguring = notification.object as? Bool {
      controller.setConfiguring(isConfiguring)
    }
  }

  // MARK: SPUStandardUserDriverDelegate

  var supportsGentleScheduledUpdateReminders: Bool = true
}

extension Notification.Name {
  static let mouseButtonSelected = Notification.Name("mouseButtonSelected")
  static let configurationStateChanged = Notification.Name("configurationStateChanged")
}
---
import Cocoa

class StatusItem {
  var statusItem: NSStatusItem?

  var handlePreferences: (() -> Void)?
  var handleUpdates: (() -> Void)?

  func enable() {
    statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)

    guard let item = statusItem else {
      print("No status item")
      return
    }

    if let menubarButton = item.button {
      menubarButton.image = NSImage(named: NSImage.Name("StatusItem"))
    }

    let menu = NSMenu()

    let preferencesItem = NSMenuItem(
      title: "Preferences…", action: #selector(showPreferences), keyEquivalent: ","
    )
    preferencesItem.target = self
    menu.addItem(preferencesItem)

    let updatesItem = NSMenuItem(
      title: "Check for updates", action: #selector(checkForUpdates), keyEquivalent: ""
    )
    updatesItem.target = self
    menu.addItem(updatesItem)

    menu.addItem(NSMenuItem.separator())

    menu.addItem(
      NSMenuItem(
        title: "Quit Flick Ring", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"
      ))

    item.menu = menu
  }

  @objc func showPreferences() {
    handlePreferences?()
  }

  @objc func checkForUpdates() {
    handleUpdates?()
  }
}
---
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>SUFeedURL</key>
	<string>https://flick-ring-updates.s3.amazonaws.com/appcast.xml</string>
	<key>SUPublicEDKey</key>
	<string>4ZfPYKvzE7b/uYW4B8f2v++7Bry/OmvYHCzl3g936KE=</string>
</dict>
</plist>
Loading more...