← 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
M desktop-bar/README.mdM desktop-bar/TicketBar.swift
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 →