riffer-rig 0.7.0 → 0.8.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 +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +2 -1
- data/lib/riffer/rig/loader.rb +79 -2
- data/lib/riffer/rig/settings/document.rb +3 -1
- data/lib/riffer/rig/terminal/renderer.rb +86 -13
- data/lib/riffer/rig/terminal/wrapper.rb +103 -0
- data/lib/riffer/rig/trust.rb +37 -0
- data/lib/riffer/rig/version.rb +1 -1
- data/sig/generated/riffer/rig/loader.rbs +36 -0
- data/sig/generated/riffer/rig/settings/document.rbs +3 -1
- data/sig/generated/riffer/rig/terminal/renderer.rbs +41 -1
- data/sig/generated/riffer/rig/terminal/wrapper.rbs +43 -0
- data/sig/generated/riffer/rig/trust.rbs +20 -0
- data/sig/manual/riffer/rig/trust.rbs +6 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9d560ab0c2be62a87e8468ba302f443985cd5d92d4872de2073c5c77b672d83f
|
|
4
|
+
data.tar.gz: b75fe6a3d1a7f0c718405f619f2f56c20fe85282ddb94aff9c2beb853344c119
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a2ca46037680a034a31b977101a9c5b442fc21f56842cb3055f2163493d42eef2d92ccdbc53aa9bf5fd2749af6fcfd52fb6d74aecbea0da907a5bac3ecee7498
|
|
7
|
+
data.tar.gz: 4e531d3fee7fa1107e7d6a7c5d1fa064c4bbdf9e46763353332d4b9d9b57b6a37436d54326fe06e21248c147d99e15ddaac4a18b6ee8ffd34be4e29d733444ae
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.0](https://github.com/bottrall/riffer-rig/compare/v0.7.0...v0.8.0) (2026-10-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* discover rig.rb files with project trust and optional gem autoload ([#208](https://github.com/bottrall/riffer-rig/issues/208)) ([6ed818f](https://github.com/bottrall/riffer-rig/commit/6ed818f5b5d382b3a9c7d48ce667892b10190985))
|
|
9
|
+
* truncate tool output and fit lines to the terminal width ([#209](https://github.com/bottrall/riffer-rig/issues/209)) ([3765de1](https://github.com/bottrall/riffer-rig/commit/3765de1f4392d903939c1458e843c2ca1e70112e))
|
|
10
|
+
|
|
3
11
|
## [0.7.0](https://github.com/bottrall/riffer-rig/compare/v0.6.0...v0.7.0) (2026-09-30)
|
|
4
12
|
|
|
5
13
|
|
data/README.md
CHANGED
|
@@ -35,7 +35,7 @@ The model is the first of these that is set: `--model`, `RIFFER_MODEL`, `model`
|
|
|
35
35
|
| Flag | Effect |
|
|
36
36
|
| ----------------------- | -------------------------------------------------------------------------- |
|
|
37
37
|
| `--model provider/name` | the model for this session, over `RIFFER_MODEL` and the settings |
|
|
38
|
-
| `--no-extensions` | skips
|
|
38
|
+
| `--no-extensions` | skips the [`rig.rb` files](docs/EXTENSIONS.md#the-rigrb-files) and gem autoload; the bundle still loads |
|
|
39
39
|
| `--no-skills` | leaves out [Agent Skills](docs/SKILLS.md) |
|
|
40
40
|
| `--no-agents-md` | leaves out the [AGENTS.md](docs/INSTRUCTIONS.md#agentsmd) instructions |
|
|
41
41
|
| `--tools read,bash` | only these tools, by identifier ([Tools](docs/TOOLS.md)) |
|
|
@@ -124,6 +124,7 @@ Every project chore is a script in `bin/`. The Rakefile behind them is an implem
|
|
|
124
124
|
| `bin/rbs-watch` | Regenerate `sig/generated` whenever `lib/` changes |
|
|
125
125
|
| `bin/ci` | Run everything CI runs, serially. Use before pushing |
|
|
126
126
|
| `bin/build` | Build the gem into `pkg/`; the publish workflow runs this before `gem push` |
|
|
127
|
+
| `bin/docs` | Build the docs site and API reference into `_site/`; `bin/docs serve` serves it at http://localhost:8000 |
|
|
127
128
|
| `bin/plans` | Serve the building plans in `plans/` at http://localhost:8001 |
|
|
128
129
|
|
|
129
130
|
## Releasing
|
data/lib/riffer/rig/loader.rb
CHANGED
|
@@ -11,13 +11,19 @@ class Riffer::Rig::Loader
|
|
|
11
11
|
NO_MODEL = 'No model is set: pass --model provider/name, set RIFFER_MODEL, or set "model" in ' \
|
|
12
12
|
"~/.riffer/settings.json, with a provider from: #{PROVIDER_LIST}".freeze #: String
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
EXTENSION_FEATURE = 'riffer/rig/extension' #: String
|
|
15
|
+
|
|
16
|
+
private_constant :EXTENSION_FEATURE, :PROVIDER_LIST, :ONBOARDING_QUESTION, :NO_MODEL
|
|
15
17
|
|
|
16
18
|
# @rbs @cwd: String
|
|
17
19
|
# @rbs @host: Riffer::Rig::Hosts::_Host
|
|
18
20
|
# @rbs @env: Riffer::Rig::Env
|
|
19
21
|
# @rbs @home: String
|
|
20
22
|
# @rbs @riffer_config: Riffer::Config
|
|
23
|
+
# @rbs @tracked_files: Array[String]
|
|
24
|
+
|
|
25
|
+
# @dynamic tracked_files
|
|
26
|
+
attr_reader :tracked_files #: Array[String]
|
|
21
27
|
|
|
22
28
|
# @rbs cwd: String
|
|
23
29
|
# @rbs host: Riffer::Rig::Hosts::_Host
|
|
@@ -61,6 +67,7 @@ class Riffer::Rig::Loader
|
|
|
61
67
|
@env = env
|
|
62
68
|
@home = home
|
|
63
69
|
@riffer_config = riffer_config
|
|
70
|
+
@tracked_files = []
|
|
64
71
|
end
|
|
65
72
|
|
|
66
73
|
# @rbs model: String?
|
|
@@ -87,10 +94,12 @@ class Riffer::Rig::Loader
|
|
|
87
94
|
provider = Riffer::Rig::Settings.provider_for(selected).to_s
|
|
88
95
|
credentials = { provider.to_sym => credentials_for(provider) }
|
|
89
96
|
stripped = { skills: skills, agents_md: agents_md }.reject { |_name, kept| kept }.keys
|
|
97
|
+
loaded = Riffer::Rig::Bundled::BY_NAME.except(*document.disabled.map(&:to_sym), *stripped).values
|
|
98
|
+
loaded += load_rig_files(document.autoload) if extensions
|
|
90
99
|
|
|
91
100
|
Riffer::Rig::Runtime.new(
|
|
92
101
|
selected,
|
|
93
|
-
extensions:
|
|
102
|
+
extensions: loaded,
|
|
94
103
|
tools: tools,
|
|
95
104
|
settings: settings,
|
|
96
105
|
host: @host,
|
|
@@ -105,6 +114,74 @@ class Riffer::Rig::Loader
|
|
|
105
114
|
|
|
106
115
|
private
|
|
107
116
|
|
|
117
|
+
# @rbs autoload: bool
|
|
118
|
+
# @rbs return: Array[Riffer::Rig::Extension]
|
|
119
|
+
def load_rig_files(autoload)
|
|
120
|
+
recorded = gem_extension_files(autoload).flat_map { |file| load_file(File.expand_path(file), track: false) }
|
|
121
|
+
home = File.expand_path(File.join(@home, '.riffer', 'rig.rb'))
|
|
122
|
+
recorded.concat(load_file(home, track: true)) if File.file?(home)
|
|
123
|
+
project = File.expand_path(File.join(@cwd, '.riffer', 'rig.rb'))
|
|
124
|
+
recorded.concat(load_file(project, track: true)) if File.file?(project) && trusted?(project)
|
|
125
|
+
recorded
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @rbs autoload: bool
|
|
129
|
+
# @rbs return: Array[String]
|
|
130
|
+
def gem_extension_files(autoload)
|
|
131
|
+
return [] unless autoload
|
|
132
|
+
|
|
133
|
+
Gem.find_files(EXTENSION_FEATURE)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# @rbs path: String
|
|
137
|
+
# @rbs return: bool
|
|
138
|
+
def trusted?(path)
|
|
139
|
+
stored = Riffer::Rig::Trust.read(trust_path)[path]
|
|
140
|
+
return stored unless stored.nil?
|
|
141
|
+
|
|
142
|
+
asked = @host.capabilities.include?(:confirm)
|
|
143
|
+
decision = asked ? @host.confirm("Trust #{path}?") : false
|
|
144
|
+
Riffer::Rig::Trust.store(trust_path, path, decision) if asked
|
|
145
|
+
decision
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# @rbs return: String
|
|
149
|
+
def trust_path
|
|
150
|
+
File.join(@home, '.riffer', 'trust.json')
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @rbs path: String
|
|
154
|
+
# @rbs track: bool
|
|
155
|
+
# @rbs return: Array[Riffer::Rig::Extension]
|
|
156
|
+
def load_file(path, track:)
|
|
157
|
+
before = Riffer::Rig.extensions
|
|
158
|
+
before_features = $LOADED_FEATURES.dup
|
|
159
|
+
begin
|
|
160
|
+
load path
|
|
161
|
+
rescue StandardError => e
|
|
162
|
+
track_loaded(before_features, path) if track
|
|
163
|
+
@host.notify("#{path} failed to load: #{e.message}", level: :error)
|
|
164
|
+
return []
|
|
165
|
+
end
|
|
166
|
+
track_loaded(before_features, path) if track
|
|
167
|
+
Riffer::Rig.extensions.values.reject { |extension| before[extension.name] == extension }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# @rbs before_features: Array[String]
|
|
171
|
+
# @rbs path: String
|
|
172
|
+
# @rbs return: void
|
|
173
|
+
def track_loaded(before_features, path)
|
|
174
|
+
recorded = ($LOADED_FEATURES - before_features) + [path] #: Array[String]
|
|
175
|
+
@tracked_files.concat(recorded.select { |feature| local?(feature) })
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @rbs feature: String
|
|
179
|
+
# @rbs return: bool
|
|
180
|
+
def local?(feature)
|
|
181
|
+
roots = [File.expand_path(File.join(@home, '.riffer')), File.expand_path(@cwd)] #: Array[String]
|
|
182
|
+
roots.any? { |root| feature.start_with?("#{root}#{File::SEPARATOR}") }
|
|
183
|
+
end
|
|
184
|
+
|
|
108
185
|
# @rbs configured: String?
|
|
109
186
|
# @rbs return: String
|
|
110
187
|
def select_model(configured)
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Riffer::Rig::Settings::Document
|
|
4
|
-
# @dynamic model, reasoning, models, providers, disabled
|
|
4
|
+
# @dynamic model, reasoning, models, providers, disabled, autoload
|
|
5
5
|
attr_reader :model #: String?
|
|
6
6
|
attr_reader :reasoning #: String?
|
|
7
7
|
attr_reader :models #: Hash[String, Riffer::Rig::Settings::Pricing]
|
|
8
8
|
attr_reader :providers #: Hash[String, Hash[String, String]]
|
|
9
9
|
attr_reader :disabled #: Array[String]
|
|
10
|
+
attr_reader :autoload #: bool
|
|
10
11
|
|
|
11
12
|
# @rbs source: Hash[Symbol, untyped]
|
|
12
13
|
# @rbs return: void
|
|
@@ -26,6 +27,7 @@ class Riffer::Rig::Settings::Document
|
|
|
26
27
|
disabled = hash_or_empty(source[:extensions])[:disabled]
|
|
27
28
|
names = disabled.is_a?(Array) ? disabled.grep(String) : [] #: Array[String]
|
|
28
29
|
@disabled = names.freeze
|
|
30
|
+
@autoload = hash_or_empty(source[:extensions])[:autoload] == true
|
|
29
31
|
freeze
|
|
30
32
|
end
|
|
31
33
|
|
|
@@ -1,32 +1,47 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'io/console'
|
|
3
4
|
require 'json'
|
|
4
5
|
|
|
5
6
|
class Riffer::Rig::Terminal::Renderer
|
|
6
7
|
RESULT_PREVIEW_LIMIT = 200 #: Integer
|
|
7
8
|
|
|
9
|
+
ARGUMENT_PREVIEW_LIMIT = 60 #: Integer
|
|
10
|
+
|
|
11
|
+
PROSE_INDENT = 2 #: Integer
|
|
12
|
+
|
|
13
|
+
TOOL_CALL_INDENT = 4 #: Integer
|
|
14
|
+
|
|
15
|
+
TOOL_RESULT_INDENT = 6 #: Integer
|
|
16
|
+
|
|
8
17
|
# @rbs @io: IO
|
|
9
18
|
# @rbs @theme: Riffer::Rig::Terminal::Theme
|
|
10
19
|
# @rbs @smoother: Riffer::Rig::Terminal::Smoother | PassThroughSmoother
|
|
11
20
|
# @rbs @cursor: Riffer::Rig::Terminal::Cursor
|
|
12
21
|
# @rbs @prose_gap_pending: bool
|
|
22
|
+
# @rbs @width: Integer?
|
|
23
|
+
# @rbs @wrapper: Riffer::Rig::Terminal::Wrapper
|
|
13
24
|
|
|
14
25
|
# @rbs io: IO
|
|
15
26
|
# @rbs theme: Riffer::Rig::Terminal::Theme
|
|
16
27
|
# @rbs smoother: Riffer::Rig::Terminal::Smoother | PassThroughSmoother
|
|
17
28
|
# @rbs cursor: Riffer::Rig::Terminal::Cursor
|
|
29
|
+
# @rbs width: Integer?
|
|
18
30
|
# @rbs return: void
|
|
19
31
|
def initialize(
|
|
20
32
|
io: $stdout,
|
|
21
33
|
theme: Riffer::Rig::Terminal::Theme.for(io),
|
|
22
34
|
smoother: PassThroughSmoother.new(io),
|
|
23
|
-
cursor: Riffer::Rig::Terminal::Cursor.new(io: io, theme: theme)
|
|
35
|
+
cursor: Riffer::Rig::Terminal::Cursor.new(io: io, theme: theme),
|
|
36
|
+
width: nil
|
|
24
37
|
)
|
|
25
38
|
@io = io
|
|
26
39
|
@theme = theme
|
|
27
40
|
@smoother = smoother
|
|
28
41
|
@cursor = cursor
|
|
29
42
|
@prose_gap_pending = false
|
|
43
|
+
@width = width
|
|
44
|
+
@wrapper = new_wrapper
|
|
30
45
|
end
|
|
31
46
|
|
|
32
47
|
# @rbs event: Riffer::StreamEvents::TextDelta | Riffer::StreamEvents::ToolCallDone | Riffer::StreamEvents::Interrupt
|
|
@@ -36,7 +51,7 @@ class Riffer::Rig::Terminal::Renderer
|
|
|
36
51
|
when Riffer::StreamEvents::TextDelta
|
|
37
52
|
render_prose(event.content)
|
|
38
53
|
when Riffer::StreamEvents::ToolCallDone
|
|
39
|
-
render_tool_activity(
|
|
54
|
+
render_tool_activity(TOOL_CALL_INDENT) { "⚙ #{event.name}(#{format_arguments(event.arguments)})" }
|
|
40
55
|
when Riffer::StreamEvents::Interrupt
|
|
41
56
|
render_block(0) { @theme.dim("[interrupted: #{event.reason}]") }
|
|
42
57
|
end
|
|
@@ -73,7 +88,12 @@ class Riffer::Rig::Terminal::Renderer
|
|
|
73
88
|
def usage(usage, session)
|
|
74
89
|
return unless usage && session
|
|
75
90
|
|
|
76
|
-
|
|
91
|
+
flush_prose
|
|
92
|
+
@prose_gap_pending = true
|
|
93
|
+
@io.puts
|
|
94
|
+
text = (@wrapper << usage_line(usage, session)) + @wrapper.flush
|
|
95
|
+
@io.print(@theme.dim(text)) unless text.empty?
|
|
96
|
+
@io.flush
|
|
77
97
|
end
|
|
78
98
|
|
|
79
99
|
# @rbs return: void
|
|
@@ -115,13 +135,14 @@ class Riffer::Rig::Terminal::Renderer
|
|
|
115
135
|
|
|
116
136
|
# @rbs return: void
|
|
117
137
|
def end_turn
|
|
138
|
+
flush_prose
|
|
118
139
|
@smoother.finish
|
|
119
140
|
@cursor.show
|
|
120
141
|
end
|
|
121
142
|
|
|
122
143
|
# @rbs return: void
|
|
123
144
|
def drain
|
|
124
|
-
|
|
145
|
+
flush_prose
|
|
125
146
|
end
|
|
126
147
|
|
|
127
148
|
# @rbs message: Riffer::Messages::Base
|
|
@@ -130,9 +151,9 @@ class Riffer::Rig::Terminal::Renderer
|
|
|
130
151
|
return unless message.is_a?(Riffer::Messages::Tool)
|
|
131
152
|
|
|
132
153
|
open_tool_activity
|
|
133
|
-
line = "↳ #{preview(message.content)}"
|
|
154
|
+
line = fit("↳ #{preview(message.content)}", TOOL_RESULT_INDENT)
|
|
134
155
|
styled = message.error? ? @theme.red(line) : @theme.dim(line)
|
|
135
|
-
@io.print("
|
|
156
|
+
@io.print("#{' ' * TOOL_RESULT_INDENT}#{styled}\n")
|
|
136
157
|
@io.flush
|
|
137
158
|
end
|
|
138
159
|
|
|
@@ -167,7 +188,7 @@ class Riffer::Rig::Terminal::Renderer
|
|
|
167
188
|
# @rbs &block: () -> String
|
|
168
189
|
# @rbs return: void
|
|
169
190
|
def render_block(indent, &)
|
|
170
|
-
|
|
191
|
+
flush_prose
|
|
171
192
|
@io.puts
|
|
172
193
|
@prose_gap_pending = true
|
|
173
194
|
@io.puts((' ' * indent) + yield)
|
|
@@ -182,8 +203,10 @@ class Riffer::Rig::Terminal::Renderer
|
|
|
182
203
|
if @prose_gap_pending
|
|
183
204
|
@prose_gap_pending = false
|
|
184
205
|
@io.print("\n")
|
|
206
|
+
@wrapper = new_wrapper
|
|
185
207
|
end
|
|
186
|
-
@
|
|
208
|
+
emit = @wrapper << content
|
|
209
|
+
@smoother << emit unless emit.empty?
|
|
187
210
|
end
|
|
188
211
|
|
|
189
212
|
# @rbs indent: Integer
|
|
@@ -191,7 +214,7 @@ class Riffer::Rig::Terminal::Renderer
|
|
|
191
214
|
# @rbs return: void
|
|
192
215
|
def render_tool_activity(indent, &)
|
|
193
216
|
open_tool_activity
|
|
194
|
-
@io.puts((' ' * indent) + yield)
|
|
217
|
+
@io.puts((' ' * indent) + @theme.cyan(fit(yield, indent)))
|
|
195
218
|
@io.flush
|
|
196
219
|
end
|
|
197
220
|
|
|
@@ -201,7 +224,7 @@ class Riffer::Rig::Terminal::Renderer
|
|
|
201
224
|
# group; the group stays open so the prose after it pays the closing gap.
|
|
202
225
|
return if @prose_gap_pending
|
|
203
226
|
|
|
204
|
-
|
|
227
|
+
flush_prose
|
|
205
228
|
@io.puts
|
|
206
229
|
@prose_gap_pending = true
|
|
207
230
|
end
|
|
@@ -224,7 +247,7 @@ class Riffer::Rig::Terminal::Renderer
|
|
|
224
247
|
# @rbs return: String
|
|
225
248
|
def format_arguments(arguments)
|
|
226
249
|
parsed = JSON.parse(arguments)
|
|
227
|
-
parsed.map { |key, value| "#{key}: #{value.inspect}" }.join(', ')
|
|
250
|
+
parsed.map { |key, value| "#{key}: #{elide(value.inspect, ARGUMENT_PREVIEW_LIMIT)}" }.join(', ')
|
|
228
251
|
rescue JSON::ParserError
|
|
229
252
|
arguments
|
|
230
253
|
end
|
|
@@ -233,8 +256,58 @@ class Riffer::Rig::Terminal::Renderer
|
|
|
233
256
|
# @rbs return: String
|
|
234
257
|
def preview(content)
|
|
235
258
|
first_line = content.to_s.lines.first.to_s.chomp
|
|
236
|
-
|
|
259
|
+
elide(first_line, RESULT_PREVIEW_LIMIT)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# @rbs text: String
|
|
263
|
+
# @rbs limit: Integer
|
|
264
|
+
# @rbs return: String
|
|
265
|
+
def elide(text, limit)
|
|
266
|
+
return text if text.length <= limit
|
|
267
|
+
return '…' if limit < 1
|
|
268
|
+
|
|
269
|
+
"#{text[0, limit - 1]}…"
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Truncation is what keeps an indented line from wrapping: a wrapped
|
|
273
|
+
# continuation starts at column zero and destroys the prose > tool nesting.
|
|
274
|
+
# @rbs text: String
|
|
275
|
+
# @rbs indent: Integer
|
|
276
|
+
# @rbs return: String
|
|
277
|
+
def fit(text, indent)
|
|
278
|
+
width = detect_width
|
|
279
|
+
return text unless width
|
|
280
|
+
|
|
281
|
+
elide(text, width - indent)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# @rbs return: void
|
|
285
|
+
def flush_prose
|
|
286
|
+
emit = @wrapper.flush
|
|
287
|
+
@smoother << emit unless emit.empty?
|
|
288
|
+
@smoother.drain
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# @rbs return: Riffer::Rig::Terminal::Wrapper
|
|
292
|
+
def new_wrapper
|
|
293
|
+
Riffer::Rig::Terminal::Wrapper.new(width: detect_width, indent: PROSE_INDENT)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# @rbs return: Integer?
|
|
297
|
+
def detect_width
|
|
298
|
+
@width || detected_width
|
|
299
|
+
end
|
|
237
300
|
|
|
238
|
-
|
|
301
|
+
# winsize raises on ttys with no queryable window size and some pty
|
|
302
|
+
# wrappers report zero columns; either way the real width is unknowable,
|
|
303
|
+
# and nil leaves every consumer un-fitted rather than mis-fitted.
|
|
304
|
+
# @rbs return: Integer?
|
|
305
|
+
def detected_width
|
|
306
|
+
return nil unless @io.tty?
|
|
307
|
+
|
|
308
|
+
width = @io.winsize[1]
|
|
309
|
+
width.positive? ? width : nil
|
|
310
|
+
rescue SystemCallError
|
|
311
|
+
nil
|
|
239
312
|
end
|
|
240
313
|
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Riffer::Rig::Terminal::Wrapper
|
|
4
|
+
# @rbs @width: Integer?
|
|
5
|
+
# @rbs @indent: Integer
|
|
6
|
+
# @rbs @line: String
|
|
7
|
+
|
|
8
|
+
# @rbs width: Integer?
|
|
9
|
+
# @rbs indent: Integer
|
|
10
|
+
# @rbs return: void
|
|
11
|
+
def initialize(width:, indent:)
|
|
12
|
+
@width = width
|
|
13
|
+
@indent = indent
|
|
14
|
+
@line = +''
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @rbs text: String
|
|
18
|
+
# @rbs return: String
|
|
19
|
+
def <<(text)
|
|
20
|
+
@line << text
|
|
21
|
+
out = +''
|
|
22
|
+
while (logical = take_line)
|
|
23
|
+
out << physical_lines(logical)
|
|
24
|
+
end
|
|
25
|
+
out
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @rbs return: String
|
|
29
|
+
def flush
|
|
30
|
+
out = +''
|
|
31
|
+
out << physical_lines(@line) unless @line.strip.empty?
|
|
32
|
+
@line = +''
|
|
33
|
+
out
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
# @rbs return: String?
|
|
39
|
+
def take_line
|
|
40
|
+
if (index = @line.index("\n"))
|
|
41
|
+
@line.slice!(0, index + 1).to_s.chomp
|
|
42
|
+
elsif overfull?
|
|
43
|
+
take_overfull_head
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @rbs return: bool
|
|
48
|
+
def overfull?
|
|
49
|
+
width = @width
|
|
50
|
+
return false unless width
|
|
51
|
+
|
|
52
|
+
@line.length > width - @indent
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @rbs return: String
|
|
56
|
+
def take_overfull_head
|
|
57
|
+
limit = (@width || 0) - @indent
|
|
58
|
+
head = @line[0, limit].to_s
|
|
59
|
+
if (space = head.rindex(' '))
|
|
60
|
+
@line.slice!(0, space + 1)
|
|
61
|
+
head[0, space].to_s
|
|
62
|
+
else
|
|
63
|
+
@line.slice!(0, limit).to_s
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @rbs logical: String
|
|
68
|
+
# @rbs return: String
|
|
69
|
+
def physical_lines(logical)
|
|
70
|
+
return "\n" if logical.empty?
|
|
71
|
+
|
|
72
|
+
wrap_words(logical).map { |line| "#{indent}#{line}\n" }.join
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @rbs return: String
|
|
76
|
+
def indent
|
|
77
|
+
' ' * @indent
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @rbs text: String
|
|
81
|
+
# @rbs return: Array[String]
|
|
82
|
+
def wrap_words(text)
|
|
83
|
+
width = @width
|
|
84
|
+
limit = width ? width - @indent : nil
|
|
85
|
+
return [text] unless limit
|
|
86
|
+
|
|
87
|
+
out = [] #: Array[String]
|
|
88
|
+
current = nil #: String?
|
|
89
|
+
text.split.each do |word|
|
|
90
|
+
if current
|
|
91
|
+
if current.length + 1 + word.length <= limit
|
|
92
|
+
current = "#{current} #{word}"
|
|
93
|
+
next
|
|
94
|
+
end
|
|
95
|
+
out << current
|
|
96
|
+
end
|
|
97
|
+
out << word.slice!(0, limit).to_s while word.length > limit
|
|
98
|
+
current = word unless word.empty?
|
|
99
|
+
end
|
|
100
|
+
out << current if current
|
|
101
|
+
out
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
module Riffer::Rig::Trust
|
|
7
|
+
extend self
|
|
8
|
+
|
|
9
|
+
# @rbs path: String
|
|
10
|
+
# @rbs return: Hash[String, bool]
|
|
11
|
+
def read(path)
|
|
12
|
+
return {} unless File.file?(path)
|
|
13
|
+
|
|
14
|
+
source = JSON.parse(File.read(path))
|
|
15
|
+
source.is_a?(Hash) ? source.select { |_file, decision| [true, false].include?(decision) } : {}
|
|
16
|
+
rescue JSON::ParserError
|
|
17
|
+
{}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @rbs path: String
|
|
21
|
+
# @rbs file: String
|
|
22
|
+
# @rbs decision: bool
|
|
23
|
+
# @rbs return: void
|
|
24
|
+
def store(path, file, decision)
|
|
25
|
+
write(path, read(path).merge(file => decision))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# @rbs path: String
|
|
31
|
+
# @rbs document: Hash[String, bool]
|
|
32
|
+
# @rbs return: void
|
|
33
|
+
def write(path, document)
|
|
34
|
+
FileUtils.mkdir_p(File.dirname(path), mode: 0o700)
|
|
35
|
+
File.write(path, JSON.pretty_generate(document))
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/riffer/rig/version.rb
CHANGED
|
@@ -10,6 +10,10 @@ class Riffer::Rig::Loader
|
|
|
10
10
|
|
|
11
11
|
NO_MODEL: String
|
|
12
12
|
|
|
13
|
+
EXTENSION_FEATURE: String
|
|
14
|
+
|
|
15
|
+
@tracked_files: Array[String]
|
|
16
|
+
|
|
13
17
|
@riffer_config: Riffer::Config
|
|
14
18
|
|
|
15
19
|
@home: String
|
|
@@ -20,6 +24,9 @@ class Riffer::Rig::Loader
|
|
|
20
24
|
|
|
21
25
|
@cwd: String
|
|
22
26
|
|
|
27
|
+
# @dynamic tracked_files
|
|
28
|
+
attr_reader tracked_files: Array[String]
|
|
29
|
+
|
|
23
30
|
# @rbs cwd: String
|
|
24
31
|
# @rbs host: Riffer::Rig::Hosts::_Host
|
|
25
32
|
# @rbs env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
|
|
@@ -53,6 +60,35 @@ class Riffer::Rig::Loader
|
|
|
53
60
|
|
|
54
61
|
private
|
|
55
62
|
|
|
63
|
+
# @rbs autoload: bool
|
|
64
|
+
# @rbs return: Array[Riffer::Rig::Extension]
|
|
65
|
+
def load_rig_files: (bool autoload) -> Array[Riffer::Rig::Extension]
|
|
66
|
+
|
|
67
|
+
# @rbs autoload: bool
|
|
68
|
+
# @rbs return: Array[String]
|
|
69
|
+
def gem_extension_files: (bool autoload) -> Array[String]
|
|
70
|
+
|
|
71
|
+
# @rbs path: String
|
|
72
|
+
# @rbs return: bool
|
|
73
|
+
def trusted?: (String path) -> bool
|
|
74
|
+
|
|
75
|
+
# @rbs return: String
|
|
76
|
+
def trust_path: () -> String
|
|
77
|
+
|
|
78
|
+
# @rbs path: String
|
|
79
|
+
# @rbs track: bool
|
|
80
|
+
# @rbs return: Array[Riffer::Rig::Extension]
|
|
81
|
+
def load_file: (String path, track: bool) -> Array[Riffer::Rig::Extension]
|
|
82
|
+
|
|
83
|
+
# @rbs before_features: Array[String]
|
|
84
|
+
# @rbs path: String
|
|
85
|
+
# @rbs return: void
|
|
86
|
+
def track_loaded: (Array[String] before_features, String path) -> void
|
|
87
|
+
|
|
88
|
+
# @rbs feature: String
|
|
89
|
+
# @rbs return: bool
|
|
90
|
+
def local?: (String feature) -> bool
|
|
91
|
+
|
|
56
92
|
# @rbs configured: String?
|
|
57
93
|
# @rbs return: String
|
|
58
94
|
def select_model: (String? configured) -> String
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Generated from lib/riffer/rig/settings/document.rb with RBS::Inline
|
|
2
2
|
|
|
3
3
|
class Riffer::Rig::Settings::Document
|
|
4
|
-
# @dynamic model, reasoning, models, providers, disabled
|
|
4
|
+
# @dynamic model, reasoning, models, providers, disabled, autoload
|
|
5
5
|
attr_reader model: String?
|
|
6
6
|
|
|
7
7
|
attr_reader reasoning: String?
|
|
@@ -12,6 +12,8 @@ class Riffer::Rig::Settings::Document
|
|
|
12
12
|
|
|
13
13
|
attr_reader disabled: Array[String]
|
|
14
14
|
|
|
15
|
+
attr_reader autoload: bool
|
|
16
|
+
|
|
15
17
|
# @rbs source: Hash[Symbol, untyped]
|
|
16
18
|
# @rbs return: void
|
|
17
19
|
def initialize: (Hash[Symbol, untyped] source) -> void
|
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
class Riffer::Rig::Terminal::Renderer
|
|
4
4
|
RESULT_PREVIEW_LIMIT: Integer
|
|
5
5
|
|
|
6
|
+
ARGUMENT_PREVIEW_LIMIT: Integer
|
|
7
|
+
|
|
8
|
+
PROSE_INDENT: Integer
|
|
9
|
+
|
|
10
|
+
TOOL_CALL_INDENT: Integer
|
|
11
|
+
|
|
12
|
+
TOOL_RESULT_INDENT: Integer
|
|
13
|
+
|
|
14
|
+
@wrapper: Riffer::Rig::Terminal::Wrapper
|
|
15
|
+
|
|
16
|
+
@width: Integer?
|
|
17
|
+
|
|
6
18
|
@prose_gap_pending: bool
|
|
7
19
|
|
|
8
20
|
@cursor: Riffer::Rig::Terminal::Cursor
|
|
@@ -17,8 +29,9 @@ class Riffer::Rig::Terminal::Renderer
|
|
|
17
29
|
# @rbs theme: Riffer::Rig::Terminal::Theme
|
|
18
30
|
# @rbs smoother: Riffer::Rig::Terminal::Smoother | PassThroughSmoother
|
|
19
31
|
# @rbs cursor: Riffer::Rig::Terminal::Cursor
|
|
32
|
+
# @rbs width: Integer?
|
|
20
33
|
# @rbs return: void
|
|
21
|
-
def initialize: (?io: IO, ?theme: Riffer::Rig::Terminal::Theme, ?smoother: Riffer::Rig::Terminal::Smoother | PassThroughSmoother, ?cursor: Riffer::Rig::Terminal::Cursor) -> void
|
|
34
|
+
def initialize: (?io: IO, ?theme: Riffer::Rig::Terminal::Theme, ?smoother: Riffer::Rig::Terminal::Smoother | PassThroughSmoother, ?cursor: Riffer::Rig::Terminal::Cursor, ?width: Integer?) -> void
|
|
22
35
|
|
|
23
36
|
# @rbs event: Riffer::StreamEvents::TextDelta | Riffer::StreamEvents::ToolCallDone | Riffer::StreamEvents::Interrupt
|
|
24
37
|
# @rbs return: void
|
|
@@ -123,4 +136,31 @@ class Riffer::Rig::Terminal::Renderer
|
|
|
123
136
|
# @rbs content: String
|
|
124
137
|
# @rbs return: String
|
|
125
138
|
def preview: (String content) -> String
|
|
139
|
+
|
|
140
|
+
# @rbs text: String
|
|
141
|
+
# @rbs limit: Integer
|
|
142
|
+
# @rbs return: String
|
|
143
|
+
def elide: (String text, Integer limit) -> String
|
|
144
|
+
|
|
145
|
+
# Truncation is what keeps an indented line from wrapping: a wrapped
|
|
146
|
+
# continuation starts at column zero and destroys the prose > tool nesting.
|
|
147
|
+
# @rbs text: String
|
|
148
|
+
# @rbs indent: Integer
|
|
149
|
+
# @rbs return: String
|
|
150
|
+
def fit: (String text, Integer indent) -> String
|
|
151
|
+
|
|
152
|
+
# @rbs return: void
|
|
153
|
+
def flush_prose: () -> void
|
|
154
|
+
|
|
155
|
+
# @rbs return: Riffer::Rig::Terminal::Wrapper
|
|
156
|
+
def new_wrapper: () -> Riffer::Rig::Terminal::Wrapper
|
|
157
|
+
|
|
158
|
+
# @rbs return: Integer?
|
|
159
|
+
def detect_width: () -> Integer?
|
|
160
|
+
|
|
161
|
+
# winsize raises on ttys with no queryable window size and some pty
|
|
162
|
+
# wrappers report zero columns; either way the real width is unknowable,
|
|
163
|
+
# and nil leaves every consumer un-fitted rather than mis-fitted.
|
|
164
|
+
# @rbs return: Integer?
|
|
165
|
+
def detected_width: () -> Integer?
|
|
126
166
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Generated from lib/riffer/rig/terminal/wrapper.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class Riffer::Rig::Terminal::Wrapper
|
|
4
|
+
@width: Integer?
|
|
5
|
+
|
|
6
|
+
@indent: Integer
|
|
7
|
+
|
|
8
|
+
@line: String
|
|
9
|
+
|
|
10
|
+
# @rbs width: Integer?
|
|
11
|
+
# @rbs indent: Integer
|
|
12
|
+
# @rbs return: void
|
|
13
|
+
def initialize: (width: Integer?, indent: Integer) -> void
|
|
14
|
+
|
|
15
|
+
# @rbs text: String
|
|
16
|
+
# @rbs return: String
|
|
17
|
+
def <<: (String text) -> String
|
|
18
|
+
|
|
19
|
+
# @rbs return: String
|
|
20
|
+
def flush: () -> String
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# @rbs return: String?
|
|
25
|
+
def take_line: () -> String?
|
|
26
|
+
|
|
27
|
+
# @rbs return: bool
|
|
28
|
+
def overfull?: () -> bool
|
|
29
|
+
|
|
30
|
+
# @rbs return: String
|
|
31
|
+
def take_overfull_head: () -> String
|
|
32
|
+
|
|
33
|
+
# @rbs logical: String
|
|
34
|
+
# @rbs return: String
|
|
35
|
+
def physical_lines: (String logical) -> String
|
|
36
|
+
|
|
37
|
+
# @rbs return: String
|
|
38
|
+
def indent: () -> String
|
|
39
|
+
|
|
40
|
+
# @rbs text: String
|
|
41
|
+
# @rbs return: Array[String]
|
|
42
|
+
def wrap_words: (String text) -> Array[String]
|
|
43
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Generated from lib/riffer/rig/trust.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Riffer::Rig::Trust
|
|
4
|
+
# @rbs path: String
|
|
5
|
+
# @rbs return: Hash[String, bool]
|
|
6
|
+
def read: (String path) -> Hash[String, bool]
|
|
7
|
+
|
|
8
|
+
# @rbs path: String
|
|
9
|
+
# @rbs file: String
|
|
10
|
+
# @rbs decision: bool
|
|
11
|
+
# @rbs return: void
|
|
12
|
+
def store: (String path, String file, bool decision) -> void
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# @rbs path: String
|
|
17
|
+
# @rbs document: Hash[String, bool]
|
|
18
|
+
# @rbs return: void
|
|
19
|
+
def write: (String path, Hash[String, bool] document) -> void
|
|
20
|
+
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.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jake Bottrall
|
|
@@ -200,10 +200,12 @@ files:
|
|
|
200
200
|
- lib/riffer/rig/terminal/renderer.rb
|
|
201
201
|
- lib/riffer/rig/terminal/smoother.rb
|
|
202
202
|
- lib/riffer/rig/terminal/theme.rb
|
|
203
|
+
- lib/riffer/rig/terminal/wrapper.rb
|
|
203
204
|
- lib/riffer/rig/tools/bash.rb
|
|
204
205
|
- lib/riffer/rig/tools/edit.rb
|
|
205
206
|
- lib/riffer/rig/tools/read.rb
|
|
206
207
|
- lib/riffer/rig/tools/write.rb
|
|
208
|
+
- lib/riffer/rig/trust.rb
|
|
207
209
|
- lib/riffer/rig/version.rb
|
|
208
210
|
- sig/generated/riffer/rig.rbs
|
|
209
211
|
- sig/generated/riffer/rig/bundled.rbs
|
|
@@ -272,10 +274,12 @@ files:
|
|
|
272
274
|
- sig/generated/riffer/rig/terminal/renderer.rbs
|
|
273
275
|
- sig/generated/riffer/rig/terminal/smoother.rbs
|
|
274
276
|
- sig/generated/riffer/rig/terminal/theme.rbs
|
|
277
|
+
- sig/generated/riffer/rig/terminal/wrapper.rbs
|
|
275
278
|
- sig/generated/riffer/rig/tools/bash.rbs
|
|
276
279
|
- sig/generated/riffer/rig/tools/edit.rbs
|
|
277
280
|
- sig/generated/riffer/rig/tools/read.rbs
|
|
278
281
|
- sig/generated/riffer/rig/tools/write.rbs
|
|
282
|
+
- sig/generated/riffer/rig/trust.rbs
|
|
279
283
|
- sig/generated/riffer/rig/version.rbs
|
|
280
284
|
- sig/manual/riffer/rig.rbs
|
|
281
285
|
- sig/manual/riffer/rig/cli.rbs
|
|
@@ -292,6 +296,7 @@ files:
|
|
|
292
296
|
- sig/manual/riffer/rig/support.rbs
|
|
293
297
|
- sig/manual/riffer/rig/terminal/banner.rbs
|
|
294
298
|
- sig/manual/riffer/rig/tools.rbs
|
|
299
|
+
- sig/manual/riffer/rig/trust.rbs
|
|
295
300
|
homepage: https://github.com/bottrall/riffer-rig
|
|
296
301
|
licenses:
|
|
297
302
|
- MIT
|