riffer-rig 0.2.0 → 0.3.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/README.md +2 -2
  4. data/lib/riffer/rig/cli.rb +21 -1
  5. data/lib/riffer/rig/coding_agent.rb +7 -5
  6. data/lib/riffer/rig/credentials.rb +24 -4
  7. data/lib/riffer/rig/repl.rb +70 -5
  8. data/lib/riffer/rig/settings/pricing.rb +19 -0
  9. data/lib/riffer/rig/settings.rb +33 -8
  10. data/lib/riffer/rig/token_tally.rb +22 -6
  11. data/lib/riffer/rig/tools/bash.rb +19 -4
  12. data/lib/riffer/rig/tools/edit.rb +6 -0
  13. data/lib/riffer/rig/tools/read.rb +6 -1
  14. data/lib/riffer/rig/tools/write.rb +4 -0
  15. data/lib/riffer/rig/ui/animator.rb +51 -11
  16. data/lib/riffer/rig/ui/banner.rb +28 -4
  17. data/lib/riffer/rig/ui/cursor.rb +46 -0
  18. data/lib/riffer/rig/ui/palette.rb +13 -7
  19. data/lib/riffer/rig/ui/renderer.rb +38 -7
  20. data/lib/riffer/rig/ui/smoother.rb +31 -5
  21. data/lib/riffer/rig/ui/theme.rb +38 -7
  22. data/lib/riffer/rig/version.rb +1 -1
  23. data/sig/generated/riffer/rig/cli.rbs +41 -0
  24. data/sig/generated/riffer/rig/coding_agent.rbs +9 -0
  25. data/sig/generated/riffer/rig/credentials.rbs +55 -0
  26. data/sig/generated/riffer/rig/repl.rbs +76 -0
  27. data/sig/generated/riffer/rig/settings/pricing.rbs +23 -0
  28. data/sig/generated/riffer/rig/settings.rbs +92 -0
  29. data/sig/generated/riffer/rig/token_tally.rbs +61 -0
  30. data/sig/generated/riffer/rig/tools/bash.rbs +28 -0
  31. data/sig/generated/riffer/rig/tools/edit.rbs +11 -0
  32. data/sig/generated/riffer/rig/tools/read.rbs +12 -0
  33. data/sig/generated/riffer/rig/tools/write.rbs +9 -0
  34. data/sig/generated/riffer/rig/ui/animator.rbs +76 -0
  35. data/sig/generated/riffer/rig/ui/banner.rbs +42 -0
  36. data/sig/generated/riffer/rig/ui/cursor.rbs +33 -0
  37. data/sig/generated/riffer/rig/ui/palette.rbs +17 -0
  38. data/sig/generated/riffer/rig/ui/renderer.rbs +68 -0
  39. data/sig/generated/riffer/rig/ui/smoother.rbs +67 -0
  40. data/sig/generated/riffer/rig/ui/theme.rbs +60 -0
  41. data/sig/generated/riffer/rig/version.rbs +7 -0
  42. data/sig/generated/riffer/rig.rbs +2 -0
  43. data/sig/manual/riffer/rig/cli.rbs +8 -0
  44. data/sig/manual/riffer/rig/credentials.rbs +8 -0
  45. data/sig/manual/riffer/rig/settings.rbs +8 -0
  46. data/sig/manual/riffer/rig/tools.rbs +5 -0
  47. data/sig/manual/riffer/rig/ui/banner.rbs +8 -0
  48. data/sig/manual/riffer/rig/ui.rbs +5 -0
  49. metadata +30 -2
@@ -3,20 +3,36 @@
3
3
  # Animated output is gated behind an interactive, colour-enabled TTY, so piped
4
4
  # or tested runs stay silent and escape-free.
5
5
  class Riffer::Rig::UI::Animator
6
- REVEAL_FRAME_SECONDS = 0.05
7
- SPINNER_FRAME_SECONDS = 0.12
8
- EQ_LEVELS = '▁▂▃▄▅▆▇█'.chars.freeze
9
- EQ_BARS = 7
10
- NEUTRAL_LABEL = 'riffing…'
6
+ REVEAL_FRAME_SECONDS = 0.05 #: Float
7
+
8
+ SPINNER_FRAME_SECONDS = 0.12 #: Float
9
+
10
+ EQ_LEVELS = '▁▂▃▄▅▆▇█'.chars.freeze #: Array[String]
11
+
12
+ EQ_BARS = 7 #: Integer
13
+
14
+ NEUTRAL_LABEL = 'riffing…' #: String
15
+
11
16
  REASONING_PHRASES = [
12
17
  'contemplating…', 'pondering…', 'mulling it over…', 'reasoning…',
13
18
  'connecting the dots…', 'herding thoughts…', 'consulting the muse…',
14
19
  'doing some deep listening…', 'warming up…', 'tuning up…',
15
20
  'in the woodshed…', 'vamping…', 'finding the key…', 'counting it off…',
16
21
  'jamming internally…'
17
- ].freeze
18
- REASONING_TICK_RANGE = (1..5)
22
+ ].freeze #: Array[String]
19
23
 
24
+ REASONING_TICK_RANGE = (1..5) #: Range[Integer]
25
+
26
+ # @rbs @io: untyped
27
+ # @rbs @theme: Riffer::Rig::UI::Theme
28
+ # @rbs @thread: Thread?
29
+ # @rbs @mode: Symbol
30
+ # @rbs @phrase: String?
31
+ # @rbs @stop: bool
32
+
33
+ # @rbs io: untyped
34
+ # @rbs ?theme: Riffer::Rig::UI::Theme
35
+ # @rbs return: void
20
36
  def initialize(io: $stdout, theme: Riffer::Rig::UI::Theme.for(io))
21
37
  @io = io
22
38
  @theme = theme
@@ -26,6 +42,9 @@ class Riffer::Rig::UI::Animator
26
42
  end
27
43
 
28
44
  # When not on a TTY, prints the final frame once instead of animating.
45
+ #
46
+ # @rbs frames: Array[Array[String]]
47
+ # @rbs return: void
29
48
  def reveal(frames)
30
49
  unless enabled?
31
50
  frames.last.each { |line| @io.puts(line) }
@@ -41,6 +60,8 @@ class Riffer::Rig::UI::Animator
41
60
  end
42
61
  end
43
62
 
63
+ # @rbs ?mode: Symbol
64
+ # @rbs return: void
44
65
  def start(mode = :neutral)
45
66
  return unless enabled?
46
67
 
@@ -54,16 +75,21 @@ class Riffer::Rig::UI::Animator
54
75
  @thread = Thread.new { animate }
55
76
  end
56
77
 
78
+ # @rbs return: void
57
79
  def stop
58
- return unless @thread
80
+ thread = @thread
81
+ return unless thread
59
82
 
60
83
  @stop = true
61
- @thread.join
84
+ thread.join
62
85
  @thread = nil
63
86
  @io.print("\r\e[K")
64
87
  @io.flush
65
88
  end
66
89
 
90
+ # @rbs tick: Integer
91
+ # @rbs ?label: String
92
+ # @rbs return: String
67
93
  def equalizer(tick, label = NEUTRAL_LABEL)
68
94
  bars = Array.new(EQ_BARS) do |i|
69
95
  height = (Math.sin((tick + i) * 0.6).abs * (EQ_LEVELS.length - 1)).round
@@ -75,6 +101,7 @@ class Riffer::Rig::UI::Animator
75
101
 
76
102
  private
77
103
 
104
+ # @rbs return: void
78
105
  def animate
79
106
  tick = 0
80
107
  roll_at = 0.0 # force an immediate phrase roll when entering reasoning mode
@@ -88,18 +115,31 @@ class Riffer::Rig::UI::Animator
88
115
  end
89
116
 
90
117
  # Re-rolls both phrase and duration whenever the reasoning tick expires.
118
+ #
119
+ # @rbs roll_at: Float
120
+ # @rbs return: Float
91
121
  def roll_phrase(roll_at)
92
122
  now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
93
123
  return roll_at unless @mode == :reasoning && now >= roll_at
94
124
 
95
125
  @phrase = REASONING_PHRASES.sample
96
- now + rand(REASONING_TICK_RANGE)
126
+ now + roll_rand
127
+ end
128
+
129
+ # Range rand returns nil for an empty range; this one is a non-empty constant.
130
+ #
131
+ # @rbs return: Integer
132
+ def roll_rand
133
+ x = rand(REASONING_TICK_RANGE)
134
+ x || 0
97
135
  end
98
136
 
137
+ # @rbs return: String
99
138
  def label
100
- @mode == :reasoning ? @phrase : NEUTRAL_LABEL
139
+ @mode == :reasoning ? @phrase || NEUTRAL_LABEL : NEUTRAL_LABEL
101
140
  end
102
141
 
142
+ # @rbs return: bool
103
143
  def enabled?
104
144
  @theme.enabled && @io.respond_to?(:tty?) && @io.tty?
105
145
  end
@@ -10,7 +10,7 @@ module Riffer::Rig::UI::Banner
10
10
  '██╔══██╗██║██╔══╝ ██╔══╝ ██╔══╝ ██╔══██╗',
11
11
  '██║ ██║██║██║ ██║ ███████╗██║ ██║',
12
12
  '╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝'
13
- ].freeze
13
+ ].freeze #: Array[String]
14
14
 
15
15
  ROW_COLOURS = [
16
16
  Riffer::Rig::UI::Palette::PINK,
@@ -19,25 +19,49 @@ module Riffer::Rig::UI::Banner
19
19
  Riffer::Rig::UI::Palette::BLUE,
20
20
  Riffer::Rig::UI::Palette::CYAN,
21
21
  Riffer::Rig::UI::Palette::CYAN
22
- ].freeze
22
+ ].freeze #: Array[Array[Integer]]
23
23
 
24
- INFO_LABEL_WIDTH = 8
25
- INDENT = ' '
24
+ INFO_LABEL_WIDTH = 8 #: Integer
26
25
 
26
+ INDENT = ' ' #: String
27
+
28
+ # @rbs theme: Riffer::Rig::UI::Theme
29
+ # @rbs model: String
30
+ # @rbs cwd: String
31
+ # @rbs context: String
32
+ # @rbs skills: String
33
+ # @rbs version: String
34
+ # @rbs return: String
27
35
  def call(theme, model:, cwd:, context:, skills:, version:)
28
36
  lines(theme, model: model, cwd: cwd, context: context, skills: skills, version: version).join("\n")
29
37
  end
30
38
 
39
+ # @rbs theme: Riffer::Rig::UI::Theme
40
+ # @rbs model: String
41
+ # @rbs cwd: String
42
+ # @rbs context: String
43
+ # @rbs skills: String
44
+ # @rbs version: String
45
+ # @rbs return: Array[String]
31
46
  def lines(theme, model:, cwd:, context:, skills:, version:)
32
47
  [''] + art(theme) + [''] +
33
48
  info(theme, model: model, cwd: cwd, context: context, skills: skills, version: version) + ['']
34
49
  end
35
50
 
51
+ # @rbs theme: Riffer::Rig::UI::Theme
52
+ # @rbs return: Array[String]
36
53
  def art(theme)
37
54
  art = WORDMARK.each_index.map { |i| "#{INDENT}#{theme.paint(WORDMARK[i], ROW_COLOURS[i])}" }
38
55
  art + ["#{INDENT}#{theme.cyan("♪ let's riff ♪")}"]
39
56
  end
40
57
 
58
+ # @rbs theme: Riffer::Rig::UI::Theme
59
+ # @rbs model: String
60
+ # @rbs cwd: String
61
+ # @rbs context: String
62
+ # @rbs skills: String
63
+ # @rbs version: String
64
+ # @rbs return: Array[String]
41
65
  def info(theme, model:, cwd:, context:, skills:, version:)
42
66
  rows = { model: model, cwd: cwd, context: context, skills: skills, version: version }.map do |label, value|
43
67
  " #{theme.magenta('▸')} #{theme.grey(label.to_s.ljust(INFO_LABEL_WIDTH))}#{value}"
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Hides the terminal cursor while a turn is in flight: a hidden cursor can't
4
+ # flicker against the animator's erase-and-redraw churn.
5
+ class Riffer::Rig::UI::Cursor
6
+ HIDE = "\e[?25l" #: String
7
+
8
+ SHOW = "\e[?25h" #: String
9
+
10
+ # @rbs @io: untyped
11
+ # @rbs @theme: Riffer::Rig::UI::Theme
12
+
13
+ # @rbs io: untyped
14
+ # @rbs ?theme: Riffer::Rig::UI::Theme
15
+ # @rbs return: void
16
+ def initialize(io: $stdout, theme: Riffer::Rig::UI::Theme.for(io))
17
+ @io = io
18
+ @theme = theme
19
+ end
20
+
21
+ # @rbs return: void
22
+ def hide
23
+ write(HIDE)
24
+ end
25
+
26
+ # @rbs return: void
27
+ def show
28
+ write(SHOW)
29
+ end
30
+
31
+ private
32
+
33
+ # @rbs sequence: String
34
+ # @rbs return: void
35
+ def write(sequence)
36
+ return unless enabled?
37
+
38
+ @io.print(sequence)
39
+ @io.flush
40
+ end
41
+
42
+ # @rbs return: bool
43
+ def enabled?
44
+ @theme.enabled && @io.respond_to?(:tty?) && @io.tty?
45
+ end
46
+ end
@@ -1,11 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Riffer::Rig::UI::Palette
4
- PINK = [255, 106, 193].freeze
5
- MAGENTA = [192, 76, 253].freeze
6
- PURPLE = [122, 92, 255].freeze
7
- BLUE = [77, 124, 255].freeze
8
- CYAN = [45, 226, 230].freeze
9
- GREY = [138, 139, 154].freeze
10
- RED = [255, 92, 122].freeze
4
+ PINK = [255, 106, 193].freeze #: Array[Integer]
5
+
6
+ MAGENTA = [192, 76, 253].freeze #: Array[Integer]
7
+
8
+ PURPLE = [122, 92, 255].freeze #: Array[Integer]
9
+
10
+ BLUE = [77, 124, 255].freeze #: Array[Integer]
11
+
12
+ CYAN = [45, 226, 230].freeze #: Array[Integer]
13
+
14
+ GREY = [138, 139, 154].freeze #: Array[Integer]
15
+
16
+ RED = [255, 92, 122].freeze #: Array[Integer]
11
17
  end
@@ -3,8 +3,18 @@
3
3
  require 'json'
4
4
 
5
5
  class Riffer::Rig::UI::Renderer
6
- RESULT_PREVIEW_LIMIT = 200
7
-
6
+ RESULT_PREVIEW_LIMIT = 200 #: Integer
7
+
8
+ # @rbs @io: untyped
9
+ # @rbs @theme: Riffer::Rig::UI::Theme
10
+ # @rbs @tally: Riffer::Rig::TokenTally?
11
+ # @rbs @smoother: Riffer::Rig::UI::Smoother | PassThroughSmoother?
12
+
13
+ # @rbs io: untyped
14
+ # @rbs ?theme: Riffer::Rig::UI::Theme
15
+ # @rbs ?tally: Riffer::Rig::TokenTally?
16
+ # @rbs ?smoother: Riffer::Rig::UI::Smoother | PassThroughSmoother?
17
+ # @rbs return: void
8
18
  def initialize(io: $stdout, theme: Riffer::Rig::UI::Theme.for(io), tally: nil, smoother: nil)
9
19
  @io = io
10
20
  @theme = theme
@@ -12,6 +22,8 @@ class Riffer::Rig::UI::Renderer
12
22
  @smoother = smoother
13
23
  end
14
24
 
25
+ # @rbs event: Riffer::StreamEvents::Base
26
+ # @rbs return: void
15
27
  def render(event)
16
28
  case event
17
29
  when Riffer::StreamEvents::TextDelta
@@ -31,6 +43,8 @@ class Riffer::Rig::UI::Renderer
31
43
  end
32
44
  end
33
45
 
46
+ # @rbs message: Riffer::Messages::Base
47
+ # @rbs return: void
34
48
  def render_tool_result(message)
35
49
  return unless message.is_a?(Riffer::Messages::Tool)
36
50
 
@@ -40,46 +54,61 @@ class Riffer::Rig::UI::Renderer
40
54
 
41
55
  private
42
56
 
57
+ # @rbs return: Riffer::Rig::UI::Smoother | PassThroughSmoother
43
58
  def smoother
44
59
  @smoother || PassThroughSmoother.new(@io)
45
60
  end
46
61
 
47
- # Stand-in when no smoother is injected, so a bare Renderer still prints
62
+ # Stand-in when no smoother is injected, so a bare Riffer::Rig::UI::Renderer still prints
48
63
  # synchronously.
49
64
  class PassThroughSmoother
65
+ # @rbs @io: untyped
66
+
67
+ # @rbs io: untyped
68
+ # @rbs return: void
50
69
  def initialize(io) = @io = io
51
70
 
71
+ # @rbs content: String
72
+ # @rbs return: self
52
73
  def <<(content)
53
74
  @io.print(content)
54
75
  @io.flush
55
76
  self
56
77
  end
57
78
 
79
+ # @rbs return: nil
58
80
  def drain = nil
59
81
 
82
+ # @rbs return: nil
60
83
  def finish = nil
61
84
  end
62
85
 
86
+ # @rbs return: void
63
87
  def drain_smoother
64
88
  smoother.drain
65
89
  end
66
90
 
91
+ # @rbs usage: Riffer::Providers::TokenUsage
92
+ # @rbs return: void
67
93
  def render_token_usage(usage)
68
- return unless @tally
94
+ tally = @tally
95
+ return unless tally
69
96
 
70
- @tally.add(usage)
97
+ tally.add(usage)
71
98
 
72
99
  parts = ["↑#{usage.input_tokens}", "↓#{usage.output_tokens}"]
73
100
  parts << "cache_write:#{usage.cache_write_tokens}" if usage.cache_write_tokens&.positive?
74
101
  parts << "cache_read:#{usage.cache_read_tokens}" if usage.cache_read_tokens&.positive?
75
102
 
76
- session_parts = ["session #{@tally.total_tokens} tok"]
77
- cost = @tally.estimated_cost
103
+ session_parts = ["session #{tally.total_tokens} tok"]
104
+ cost = tally.estimated_cost
78
105
  session_parts << format('~$%.4f', cost) if cost
79
106
 
80
107
  @io.puts("\n#{@theme.dim("#{parts.join(' · ')} #{session_parts.join(' · ')}")}")
81
108
  end
82
109
 
110
+ # @rbs arguments: String
111
+ # @rbs return: String
83
112
  def format_arguments(arguments)
84
113
  parsed = JSON.parse(arguments)
85
114
  parsed.map { |key, value| "#{key}: #{value.inspect}" }.join(', ')
@@ -87,6 +116,8 @@ class Riffer::Rig::UI::Renderer
87
116
  arguments
88
117
  end
89
118
 
119
+ # @rbs content: String
120
+ # @rbs return: String
90
121
  def preview(content)
91
122
  first_line = content.to_s.lines.first.to_s.chomp
92
123
  return first_line if first_line.length <= RESULT_PREVIEW_LIMIT
@@ -4,7 +4,7 @@
4
4
  # typing. On a non-TTY output it is transparent: writes pass through
5
5
  # synchronously and no thread ever runs.
6
6
  class Riffer::Rig::UI::Smoother
7
- TICK_SECONDS = 1.0 / 60.0
7
+ TICK_SECONDS = 1.0 / 60.0 #: Float
8
8
 
9
9
  # A frame removes only a sixtieth of the remaining backlog — an exponential
10
10
  # decay whose tail keeps text flowing for a second or more, so the reveal
@@ -12,9 +12,24 @@ class Riffer::Rig::UI::Smoother
12
12
  # releases so the effective rate can settle at the provider's throughput;
13
13
  # the floor is fractional (10 chars/s) so it can't over-drain a backlog
14
14
  # that's trickling in slower than 60 chars/s.
15
- BACKLOG_FRACTION_PER_TICK = Rational(1, 60)
16
- MIN_CHARS_PER_TICK = Rational(1, 6)
15
+ #
16
+ BACKLOG_FRACTION_PER_TICK = Rational(1, 60) #: Rational
17
17
 
18
+ MIN_CHARS_PER_TICK = Rational(1, 6) #: Rational
19
+
20
+ # @rbs @io: untyped
21
+ # @rbs @theme: Riffer::Rig::UI::Theme
22
+ # @rbs @clock: singleton(Kernel)
23
+ # @rbs @backlog: String
24
+ # @rbs @carry: Rational
25
+ # @rbs @mutex: Mutex
26
+ # @rbs @thread: Thread?
27
+ # @rbs @stop: bool
28
+
29
+ # @rbs io: untyped
30
+ # @rbs ?theme: Riffer::Rig::UI::Theme
31
+ # @rbs clock: singleton(Kernel)
32
+ # @rbs return: void
18
33
  def initialize(io: $stdout, theme: Riffer::Rig::UI::Theme.for(io), clock: Kernel)
19
34
  @io = io
20
35
  @theme = theme
@@ -26,6 +41,7 @@ class Riffer::Rig::UI::Smoother
26
41
  @stop = false
27
42
  end
28
43
 
44
+ # @rbs return: void
29
45
  def start
30
46
  return unless enabled?
31
47
  return if @thread
@@ -39,6 +55,8 @@ class Riffer::Rig::UI::Smoother
39
55
  end
40
56
  end
41
57
 
58
+ # @rbs content: String
59
+ # @rbs return: self
42
60
  def <<(content)
43
61
  if enabled?
44
62
  @mutex.synchronize { @backlog << content }
@@ -50,6 +68,8 @@ class Riffer::Rig::UI::Smoother
50
68
  end
51
69
 
52
70
  # The write sits inside the mutex so a concurrent tick can't reorder a drain.
71
+ #
72
+ # @rbs return: void
53
73
  def tick
54
74
  @mutex.synchronize do
55
75
  return if @backlog.empty?
@@ -65,6 +85,8 @@ class Riffer::Rig::UI::Smoother
65
85
  end
66
86
 
67
87
  # Called before non-delta output so printed order matches stream order.
88
+ #
89
+ # @rbs return: void
68
90
  def drain
69
91
  @mutex.synchronize do
70
92
  return if @backlog.empty?
@@ -75,10 +97,13 @@ class Riffer::Rig::UI::Smoother
75
97
  end
76
98
 
77
99
  # Idempotent: the REPL's ensure path runs it even after a happy-path finish.
100
+ #
101
+ # @rbs return: void
78
102
  def finish
79
- if @thread
103
+ thread = @thread
104
+ if thread
80
105
  @stop = true
81
- @thread.join
106
+ thread.join
82
107
  @thread = nil
83
108
  end
84
109
  drain
@@ -86,6 +111,7 @@ class Riffer::Rig::UI::Smoother
86
111
 
87
112
  private
88
113
 
114
+ # @rbs return: bool
89
115
  def enabled?
90
116
  @theme.enabled && @io.respond_to?(:tty?) && @io.tty?
91
117
  end
@@ -2,43 +2,74 @@
2
2
 
3
3
  # +enabled+ gates every escape so the same code path emits plain, escape-free
4
4
  # text when piped or tested.
5
- Riffer::Rig::UI::Theme = Data.define(:enabled)
6
-
7
- # Behaviour lives in a reopened class (rather than the `Data.define` block) so
8
- # the type checker can analyse the methods.
9
5
  class Riffer::Rig::UI::Theme
6
+ # @rbs @enabled: bool
7
+
8
+ # @rbs return: bool
9
+ # @dynamic enabled
10
+ attr_reader :enabled #: bool
11
+
12
+ # @rbs enabled: bool
13
+ # @rbs return: void
14
+ def initialize(enabled:)
15
+ @enabled = enabled
16
+ end
17
+
18
+ # @rbs io: untyped
19
+ # @rbs return: Riffer::Rig::UI::Theme
10
20
  def self.for(io)
11
21
  new(enabled: io.respond_to?(:tty?) && io.tty? && !ENV.key?('NO_COLOR'))
12
22
  end
13
23
 
24
+ # @rbs text: String
25
+ # @rbs rgb: Array[Integer]
26
+ # @rbs return: String
14
27
  def paint(text, rgb)
15
- return text.to_s unless enabled
28
+ return text unless enabled
16
29
 
17
30
  "\e[38;2;#{rgb[0]};#{rgb[1]};#{rgb[2]}m#{text}\e[0m"
18
31
  end
19
32
 
33
+ # @rbs text: String
34
+ # @rbs return: String
20
35
  def pink(text) = paint(text, Riffer::Rig::UI::Palette::PINK)
21
36
 
37
+ # @rbs text: String
38
+ # @rbs return: String
22
39
  def magenta(text) = paint(text, Riffer::Rig::UI::Palette::MAGENTA)
23
40
 
41
+ # @rbs text: String
42
+ # @rbs return: String
24
43
  def purple(text) = paint(text, Riffer::Rig::UI::Palette::PURPLE)
25
44
 
45
+ # @rbs text: String
46
+ # @rbs return: String
26
47
  def blue(text) = paint(text, Riffer::Rig::UI::Palette::BLUE)
27
48
 
49
+ # @rbs text: String
50
+ # @rbs return: String
28
51
  def cyan(text) = paint(text, Riffer::Rig::UI::Palette::CYAN)
29
52
 
53
+ # @rbs text: String
54
+ # @rbs return: String
30
55
  def grey(text) = paint(text, Riffer::Rig::UI::Palette::GREY)
31
56
 
57
+ # @rbs text: String
58
+ # @rbs return: String
32
59
  def red(text) = paint(text, Riffer::Rig::UI::Palette::RED)
33
60
 
61
+ # @rbs text: String
62
+ # @rbs return: String
34
63
  def dim(text)
35
- return text.to_s unless enabled
64
+ return text unless enabled
36
65
 
37
66
  "\e[2m#{text}\e[0m"
38
67
  end
39
68
 
69
+ # @rbs text: String
70
+ # @rbs return: String
40
71
  def bold(text)
41
- return text.to_s unless enabled
72
+ return text unless enabled
42
73
 
43
74
  "\e[1m#{text}\e[0m"
44
75
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Riffer
4
4
  module Rig
5
- VERSION = '0.2.0'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
@@ -0,0 +1,41 @@
1
+ # Generated from lib/riffer/rig/cli.rb with RBS::Inline
2
+
3
+ module Riffer::Rig::CLI
4
+ PROVIDER_URLS: Hash[String, String]
5
+
6
+ # @rbs output: untyped
7
+ # @rbs input: untyped
8
+ # @rbs return: Integer
9
+ def start: (?output: untyped, ?input: untyped) -> Integer
10
+
11
+ private
12
+
13
+ # @rbs provider: String?
14
+ # @rbs api_key: String
15
+ # @rbs return: void
16
+ def configure_provider: (String? provider, String api_key) -> void
17
+
18
+ # @rbs provider: String?
19
+ # @rbs theme: Riffer::Rig::UI::Theme
20
+ # @rbs output: untyped
21
+ # @rbs input: untyped
22
+ # @rbs return: String?
23
+ def onboard: (String? provider, Riffer::Rig::UI::Theme theme, output: untyped, input: untyped) -> String?
24
+
25
+ # @rbs provider: String?
26
+ # @rbs return: String
27
+ def env_var_for: (String? provider) -> String
28
+
29
+ # @rbs input: untyped
30
+ # @rbs return: String?
31
+ def read_secret: (untyped input) -> String?
32
+
33
+ # @rbs theme: Riffer::Rig::UI::Theme
34
+ # @rbs animator: Riffer::Rig::UI::Animator
35
+ # @rbs model: String
36
+ # @rbs return: void
37
+ def reveal_banner: (Riffer::Rig::UI::Theme theme, Riffer::Rig::UI::Animator animator, String model) -> void
38
+
39
+ # @rbs return: String
40
+ def count_skills: () -> String
41
+ end
@@ -0,0 +1,9 @@
1
+ # Generated from lib/riffer/rig/coding_agent.rb with RBS::Inline
2
+
3
+ class Riffer::Rig::CodingAgent < Riffer::Agent
4
+ GLOBAL_AGENTS_FILE: String
5
+
6
+ GLOBAL_SKILLS_DIR: String
7
+
8
+ PROJECT_SKILLS_DIR: ^() -> String
9
+ end
@@ -0,0 +1,55 @@
1
+ # Generated from lib/riffer/rig/credentials.rb with RBS::Inline
2
+
3
+ # Reads and writes API keys for each supported provider in
4
+ # <tt>~/.riffer/auth.json</tt>.
5
+ #
6
+ # Keys are stored under the provider name, e.g.:
7
+ #
8
+ # {
9
+ # "anthropic": "sk-ant-...",
10
+ # "openai": "sk-...",
11
+ # "gemini": "...",
12
+ # "openrouter": "sk-or-..."
13
+ # }
14
+ #
15
+ # The environment variable checked per provider:
16
+ #
17
+ # anthropic → ANTHROPIC_API_KEY
18
+ # openai → OPENAI_API_KEY
19
+ # gemini → GEMINI_API_KEY
20
+ # openrouter → OPENROUTER_API_KEY
21
+ module Riffer::Rig::Credentials
22
+ PATH: String
23
+
24
+ PROVIDER_ENV_VARS: Hash[String, String]
25
+
26
+ # Returns the API key for +provider+, checking the environment variable first,
27
+ # then the stored file. Returns +nil+ if no key is available.
28
+ #
29
+ # @rbs provider: String?
30
+ # @rbs path: String
31
+ # @rbs return: String?
32
+ def api_key_for: (String? provider, ?path: String) -> String?
33
+
34
+ # Saves an API key for +provider+ to the auth file with 0600 permissions.
35
+ #
36
+ # @rbs provider: String
37
+ # @rbs key: String
38
+ # @rbs path: String
39
+ # @rbs return: String
40
+ def save_api_key: (String provider, String key, ?path: String) -> String
41
+
42
+ private
43
+
44
+ # @rbs provider: String?
45
+ # @rbs path: String
46
+ # @rbs return: String?
47
+ def key_from_file: (String? provider, String path) -> String?
48
+
49
+ # The parsed auth file, or an empty hash when the file is absent or
50
+ # malformed.
51
+ #
52
+ # @rbs path: String
53
+ # @rbs return: Hash[String, untyped]
54
+ def read: (String path) -> Hash[String, untyped]
55
+ end