diff options
Diffstat (limited to 'menubar/model.swift')
| -rw-r--r-- | menubar/model.swift | 47 |
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) } |
