riffer-rig 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 613c884d96e252c8793e442b39cf843d5c91438fc7d6ae6bc963536cb978de09
4
- data.tar.gz: f4e2596e6c67e87f19011afa452c207f5f6e8b0394673c58e1ae22a57e2f2269
3
+ metadata.gz: e1743d133b2e91f50f10a376bbb8cd6f2d21f4d6c2f90600d5415d4ec9047b26
4
+ data.tar.gz: c8131bfda29574b5c86acb1b1ebebfe0ec1dc5cc82ff7c8c8cca5410bb7b34d0
5
5
  SHA512:
6
- metadata.gz: 1d1b670b735b7d06b7b88d38c305e09018db879ca5ffe1a65a89f1424ae25b9d324b234bb3d38e89889305f85cb1f97822d2a76fba083f83e76f256cec6de331
7
- data.tar.gz: 77c745faeb40661a33801885cee13d7811b05c9e557e5798a5ebb6233aef18e7c74e194f4fc9b4347cac4ff0b8182ca8c42b2e70fda71a051ee13849dbdea8f3
6
+ metadata.gz: 46930dae154e4a07916c0f1391e3f44419a7e385f5d31f02509a9740276ca6bfc993efb4f0e95695ad439045f69d9c3a74e344a43af7f83a75afdb6bd94f1e58
7
+ data.tar.gz: 18ce4fe1003d1767a234936fae81fb921c49461cefb7f6516bf051047734b68580bfc124803d353296a71daa9919f5f77805c83bc3c2599c9e17e8b2650b4b1d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.15.0](https://github.com/bottrall/riffer-rig/compare/v0.14.0...v0.15.0) (2026-10-18)
4
+
5
+
6
+ ### Features
7
+
8
+ * **terminal:** style the turn report like the status bar ([#562](https://github.com/bottrall/riffer-rig/issues/562)) ([710bd34](https://github.com/bottrall/riffer-rig/commit/710bd34b993d2743a78935ebbf3116d50cc063d7))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * label the status-bar step counter as steps, not turns ([#560](https://github.com/bottrall/riffer-rig/issues/560)) ([80e6b2b](https://github.com/bottrall/riffer-rig/commit/80e6b2b60756ba4896878ef7df736511a3ec2ed0))
14
+
3
15
  ## [0.14.0](https://github.com/bottrall/riffer-rig/compare/v0.13.0...v0.14.0) (2026-10-18)
4
16
 
5
17
 
@@ -83,7 +83,7 @@ class Riffer::Rig::Terminal::Ticker
83
83
  estimate = @unmetered / CHARS_PER_TOKEN
84
84
  Riffer::Rig::Terminal::StatusBar.working(
85
85
  glyph: Riffer::Rig::Terminal::Spinner.call(@tick),
86
- state: stats.steps.zero? ? '' : "turn #{stats.steps}",
86
+ state: stats.steps.zero? ? '' : "step #{stats.steps}",
87
87
  clock: clock(stats.wall_time),
88
88
  up_tokens: usage&.input_tokens || 0,
89
89
  down_tokens: (usage&.output_tokens || 0) + estimate,
@@ -9,14 +9,21 @@ module Riffer::Rig::Terminal::TurnReport
9
9
 
10
10
  COLUMN_HEAD = /\A.*\S {2,}(?=\S)/ #: Regexp
11
11
 
12
+ VISIBLE = /\S/ #: Regexp
13
+
12
14
  # @rbs turn: Riffer::Rig::Terminal::TurnReport::_Turn
13
15
  # @rbs session: Riffer::Providers::TokenUsage?
14
16
  # @rbs theme: Riffer::Rig::Terminal::Theme
15
17
  # @rbs width: Integer?
16
18
  # @rbs return: String
17
19
  def call(turn, session, theme:, width:)
18
- closing = turn.interrupted? ? INTERRUPTED : footer(turn.stats, session)
19
- blocks = [*answer(turn.answer, theme: theme, width: width), theme.dim(wrap(closing, width))]
20
+ closing =
21
+ if turn.interrupted?
22
+ theme.dim(wrap(INTERRUPTED, width))
23
+ else
24
+ footer(turn.stats, session, theme: theme, width: width)
25
+ end
26
+ blocks = [*answer(turn.answer, theme: theme, width: width), closing]
20
27
  blocks.map { |block| "\n#{block}\n" }.join
21
28
  end
22
29
 
@@ -68,33 +75,108 @@ module Riffer::Rig::Terminal::TurnReport
68
75
 
69
76
  # @rbs stats: Riffer::Rig::Terminal::TurnBuffer::Stats
70
77
  # @rbs session: Riffer::Providers::TokenUsage?
78
+ # @rbs theme: Riffer::Rig::Terminal::Theme
79
+ # @rbs width: Integer?
71
80
  # @rbs return: String
72
- def footer(stats, session)
81
+ def footer(stats, session, theme:, width:)
82
+ parts = segments(stats, session)
83
+ paint(wrap(parts.map(&:first).join(' · '), width), owners(parts), theme)
84
+ end
85
+
86
+ # @rbs stats: Riffer::Rig::Terminal::TurnBuffer::Stats
87
+ # @rbs session: Riffer::Providers::TokenUsage?
88
+ # @rbs return: Array[[String, Symbol]]
89
+ def segments(stats, session)
73
90
  usage = stats.usage
74
91
  minutes, seconds = stats.wall_time.to_i.divmod(60)
75
92
  [
76
- "#{stats.steps} #{stats.steps == 1 ? 'step' : 'steps'}",
77
- format('%<minutes>02d:%<seconds>02d', minutes: minutes, seconds: seconds),
78
- *([*tokens(usage), *cost(usage)] if usage),
79
- *(["session #{session.total_tokens} tok", *cost(session)] if session)
80
- ].join(' · ')
93
+ ["#{stats.steps} #{stats.steps == 1 ? 'step' : 'steps'}", :dim],
94
+ [format('%<minutes>02d:%<seconds>02d', minutes: minutes, seconds: seconds), :dim],
95
+ *usage_segments(usage),
96
+ *session_segments(session)
97
+ ]
98
+ end
99
+
100
+ # @rbs usage: Riffer::Providers::TokenUsage?
101
+ # @rbs return: Array[[String, Symbol]]
102
+ def usage_segments(usage)
103
+ return [] unless usage
104
+
105
+ [*tokens(usage), *cost(usage)]
106
+ end
107
+
108
+ # @rbs session: Riffer::Providers::TokenUsage?
109
+ # @rbs return: Array[[String, Symbol]]
110
+ def session_segments(session)
111
+ return [] unless session
112
+
113
+ [["session #{Riffer::Rig::Terminal::Count.call(session.total_tokens)} tok", :dim], *cost(session)]
81
114
  end
82
115
 
83
116
  # @rbs usage: Riffer::Providers::TokenUsage
84
- # @rbs return: Array[String]
117
+ # @rbs return: Array[[String, Symbol]]
85
118
  def tokens(usage)
86
119
  cache = { cache_write: usage.cache_write_tokens, cache_read: usage.cache_read_tokens }
87
120
  [
88
- "↑#{usage.input_tokens}",
89
- "↓#{usage.output_tokens}",
90
- *cache.filter_map { |name, count| "#{name}:#{count}" if count&.positive? }
121
+ ["↑#{Riffer::Rig::Terminal::Count.call(usage.input_tokens)}", :dim],
122
+ ["↓#{Riffer::Rig::Terminal::Count.call(usage.output_tokens)}", :dim],
123
+ *cache.filter_map do |name, count|
124
+ ["#{name}:#{Riffer::Rig::Terminal::Count.call(count)}", :dim] if count&.positive?
125
+ end
91
126
  ]
92
127
  end
93
128
 
94
129
  # @rbs usage: Riffer::Providers::TokenUsage
95
- # @rbs return: String?
130
+ # @rbs return: Array[[String, Symbol]]
96
131
  def cost(usage)
97
132
  amount = usage.cost
98
- format('~$%.4f', amount) if amount
133
+ amount ? [[format('~$%.4f', amount), :cyan]] : []
134
+ end
135
+
136
+ # A separator's dot is dim, so a colour run carries across it into the next
137
+ # segment.
138
+ # @rbs parts: Array[[String, Symbol]]
139
+ # @rbs return: Array[Symbol]
140
+ def owners(parts)
141
+ parts.flat_map.with_index do |(text, role), index|
142
+ owned = [role] * text.scan(VISIBLE).size
143
+ owned << :dim unless index == parts.length - 1
144
+ owned
145
+ end
146
+ end
147
+
148
+ # The Wrapper moves, drops and rewrites whitespace but never a visible
149
+ # character, so the nth visible character of a wrapped line is the nth one
150
+ # the segments supplied — which is why the colours are painted after the
151
+ # wrap, where a span can never be cut in two.
152
+ # @rbs line: String
153
+ # @rbs owners: Array[Symbol]
154
+ # @rbs theme: Riffer::Rig::Terminal::Theme
155
+ # @rbs return: String
156
+ def paint(line, owners, theme)
157
+ chars = line.chars
158
+ owner_at = chars.each_index.select { |index| chars.fetch(index).match?(VISIBLE) }.zip(owners).to_h
159
+ slots = chars.each_index.map { |index| owner_at[index] }
160
+ cells = nearest(slots).zip(nearest(slots.reverse).reverse).map { |left, right| left == right ? left : nil }
161
+ runs = cells.zip(chars).chunk_while { |(left, _), (right, _)| left == right }
162
+ runs.map { |run| color(run.map(&:last).join, run.fetch(0).first, theme) }.join
163
+ end
164
+
165
+ # @rbs slots: Array[Symbol?]
166
+ # @rbs return: Array[Symbol?]
167
+ def nearest(slots)
168
+ slots.each_with_object([]) { |slot, filled| filled << (slot || filled.last) }
169
+ end
170
+
171
+ # @rbs text: String
172
+ # @rbs role: Symbol?
173
+ # @rbs theme: Riffer::Rig::Terminal::Theme
174
+ # @rbs return: String
175
+ def color(text, role, theme)
176
+ case role
177
+ when :dim then theme.dim(text)
178
+ when :cyan then theme.cyan(text)
179
+ else text
180
+ end
99
181
  end
100
182
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Riffer
4
4
  module Rig
5
- VERSION = '0.14.0'
5
+ VERSION = '0.15.0'
6
6
  end
7
7
  end
@@ -7,6 +7,8 @@ module Riffer::Rig::Terminal::TurnReport
7
7
 
8
8
  COLUMN_HEAD: Regexp
9
9
 
10
+ VISIBLE: Regexp
11
+
10
12
  # @rbs turn: Riffer::Rig::Terminal::TurnReport::_Turn
11
13
  # @rbs session: Riffer::Providers::TokenUsage?
12
14
  # @rbs theme: Riffer::Rig::Terminal::Theme
@@ -39,14 +41,55 @@ module Riffer::Rig::Terminal::TurnReport
39
41
 
40
42
  # @rbs stats: Riffer::Rig::Terminal::TurnBuffer::Stats
41
43
  # @rbs session: Riffer::Providers::TokenUsage?
44
+ # @rbs theme: Riffer::Rig::Terminal::Theme
45
+ # @rbs width: Integer?
42
46
  # @rbs return: String
43
- def footer: (Riffer::Rig::Terminal::TurnBuffer::Stats stats, Riffer::Providers::TokenUsage? session) -> String
47
+ def footer: (Riffer::Rig::Terminal::TurnBuffer::Stats stats, Riffer::Providers::TokenUsage? session, theme: Riffer::Rig::Terminal::Theme, width: Integer?) -> String
48
+
49
+ # @rbs stats: Riffer::Rig::Terminal::TurnBuffer::Stats
50
+ # @rbs session: Riffer::Providers::TokenUsage?
51
+ # @rbs return: Array[[String, Symbol]]
52
+ def segments: (Riffer::Rig::Terminal::TurnBuffer::Stats stats, Riffer::Providers::TokenUsage? session) -> Array[[ String, Symbol ]]
53
+
54
+ # @rbs usage: Riffer::Providers::TokenUsage?
55
+ # @rbs return: Array[[String, Symbol]]
56
+ def usage_segments: (Riffer::Providers::TokenUsage? usage) -> Array[[ String, Symbol ]]
57
+
58
+ # @rbs session: Riffer::Providers::TokenUsage?
59
+ # @rbs return: Array[[String, Symbol]]
60
+ def session_segments: (Riffer::Providers::TokenUsage? session) -> Array[[ String, Symbol ]]
44
61
 
45
62
  # @rbs usage: Riffer::Providers::TokenUsage
46
- # @rbs return: Array[String]
47
- def tokens: (Riffer::Providers::TokenUsage usage) -> Array[String]
63
+ # @rbs return: Array[[String, Symbol]]
64
+ def tokens: (Riffer::Providers::TokenUsage usage) -> Array[[ String, Symbol ]]
48
65
 
49
66
  # @rbs usage: Riffer::Providers::TokenUsage
50
- # @rbs return: String?
51
- def cost: (Riffer::Providers::TokenUsage usage) -> String?
67
+ # @rbs return: Array[[String, Symbol]]
68
+ def cost: (Riffer::Providers::TokenUsage usage) -> Array[[ String, Symbol ]]
69
+
70
+ # A separator's dot is dim, so a colour run carries across it into the next
71
+ # segment.
72
+ # @rbs parts: Array[[String, Symbol]]
73
+ # @rbs return: Array[Symbol]
74
+ def owners: (Array[[ String, Symbol ]] parts) -> Array[Symbol]
75
+
76
+ # The Wrapper moves, drops and rewrites whitespace but never a visible
77
+ # character, so the nth visible character of a wrapped line is the nth one
78
+ # the segments supplied — which is why the colours are painted after the
79
+ # wrap, where a span can never be cut in two.
80
+ # @rbs line: String
81
+ # @rbs owners: Array[Symbol]
82
+ # @rbs theme: Riffer::Rig::Terminal::Theme
83
+ # @rbs return: String
84
+ def paint: (String line, Array[Symbol] owners, Riffer::Rig::Terminal::Theme theme) -> String
85
+
86
+ # @rbs slots: Array[Symbol?]
87
+ # @rbs return: Array[Symbol?]
88
+ def nearest: (Array[Symbol?] slots) -> Array[Symbol?]
89
+
90
+ # @rbs text: String
91
+ # @rbs role: Symbol?
92
+ # @rbs theme: Riffer::Rig::Terminal::Theme
93
+ # @rbs return: String
94
+ def color: (String text, Symbol? role, Riffer::Rig::Terminal::Theme theme) -> String
52
95
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: riffer-rig
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Bottrall