[object Object]

← back to Ticket System

Add CPU readout and RAM Sniper control to desktop bar

1d532474d1f8a47318eb2839fd21458d1351cd66 · 2026-09-08 13:57:31 -0700 · Steve Abrams

Files touched

Diff

commit 1d532474d1f8a47318eb2839fd21458d1351cd66
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Sep 8 13:57:31 2026 -0700

    Add CPU readout and RAM Sniper control to desktop bar
---
 desktop-bar/README.md       |  5 ++++
 desktop-bar/TicketBar.swift | 57 ++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 61 insertions(+), 1 deletion(-)

diff --git a/desktop-bar/README.md b/desktop-bar/README.md
index cad2bfc2..ad930fda 100644
--- a/desktop-bar/README.md
+++ b/desktop-bar/README.md
@@ -4,6 +4,11 @@ Native read-only macOS bar showing exact ticket counts, recently active `doing`
 tickets, and the newest ledger event. It sits below the menu bar across Spaces;
 click **Open Fleet** to open the full ticket viewer.
 
+The top bar also shows a live aggregate CPU percentage and a `RAM Sniper · ON/OFF`
+button. The button starts or stops `/Users/macstudio3/bin/ram-sniper.sh`, which
+deprioritizes background CPU/RAM workloads while keeping interactive Claude and
+terminal processes responsive.
+
 Build with `./build.sh`. The launch agent uses the compiled binary in `bin/`.
 
 ## Layout controls
diff --git a/desktop-bar/TicketBar.swift b/desktop-bar/TicketBar.swift
index b0fc2d3c..d760c82b 100644
--- a/desktop-bar/TicketBar.swift
+++ b/desktop-bar/TicketBar.swift
@@ -73,12 +73,14 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
   private let doingLabel = NSTextField(labelWithString: "DOING …")
   private let working = NSTextField(labelWithString: "Working · —")
   private let latest = NSTextField(labelWithString: "Latest · —")
+  private let cpuLabel = NSTextField(labelWithString: "CPU · —")
   private var stack: NSStackView!
   private var tiles: [BarTile] = []
   private var openButton: NSButton!
   private var themeButton: NSButton!
   private var posButton: NSButton!
   private var dotsButton: NSButton!
+  private var sniperButton: NSButton!
   private var hamburger: NSButton!
   private var isNight = true
   private var barPosition = "top"                 // top | left | right | off
@@ -109,6 +111,7 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
     NSApp.setActivationPolicy(.accessory)
     buildPanel()
     refresh()
+    refreshSystem()
     timer = Timer.scheduledTimer(withTimeInterval: 4, repeats: true) { [weak self] _ in self?.refresh() }
     NotificationCenter.default.addObserver(self, selector: #selector(reposition), name: NSApplication.didChangeScreenParametersNotification, object: nil)
   }
@@ -171,7 +174,15 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
     dotsButton.bezelStyle = .inline
     dotsButton.isBordered = false
     dotsButton.toolTip = "Terminal dots"
-    stack = NSStackView(views: tiles + [open, themeButton, posButton, dotsButton])
+    cpuLabel.font = .monospacedDigitSystemFont(ofSize: 12, weight: .medium)
+    cpuLabel.lineBreakMode = .byTruncatingTail
+    cpuLabel.alignment = .center
+    cpuLabel.widthAnchor.constraint(equalToConstant: 112).isActive = true
+    sniperButton = NSButton(title: "RAM Sniper · ON", target: self, action: #selector(toggleRamSniper))
+    sniperButton.bezelStyle = .inline
+    sniperButton.toolTip = "Toggle the background CPU/RAM priority sniper"
+    sniperButton.setContentHuggingPriority(.required, for: .horizontal)
+    stack = NSStackView(views: tiles + [cpuLabel, sniperButton, open, themeButton, posButton, dotsButton])
     stack.orientation = .horizontal
     stack.spacing = 6
     stack.alignment = .centerY
@@ -346,8 +357,11 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
                                     : NSColor(calibratedRed: 0.10, green: 0.45, blue: 0.82, alpha: 1)
     latest.textColor       = night ? NSColor(calibratedWhite: 0.72, alpha: 1)
                                     : NSColor(calibratedWhite: 0.30, alpha: 1)
+    cpuLabel.textColor     = night ? NSColor(calibratedRed: 0.45, green: 0.90, blue: 0.65, alpha: 1)
+                                    : NSColor(calibratedRed: 0.08, green: 0.42, blue: 0.24, alpha: 1)
     openButton?.contentTintColor = night ? .white : NSColor(calibratedWhite: 0.15, alpha: 1)
     themeButton?.title = night ? "🌙" : "☀️"
+    sniperButton?.contentTintColor = night ? NSColor(calibratedRed: 0.45, green: 0.90, blue: 0.65, alpha: 1) : NSColor(calibratedRed: 0.08, green: 0.42, blue: 0.24, alpha: 1)
     panel.invalidateShadow()
     panel.contentView?.needsDisplay = true
   }
@@ -553,6 +567,46 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
 
   @objc private func openViewer() { NSWorkspace.shared.open(viewerURL) }
 
+  // ── live CPU + RAM Sniper control ─────────────────────────────────────────
+  private func runCommand(_ executable: String, _ args: [String]) -> String {
+    let task = Process(); task.executableURL = URL(fileURLWithPath: executable); task.arguments = args
+    let pipe = Pipe(); task.standardOutput = pipe; task.standardError = Pipe()
+    do { try task.run(); task.waitUntilExit() } catch { return "" }
+    return String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
+  }
+
+  private func refreshSystem() {
+    DispatchQueue.global(qos: .utility).async { [weak self] in
+      guard let self else { return }
+      let top = self.runCommand("/usr/bin/top", ["-l", "1", "-n", "0", "-s", "0"])
+      let cpuText: String
+      if let line = top.split(separator: "\n").first(where: { $0.contains("CPU usage") }),
+         let user = line.split(separator: ",").first(where: { $0.contains("user") }),
+         let sys = line.split(separator: ",").first(where: { $0.contains("sys") }) {
+        let number: (Substring) -> Double = { part in Double(part.split(separator: "%").first?.split(separator: " ").last ?? "0") ?? 0 }
+        cpuText = String(format: "CPU · %.0f%%", number(user) + number(sys))
+      } else { cpuText = "CPU · —" }
+      let sniper = !self.runCommand("/usr/bin/pgrep", ["-f", "/Users/macstudio3/bin/ram-sniper.sh"]).trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
+      DispatchQueue.main.async {
+        self.cpuLabel.stringValue = cpuText
+        self.sniperButton.title = sniper ? "RAM Sniper · ON" : "RAM Sniper · OFF"
+        self.sniperButton.contentTintColor = sniper ? NSColor.systemGreen : NSColor.systemOrange
+      }
+    }
+  }
+
+  @objc private func toggleRamSniper() {
+    let running = !runCommand("/usr/bin/pgrep", ["-f", "/Users/macstudio3/bin/ram-sniper.sh"]).trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
+    if running {
+      _ = runCommand("/usr/bin/pkill", ["-TERM", "-f", "/Users/macstudio3/bin/ram-sniper.sh"])
+    } else {
+      let task = Process(); task.executableURL = URL(fileURLWithPath: "/bin/zsh")
+      task.arguments = ["-lc", "nohup /Users/macstudio3/bin/ram-sniper.sh >/tmp/ram-sniper.stdout 2>&1 &"]
+      try? task.run()
+    }
+    DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { [weak self] in self?.refreshSystem() }
+  }
+
   // ── ◉ Terminal-dots dropdown ────────────────────────────────────────────────
   // SELECTION MODEL: click-to-jump (the robust fallback). AppKit does not support
   // keeping an NSMenu open across checkbox toggles without private API / custom
@@ -794,6 +848,7 @@ private final class BarController: NSObject, NSApplicationDelegate, NSWindowDele
   }
 
   private func refresh() {
+    refreshSystem()
     DispatchQueue.global(qos: .utility).async { [weak self] in
       guard let self else { return }
       do {

← 7b55f532 Record ordered monitoring cycle with three unresolved source  ·  back to Ticket System  ·  Color RAM Sniper button by state 00953494 →