aboutsummaryrefslogtreecommitdiff
path: root/menubar/model.swift
diff options
context:
space:
mode:
Diffstat (limited to 'menubar/model.swift')
-rw-r--r--menubar/model.swift47
1 files changed, 42 insertions, 5 deletions
diff --git a/menubar/model.swift b/menubar/model.swift
index 3ed1a7e..d0bbd7e 100644
--- a/menubar/model.swift
+++ b/menubar/model.swift
@@ -97,6 +97,8 @@ struct Row: Equatable {
var draft: String = ""
/// The user looks at this pane: its window is active in an attached session.
var attended: Bool = false
+ /// What its session spent today (UTC), in dollars (`@dingger_cost`).
+ var cost: Double = 0
var location: String { "\(session) · \(window)" }
/// A draft left behind: where the user looks, they may be typing it.
@@ -116,10 +118,11 @@ struct Row: Equatable {
var markedLabel: String { (importance == .high ? "! " : "") + (forgottenDraft ? "✎ " : "") + label }
/// Dim second line: where it runs, which harness, whether it is low,
- /// and how long it waits.
+ /// what it spent today, and how long it waits.
var subtitle: String {
var parts = [location, agent]
if importance == .low { parts.append("low") }
+ if cost >= 0.005 { parts.append(dollars(cost)) }
if waiting, since != Int.max {
parts.append("\(status.rawValue) \(elapsed(sinceMs: since))")
}
@@ -127,6 +130,39 @@ struct Row: Equatable {
}
}
+/// "$0.42", "$19.85"; whole dollars from $100: "$1,967".
+func dollars(_ amount: Double) -> String {
+ if amount < 99.995 { return String(format: "$%.2f", amount) }
+ let formatter = NumberFormatter()
+ formatter.numberStyle = .decimal
+ formatter.locale = Locale(identifier: "en_US")
+ formatter.maximumFractionDigits = 0
+ return "$" + (formatter.string(from: NSNumber(value: amount.rounded())) ?? String(Int(amount.rounded())))
+}
+
+/// What pi agents spent today, this week, and this month (UTC), closed
+/// agents included, as the daemon counts it (`@dingger_cost_today`, ...).
+struct Spend: Equatable {
+ let today: Double
+ let week: Double
+ let month: Double
+
+ var isEmpty: Bool { today < 0.005 && week < 0.005 && month < 0.005 }
+
+ /// "$8.86 today · $457 this week · $8.86 this month"
+ var line: String {
+ "\(dollars(today)) today · \(dollars(week)) this week · \(dollars(month)) this month"
+ }
+}
+
+func fetchSpend() -> Spend {
+ let amount = { (name: String) in
+ Double(tmux(["show-options", "-gv", name])?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "") ?? 0
+ }
+ return Spend(today: amount("@dingger_cost_today"), week: amount("@dingger_cost_week"),
+ month: amount("@dingger_cost_month"))
+}
+
/// Compact age: "now", "4m", "2h", "3d".
func elapsed(sinceMs: Int) -> String {
let seconds = max(0, Int(Date().timeIntervalSince1970) - sinceMs / 1000)
@@ -290,26 +326,27 @@ func fetchRows() -> [Row] {
let format = [
"#{pane_id}", "#{@agent}", "#{@agent_state}", "#{@dingger_since}", "#{@agent_name}",
"#{session_name}", "#{window_name}", "#{window_panes}", "#{pane_index}", "#{pane_current_path}",
- "#{@dingger_importance}", "#{window_active}", "#{session_attached}", "#{@dingger_draft}",
+ "#{@dingger_importance}", "#{window_active}", "#{session_attached}", "#{@dingger_draft}", "#{@dingger_cost}",
].joined(separator: "\t")
guard let out = tmux(["list-panes", "-a", "-F", format]) else { return [] }
let lines = out.split(separator: "\n").map {
$0.split(separator: "\t", omittingEmptySubsequences: false).map(String.init)
}
// Looked at in any of its sessions, as the daemon counts it.
- let attended = Set(lines.filter { $0.count >= 14 && $0[11] == "1" && $0[12] != "0" }.map { $0[0] })
+ let attended = Set(lines.filter { $0.count >= 15 && $0[11] == "1" && $0[12] != "0" }.map { $0[0] })
// A pane in several sessions (a session group, a linked window) is
// listed once, under its first session, as the daemon counts it.
var listed = Set<String>()
let rows: [Row] = lines.compactMap { f in
- guard f.count >= 14, !f[1].isEmpty, listed.insert(f[0]).inserted else { return nil }
+ guard f.count >= 15, !f[1].isEmpty, listed.insert(f[0]).inserted else { return nil }
let status = Status(rawValue: f[2]) ?? .unknown
guard status != .unknown else { return nil }
let window = (Int(f[7]) ?? 1) > 1 ? "\(f[6]).\(f[8])" : f[6]
return Row(paneId: f[0], agent: f[1], name: f[4], status: status,
session: f[5], window: window, since: Int(f[3]) ?? Int.max,
path: f[9].hasPrefix(home) ? "~" + f[9].dropFirst(home.count) : f[9],
- importance: Importance(option: f[10]), draft: f[13], attended: attended.contains(f[0]))
+ importance: Importance(option: f[10]), draft: f[13], attended: attended.contains(f[0]),
+ cost: Double(f[14]) ?? 0)
}
return rows.sorted(by: listedBefore)
}