riffer-rig 0.16.0 → 0.17.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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/README.md +3 -18
  4. data/lib/riffer/rig/acp/mapper.rb +14 -2
  5. data/lib/riffer/rig/acp/servers.rb +42 -0
  6. data/lib/riffer/rig/acp.rb +17 -56
  7. data/lib/riffer/rig/cli/flags.rb +5 -0
  8. data/lib/riffer/rig/cli/sessions.rb +0 -3
  9. data/lib/riffer/rig/cli.rb +0 -3
  10. data/lib/riffer/rig/commands/auth.rb +28 -20
  11. data/lib/riffer/rig/commands/model.rb +37 -34
  12. data/lib/riffer/rig/commands/reasoning.rb +10 -10
  13. data/lib/riffer/rig/commands/reload.rb +6 -6
  14. data/lib/riffer/rig/credentials.rb +46 -14
  15. data/lib/riffer/rig/directories.rb +7 -0
  16. data/lib/riffer/rig/extension.rb +1 -0
  17. data/lib/riffer/rig/headless.rb +3 -2
  18. data/lib/riffer/rig/loader/extension_source.rb +4 -4
  19. data/lib/riffer/rig/loader/reasoning.rb +25 -0
  20. data/lib/riffer/rig/loader/reloader.rb +91 -0
  21. data/lib/riffer/rig/loader.rb +52 -171
  22. data/lib/riffer/rig/mcp/auth.rb +11 -22
  23. data/lib/riffer/rig/mcp/declaration.rb +3 -3
  24. data/lib/riffer/rig/mcp.rb +0 -8
  25. data/lib/riffer/rig/prompts/agents_md.rb +1 -1
  26. data/lib/riffer/rig/provider_setup.rb +22 -3
  27. data/lib/riffer/rig/runtime/assembly.rb +128 -0
  28. data/lib/riffer/rig/{tools/bash → runtime}/job.rb +4 -5
  29. data/lib/riffer/rig/{tools/bash → runtime}/job_log.rb +1 -1
  30. data/lib/riffer/rig/runtime/job_notices.rb +89 -0
  31. data/lib/riffer/rig/{tools/bash → runtime}/jobs.rb +38 -29
  32. data/lib/riffer/rig/runtime/mcp_registrar.rb +4 -3
  33. data/lib/riffer/rig/runtime/snapshot.rb +18 -0
  34. data/lib/riffer/rig/runtime/stop_reason.rb +22 -0
  35. data/lib/riffer/rig/runtime/turn.rb +151 -0
  36. data/lib/riffer/rig/runtime.rb +108 -347
  37. data/lib/riffer/rig/sdk.rb +8 -10
  38. data/lib/riffer/rig/settings/file.rb +3 -3
  39. data/lib/riffer/rig/settings/model_options.rb +23 -0
  40. data/lib/riffer/rig/settings.rb +2 -1
  41. data/lib/riffer/rig/stores/entries.rb +4 -4
  42. data/lib/riffer/rig/stores/jsonl.rb +1 -1
  43. data/lib/riffer/rig/stores/message_entry.rb +1 -1
  44. data/lib/riffer/rig/stores/model_entry.rb +1 -1
  45. data/lib/riffer/rig/stores/reasoning_entry.rb +1 -1
  46. data/lib/riffer/rig/stores/recorder.rb +7 -11
  47. data/lib/riffer/rig/stores/skill_entry.rb +1 -1
  48. data/lib/riffer/rig/stores/timestamp.rb +6 -0
  49. data/lib/riffer/rig/terminal/banner.rb +9 -2
  50. data/lib/riffer/rig/terminal/count.rb +6 -0
  51. data/lib/riffer/rig/terminal/frame.rb +1 -3
  52. data/lib/riffer/rig/terminal/message_queue.rb +4 -4
  53. data/lib/riffer/rig/terminal/picker.rb +5 -2
  54. data/lib/riffer/rig/terminal/repl.rb +1 -1
  55. data/lib/riffer/rig/terminal/session.rb +0 -2
  56. data/lib/riffer/rig/terminal/status_bar.rb +2 -2
  57. data/lib/riffer/rig/terminal/theme.rb +1 -0
  58. data/lib/riffer/rig/terminal/ticker.rb +3 -1
  59. data/lib/riffer/rig/terminal/turn_report.rb +1 -1
  60. data/lib/riffer/rig/tools/bash/kill.rb +1 -1
  61. data/lib/riffer/rig/tools/bash/output.rb +3 -2
  62. data/lib/riffer/rig/tools/bash.rb +3 -4
  63. data/lib/riffer/rig/version.rb +1 -1
  64. data/sig/generated/riffer/rig/acp/mapper.rbs +4 -0
  65. data/sig/generated/riffer/rig/acp/servers.rbs +21 -0
  66. data/sig/generated/riffer/rig/acp.rbs +4 -19
  67. data/sig/generated/riffer/rig/cli/flags.rbs +2 -0
  68. data/sig/generated/riffer/rig/commands/auth.rbs +18 -5
  69. data/sig/generated/riffer/rig/commands/model.rbs +19 -13
  70. data/sig/generated/riffer/rig/commands/reasoning.rbs +4 -6
  71. data/sig/generated/riffer/rig/commands/reload.rbs +4 -4
  72. data/sig/generated/riffer/rig/credentials.rbs +18 -1
  73. data/sig/generated/riffer/rig/directories.rbs +5 -0
  74. data/sig/generated/riffer/rig/loader/reasoning.rbs +13 -0
  75. data/sig/generated/riffer/rig/loader/reloader.rbs +50 -0
  76. data/sig/generated/riffer/rig/loader.rbs +13 -88
  77. data/sig/generated/riffer/rig/mcp/auth.rbs +6 -9
  78. data/sig/generated/riffer/rig/mcp/declaration.rbs +0 -3
  79. data/sig/generated/riffer/rig/mcp.rbs +0 -4
  80. data/sig/generated/riffer/rig/provider_setup.rbs +10 -5
  81. data/sig/generated/riffer/rig/runtime/assembly.rbs +74 -0
  82. data/sig/generated/riffer/rig/{tools/bash → runtime}/job.rbs +8 -8
  83. data/sig/generated/riffer/rig/{tools/bash → runtime}/job_log.rbs +2 -2
  84. data/sig/generated/riffer/rig/runtime/job_notices.rbs +46 -0
  85. data/sig/generated/riffer/rig/runtime/jobs.rbs +86 -0
  86. data/sig/generated/riffer/rig/runtime/snapshot.rbs +12 -0
  87. data/sig/generated/riffer/rig/runtime/stop_reason.rbs +13 -0
  88. data/sig/generated/riffer/rig/runtime/turn.rbs +74 -0
  89. data/sig/generated/riffer/rig/runtime.rbs +45 -148
  90. data/sig/generated/riffer/rig/sdk.rbs +5 -5
  91. data/sig/generated/riffer/rig/settings/file.rbs +2 -2
  92. data/sig/generated/riffer/rig/settings/model_options.rbs +12 -0
  93. data/sig/generated/riffer/rig/stores/entries.rbs +2 -2
  94. data/sig/generated/riffer/rig/stores/recorder.rbs +2 -8
  95. data/sig/generated/riffer/rig/stores/timestamp.rbs +4 -0
  96. data/sig/generated/riffer/rig/terminal/banner.rbs +4 -0
  97. data/sig/generated/riffer/rig/terminal/count.rbs +4 -0
  98. data/sig/generated/riffer/rig/terminal/frame.rbs +0 -2
  99. data/sig/generated/riffer/rig/terminal/message_queue.rbs +5 -5
  100. data/sig/generated/riffer/rig/terminal/picker.rbs +4 -1
  101. data/sig/generated/riffer/rig/terminal/ticker.rbs +2 -0
  102. data/sig/generated/riffer/rig/tools/bash.rbs +0 -2
  103. data/sig/manual/riffer/rig/{terminal/tally.rbs → acp/servers.rbs} +2 -2
  104. data/sig/manual/riffer/rig/{terminal/spinner.rbs → loader/reasoning.rbs} +2 -2
  105. data/sig/manual/riffer/rig/mcp.rbs +0 -5
  106. data/sig/manual/riffer/rig/{loader/credentials.rbs → runtime/stop_reason.rbs} +2 -2
  107. metadata +24 -22
  108. data/lib/riffer/rig/loader/credentials.rb +0 -23
  109. data/lib/riffer/rig/terminal/message_queue/message.rb +0 -13
  110. data/lib/riffer/rig/terminal/reasoning.rb +0 -5
  111. data/lib/riffer/rig/terminal/spinner.rb +0 -13
  112. data/lib/riffer/rig/terminal/tally.rb +0 -12
  113. data/lib/riffer/rig/tools/bash/monotonic_clock.rb +0 -8
  114. data/sig/generated/riffer/rig/loader/credentials.rbs +0 -12
  115. data/sig/generated/riffer/rig/terminal/message_queue/message.rbs +0 -10
  116. data/sig/generated/riffer/rig/terminal/reasoning.rbs +0 -5
  117. data/sig/generated/riffer/rig/terminal/spinner.rbs +0 -9
  118. data/sig/generated/riffer/rig/terminal/tally.rbs +0 -7
  119. data/sig/generated/riffer/rig/tools/bash/jobs.rbs +0 -86
  120. data/sig/generated/riffer/rig/tools/bash/monotonic_clock.rbs +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8651df8d201d07a31d22ee2df71fde5942a6219dc405a6e51ab4661f8b910c9d
4
- data.tar.gz: 472fe4de066e0a621f61f4ff3da31233b325731709f974ad396973269de36b86
3
+ metadata.gz: 9b1648704ba63c4826d30aec44988486557c4a2fccc5b3b527fef879fc407cef
4
+ data.tar.gz: 574c73fcb0f3512f486d6d4c355b69add66d54de82c87ed170c8409821c4b3f1
5
5
  SHA512:
6
- metadata.gz: 448c573762bcf24c313c5f214ebb8c17988339f74b8a511c1eb9948ef3303a46bec300f7f092a4ba4507a1b03f1dbfa2db43eda464ec78b1254df0455b86886f
7
- data.tar.gz: 6c68f67169c7f20ea0ad31f3b2937d4b487b651033f93426d79d6a97ca248a8086bc247cff97688ad7406ea6f30fe9e973c166788f802bbcc99f9a8dad47ab97
6
+ metadata.gz: 0da1408916c461365dc8d61c07dca3b00ad5f2d22d77e42d036d379ce6231c5cff0ae3ade79a2d8f907439c8e70e33753ae3e5b91786905e5366bf414d0f3521
7
+ data.tar.gz: 994802e5749d8708313eda67386af949ff4112b9b2275b6687a2078ea2f4332b8147916608cdbf8adb5f90c632bea9b395d5929b9024907f4811e2ecca75eff1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.17.0](https://github.com/bottrall/riffer-rig/compare/v0.16.0...v0.17.0) (2026-10-24)
4
+
5
+
6
+ ### Features
7
+
8
+ * authenticate to Anthropic through the SDK's credential chain ([#606](https://github.com/bottrall/riffer-rig/issues/606)) ([617fdb7](https://github.com/bottrall/riffer-rig/commit/617fdb76f5a54290d2d001b906fce3d530e2cef4))
9
+ * give tools the background job registry as a documented context key ([#579](https://github.com/bottrall/riffer-rig/issues/579)) ([a8e98ac](https://github.com/bottrall/riffer-rig/commit/a8e98acef71b354a2c7601d46366ed0f95b6da37))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * --save and /auth honour the injected home ([#596](https://github.com/bottrall/riffer-rig/issues/596)) ([032f26f](https://github.com/bottrall/riffer-rig/commit/032f26f317216f501a9dc71c3d1157ce12bc3893))
15
+ * /auth and /model refusals say how to supply a missing credential ([#597](https://github.com/bottrall/riffer-rig/issues/597)) ([ffe18fe](https://github.com/bottrall/riffer-rig/commit/ffe18fe653db97f4bbb737359f454637bc347778))
16
+ * reasoning in settings reaches the agent ([#577](https://github.com/bottrall/riffer-rig/issues/577)) ([9216429](https://github.com/bottrall/riffer-rig/commit/9216429421be51bb7511e4c8b6eec3bd177e8ba3))
17
+ * reload each Loader-built runtime with its own build's choices ([#598](https://github.com/bottrall/riffer-rig/issues/598)) ([d6b95a6](https://github.com/bottrall/riffer-rig/commit/d6b95a6f1205ecb8ae0221c88bb1012c2ce15e79))
18
+ * **terminal:** keep a capped input inside a short terminal ([#575](https://github.com/bottrall/riffer-rig/issues/575)) ([8b67c2b](https://github.com/bottrall/riffer-rig/commit/8b67c2b9d1ba404ffb12bb42f120e034aae8183b))
19
+
3
20
  ## [0.16.0](https://github.com/bottrall/riffer-rig/compare/v0.15.0...v0.16.0) (2026-10-18)
4
21
 
5
22
 
data/README.md CHANGED
@@ -25,14 +25,6 @@ The first run asks for a model as `provider/name` — for example `anthropic/cla
25
25
 
26
26
  Type a prompt and press Enter. `/help` lists the commands, and [Getting started](docs/GETTING_STARTED.md) walks through a first session.
27
27
 
28
- To run one prompt from a script or CI job:
29
-
30
- ```bash
31
- git diff | riffer -p "review this diff"
32
- ```
33
-
34
- [Headless](docs/HEADLESS.md) covers exit codes and the NDJSON stream; editors connect over [ACP](docs/ACP.md) with `riffer acp`.
35
-
36
28
  ## Extend it
37
29
 
38
30
  An extension is a Ruby block in `~/.riffer/rig.rb` (every project) or `.riffer/rig.rb` (one project):
@@ -62,20 +54,13 @@ The guides are at **<https://riffer.bottrall.dev>**, and their sources are in [`
62
54
  - [Overview](docs/OVERVIEW.md) — what riffer-rig is and how it fits together
63
55
  - [Configuration](docs/CONFIGURATION.md) — settings and where each value comes from
64
56
  - [Command line](docs/CLI.md) — every flag
57
+ - [Headless mode](docs/HEADLESS.md) — one prompt from a script or CI job, with exit codes and an NDJSON stream
58
+ - [ACP](docs/ACP.md) — riffer-rig in your editor, with `riffer acp`
65
59
  - [Extensions](docs/EXTENSIONS.md) — your own tools, commands and hooks
66
60
 
67
- ## Development
68
-
69
- ```bash
70
- bin/setup # install gems and RBS signatures
71
- bin/ci # lint, test, type-check and build the docs, as CI does
72
- ```
73
-
74
- `AGENTS.md` lists every script and the project's conventions. PR titles follow [Conventional Commits](https://www.conventionalcommits.org/); [release-please](https://github.com/googleapis/release-please) turns them into releases.
75
-
76
61
  ## Contributing
77
62
 
78
- Fork the repository, make your change on a branch, run `bin/ci`, and open a pull request with a clear description. Please follow the [Code of Conduct](CODE_OF_CONDUCT.md).
63
+ [`AGENTS.md`](AGENTS.md) has the scripts and the project's conventions. Please follow the [Code of Conduct](CODE_OF_CONDUCT.md).
79
64
 
80
65
  ## License
81
66
 
@@ -27,6 +27,18 @@ module Riffer::Rig::ACP::Mapper
27
27
  blocks.filter_map { |block| prompt_text_of(block) }.join("\n\n")
28
28
  end
29
29
 
30
+ # @rbs event: ::Riffer::StreamEvents::Base | Riffer::Rig::Events::_Event
31
+ # @rbs return: ::ACP::Types::SessionUpdate::t?
32
+ def update_for(event)
33
+ case event
34
+ when Riffer::StreamEvents::TextDelta then message_chunk(event.content)
35
+ when Riffer::Rig::Events::CommandOutput then message_chunk(event.text)
36
+ when Riffer::StreamEvents::ToolCallDone then tool_call(event)
37
+ when Riffer::Rig::Events::AfterToolCall then tool_call_done(event)
38
+ when Riffer::Rig::Events::JobExit then job_exit
39
+ end
40
+ end
41
+
30
42
  # @rbs content: String
31
43
  # @rbs return: ::ACP::Types::SessionUpdate::AgentMessageChunk
32
44
  def message_chunk(content)
@@ -96,10 +108,10 @@ module Riffer::Rig::ACP::Mapper
96
108
  def stop_reason_of(stop_reason)
97
109
  case stop_reason
98
110
  when :completed then ::ACP::Types::StopReason::END_TURN
99
- when Riffer::Rig::Runtime::INTERRUPT_CANCELLED then ::ACP::Types::StopReason::CANCELLED
111
+ when Riffer::Rig::Runtime::StopReason::INTERRUPT_CANCELLED then ::ACP::Types::StopReason::CANCELLED
100
112
  when :max_steps then ::ACP::Types::StopReason::MAX_TURN_REQUESTS
101
113
  else
102
- stopped = Riffer::Rig::Runtime::STOPPED_WITHOUT_COMPLETING.include?(stop_reason)
114
+ stopped = Riffer::Rig::Runtime::StopReason::STOPPED_WITHOUT_COMPLETING.include?(stop_reason)
103
115
  stopped ? ::ACP::Types::StopReason::MAX_TOKENS : ::ACP::Types::StopReason::REFUSAL
104
116
  end
105
117
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Riffer::Rig::ACP::Servers
4
+ extend self
5
+
6
+ # @rbs servers: Array[::ACP::Types::McpServer::t]
7
+ # @rbs return: Hash[String, Riffer::Rig::Mcp::Declaration]
8
+ def declarations(servers)
9
+ servers.filter_map { |server| declaration_of(server) }.to_h
10
+ end
11
+
12
+ # @rbs servers: Array[::ACP::Types::McpServer::t]
13
+ # @rbs return: Array[String]
14
+ def warnings(servers)
15
+ servers.filter_map do |server|
16
+ next if server.is_a?(::ACP::Types::McpServerStdio)
17
+
18
+ "Skipped MCP server #{name_of(server)}: only stdio servers are supported"
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ # @rbs server: ::ACP::Types::McpServer::t
25
+ # @rbs return: [String, Riffer::Rig::Mcp::Declaration]?
26
+ def declaration_of(server)
27
+ return unless server.is_a?(::ACP::Types::McpServerStdio)
28
+
29
+ [
30
+ server.name,
31
+ Riffer::Rig::Mcp::Declaration.new(
32
+ command: server.command, args: server.args, env: server.env.to_h { |variable| [variable.name, variable.value] }
33
+ )
34
+ ]
35
+ end
36
+
37
+ # @rbs server: ::ACP::Types::McpServer::t
38
+ # @rbs return: String
39
+ def name_of(server)
40
+ server.is_a?(Hash) ? server['name'].to_s : server.name
41
+ end
42
+ end
@@ -36,10 +36,6 @@ class Riffer::Rig::ACP
36
36
  new(input: input, output: output, env: env, home: home, cwd: cwd, build: build, adopt: adopt)
37
37
  end
38
38
 
39
- # Builds the Runtime a session runs on: the client's MCP servers flow
40
- # through the rig.mcp seam as a synthetic extension, so a reload rebuild
41
- # re-declares them and a same-name declaration from an extension replaces
42
- # them with the usual notify.
43
39
  # @rbs cwd: String
44
40
  # @rbs host: Riffer::Rig::Hosts::_Host
45
41
  # @rbs servers: Hash[String, Riffer::Rig::Mcp::Declaration]
@@ -57,6 +53,8 @@ class Riffer::Rig::ACP
57
53
  def self.session_extensions(servers)
58
54
  return [] if servers.empty?
59
55
 
56
+ # A synthetic extension, so a reload rebuild re-declares the servers and an
57
+ # extension's same-name declaration replaces them with the usual notify.
60
58
  extension = Riffer::Rig::Extension.new(SESSION_EXTENSION) do |rig|
61
59
  servers.each { |name, server| rig.mcp(name, command: server.command, args: server.args, env: server.env) }
62
60
  end
@@ -88,8 +86,6 @@ class Riffer::Rig::ACP
88
86
  @lock = Mutex.new
89
87
  end
90
88
 
91
- # Serves the connection until the client closes stdin; then the reader
92
- # thread ends and run returns.
93
89
  # @rbs &block: ? (::ACP::AgentConnection) -> void
94
90
  # @rbs return: Integer
95
91
  def run
@@ -129,7 +125,7 @@ class Riffer::Rig::ACP
129
125
  end
130
126
  ::ACP::Types::NewSessionResponse.new(session_id: runtime.id)
131
127
  rescue StandardError => e
132
- ::ACP::RequestError.new(code: ::ACP::RequestError::INTERNAL_ERROR, message: e.message)
128
+ internal_error(e)
133
129
  end
134
130
 
135
131
  # @rbs request: ::ACP::Types::PromptRequest
@@ -143,19 +139,10 @@ class Riffer::Rig::ACP
143
139
  stop_reason = :completed
144
140
  dispatch(runtime, Riffer::Rig::ACP::Mapper.prompt_text(request.prompt)) do |event|
145
141
  case event
146
- when Riffer::StreamEvents::TextDelta
147
- client.update(request.session_id, Riffer::Rig::ACP::Mapper.message_chunk(event.content))
148
- when Riffer::Rig::Events::CommandOutput
149
- client.update(request.session_id, Riffer::Rig::ACP::Mapper.message_chunk(event.text))
150
- when Riffer::StreamEvents::ToolCallDone
151
- client.update(request.session_id, Riffer::Rig::ACP::Mapper.tool_call(event))
152
- when Riffer::Rig::Events::AfterToolCall
153
- client.update(request.session_id, Riffer::Rig::ACP::Mapper.tool_call_done(event))
154
- when Riffer::Rig::Events::JobExit
155
- client.update(request.session_id, Riffer::Rig::ACP::Mapper.job_exit)
156
- when Riffer::Rig::Events::TurnEnd
157
- stop_reason = event.stop_reason
142
+ when Riffer::Rig::Events::TurnEnd then stop_reason = event.stop_reason
158
143
  end
144
+ update = Riffer::Rig::ACP::Mapper.update_for(event)
145
+ client.update(request.session_id, update) if update
159
146
  end
160
147
  # A reload in the dispatch, by /reload or the automatic check, can change
161
148
  # the commands; its rebuild also reorders them, which is no change.
@@ -168,9 +155,7 @@ class Riffer::Rig::ACP
168
155
  message: 'a prompt is already running on this session; wait for it to finish or cancel it'
169
156
  )
170
157
  rescue StandardError => e
171
- ::ACP::RequestError.new(
172
- code: ::ACP::RequestError::INTERNAL_ERROR, message: Riffer::Rig::Runtime::ErrorMessage.of(e)
173
- )
158
+ internal_error(e)
174
159
  end
175
160
 
176
161
  # @rbs request: ::ACP::Types::LoadSessionRequest
@@ -192,7 +177,7 @@ class Riffer::Rig::ACP
192
177
  client.available_commands(request.session_id, available_commands(runtime))
193
178
  ::ACP::Types::LoadSessionResponse.new
194
179
  rescue StandardError => e
195
- ::ACP::RequestError.new(code: ::ACP::RequestError::INTERNAL_ERROR, message: e.message)
180
+ internal_error(e)
196
181
  end
197
182
 
198
183
  # @rbs request: ::ACP::Types::ListSessionsRequest
@@ -215,7 +200,7 @@ class Riffer::Rig::ACP
215
200
  end
216
201
  ::ACP::Types::ListSessionsResponse.new(sessions: sessions)
217
202
  rescue StandardError => e
218
- ::ACP::RequestError.new(code: ::ACP::RequestError::INTERNAL_ERROR, message: e.message)
203
+ internal_error(e)
219
204
  end
220
205
 
221
206
  # Runs on the transport's reader thread, so it asks the turn to stop and
@@ -255,6 +240,12 @@ class Riffer::Rig::ACP
255
240
  @lock.synchronize { @sessions[id] }
256
241
  end
257
242
 
243
+ # @rbs error: StandardError
244
+ # @rbs return: ::ACP::RequestError
245
+ def internal_error(error)
246
+ ::ACP::RequestError.new(code: ::ACP::RequestError::INTERNAL_ERROR, message: Riffer::Rig::Runtime::ErrorMessage.of(error))
247
+ end
248
+
258
249
  # @rbs runtime: Riffer::Rig::Runtime
259
250
  # @rbs text: String
260
251
  # @rbs &: (::Riffer::StreamEvents::Base | Riffer::Rig::Events::_Event) -> void
@@ -312,38 +303,8 @@ class Riffer::Rig::ACP
312
303
  # @rbs host: Riffer::Rig::ACP::Host
313
304
  # @rbs return: Hash[String, Riffer::Rig::Mcp::Declaration]
314
305
  def declarations(servers, host)
315
- servers.filter_map { |server| declaration_of(server, host) }.to_h
316
- end
317
-
318
- # @rbs server: ::ACP::Types::McpServer::t
319
- # @rbs host: Riffer::Rig::ACP::Host
320
- # @rbs return: [String, Riffer::Rig::Mcp::Declaration]?
321
- def declaration_of(server, host)
322
- unless server.is_a?(::ACP::Types::McpServerStdio)
323
- host.notify("Skipped MCP server #{server_name(server)}: only stdio servers are supported", level: :warning)
324
- return nil
325
- end
326
-
327
- [
328
- server.name,
329
- Riffer::Rig::Mcp::Declaration.new(command: server.command, args: server.args, env: stdio_env(server))
330
- ]
331
- end
332
-
333
- # @rbs server: ::ACP::Types::McpServer::t
334
- # @rbs return: String
335
- def server_name(server)
336
- case server
337
- when ::ACP::Types::McpServer::Http, ::ACP::Types::McpServer::Sse then server.name
338
- when Hash then server['name'].to_s
339
- else 'unnamed'
340
- end
341
- end
342
-
343
- # @rbs server: ::ACP::Types::McpServerStdio
344
- # @rbs return: Hash[String, String]
345
- def stdio_env(server)
346
- server.env.to_h { |variable| [variable.name, variable.value] }
306
+ Riffer::Rig::ACP::Servers.warnings(servers).each { |warning| host.notify(warning, level: :warning) }
307
+ Riffer::Rig::ACP::Servers.declarations(servers)
347
308
  end
348
309
 
349
310
  # @rbs runtime: Riffer::Rig::Runtime
@@ -9,6 +9,8 @@ class Riffer::Rig::CLI::Flags
9
9
  riffer acp serve Runtimes as an ACP agent over stdio
10
10
  TEXT
11
11
 
12
+ HEADLESS_ONLY = %i[verbose json job-idle-timeout].freeze #: Array[Symbol]
13
+
12
14
  # @dynamic model, reasoning, extensions, skills, agents_md, tools, max_steps, help, version
13
15
  # @dynamic save, verbose, json, job_idle_timeout, headless, prompt, continue, resume
14
16
  attr_reader :model #: String?
@@ -34,6 +36,9 @@ class Riffer::Rig::CLI::Flags
34
36
  return "invalid argument: --max-steps #{flags.max_steps}" if flags.max_steps&.<(1)
35
37
  return "invalid argument: --job-idle-timeout #{flags.job_idle_timeout}" if flags.job_idle_timeout&.negative?
36
38
 
39
+ stray = HEADLESS_ONLY.find { |key| values.key?(key) } unless flags.headless || flags.help || flags.version
40
+ return "--#{stray} is only available with -p" if stray
41
+
37
42
  flags
38
43
  rescue OptionParser::ParseError => e
39
44
  e.message
@@ -3,9 +3,6 @@
3
3
  class Riffer::Rig::CLI::Sessions
4
4
  OFF = 'Sessions are off for this run (--no-save).' #: String
5
5
 
6
- # The terminal's sessions collaborator: translates the parsed flags into
7
- # Loader builds and store lookups, so the host never sees the flags.
8
- #
9
6
  # @rbs @flags: Riffer::Rig::CLI::Flags
10
7
  # @rbs @env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
11
8
  # @rbs @cwd: String
@@ -31,9 +31,6 @@ module Riffer::Rig::CLI
31
31
  return refuse(flags, error) if flags.is_a?(String)
32
32
  return help(output) if flags.help
33
33
  return version(output) if flags.version
34
- return refuse('--verbose is only available with -p', error) if flags.verbose && !flags.headless
35
- return refuse('--json is only available with -p', error) if flags.json && !flags.headless
36
- return refuse('--job-idle-timeout is only available with -p', error) if flags.job_idle_timeout && !flags.headless
37
34
 
38
35
  return headless(flags, input:, output:, error:, env:, cwd:, home:) if flags.headless
39
36
 
@@ -5,22 +5,28 @@ module Riffer::Rig::Commands::Auth
5
5
 
6
6
  USAGE = 'Use /auth, /auth <provider> or /auth remove <provider>' #: String
7
7
 
8
+ # @rbs env: Riffer::Rig::Env
9
+ # @rbs auth_path: String
10
+ # @rbs settings_path: String
8
11
  # @rbs return: Riffer::Rig::Command
9
- def command
12
+ def command(env:, auth_path:, settings_path:)
10
13
  Riffer::Rig::Command.new(
11
14
  'auth',
12
15
  description: 'Show, re-run or remove provider credentials',
13
16
  extension: :core
14
- ) { |ctx| run(ctx) }
17
+ ) { |ctx| run(ctx, env:, auth_path:, settings_path:) }
15
18
  end
16
19
 
17
20
  # @rbs ctx: Riffer::Rig::Command::Context
21
+ # @rbs env: Riffer::Rig::Env
22
+ # @rbs auth_path: String
23
+ # @rbs settings_path: String
18
24
  # @rbs return: void
19
- def run(ctx)
25
+ def run(ctx, env:, auth_path:, settings_path:)
20
26
  words = ctx.args.split
21
- return list(ctx) if words.empty?
22
- return remove(ctx, words[1]) if words[0] == 'remove' && words.length == 2
23
- return rerun(ctx, words[0]) if words.length == 1
27
+ return list(ctx, env:, auth_path:) if words.empty?
28
+ return remove(ctx, words[1], auth_path:, settings_path:) if words[0] == 'remove' && words.length == 2
29
+ return rerun(ctx, words[0], env:, auth_path:, settings_path:) if words.length == 1
24
30
 
25
31
  ctx.say(USAGE)
26
32
  end
@@ -34,10 +40,12 @@ module Riffer::Rig::Commands::Auth
34
40
  end
35
41
 
36
42
  # @rbs ctx: Riffer::Rig::Command::Context
43
+ # @rbs env: Riffer::Rig::Env
44
+ # @rbs auth_path: String
37
45
  # @rbs return: void
38
- def list(ctx)
46
+ def list(ctx, env:, auth_path:)
39
47
  statuses = Riffer::Rig::Providers.identifiers.to_h do |identifier|
40
- [identifier, Riffer::Rig::Credentials.status(identifier)]
48
+ [identifier, Riffer::Rig::Credentials.status(identifier, env:, auth_path:)]
41
49
  end
42
50
  unreadable = statuses.values.grep(String) #: Array[String]
43
51
  return ctx.host.notify(unreadable.first, level: :error) if unreadable.any?
@@ -48,30 +56,30 @@ module Riffer::Rig::Commands::Auth
48
56
 
49
57
  # @rbs ctx: Riffer::Rig::Command::Context
50
58
  # @rbs identifier: String
59
+ # @rbs env: Riffer::Rig::Env
60
+ # @rbs auth_path: String
61
+ # @rbs settings_path: String
51
62
  # @rbs return: void
52
- def rerun(ctx, identifier)
63
+ def rerun(ctx, identifier, env:, auth_path:, settings_path:)
53
64
  return ctx.say(hint) unless Riffer::Providers::Repository.find(identifier)
54
65
 
55
66
  provider = identifier.to_sym
56
- resolution = Riffer::Rig::Credentials.install(provider, host: ctx.host)
57
- return ctx.host.notify(resolution, level: :error) if resolution.is_a?(String)
67
+ values = Riffer::Rig::Credentials.install_values(provider, host: ctx.host, env:, auth_path:, settings_path:)
68
+ return ctx.host.notify(values, level: :error) if values.is_a?(String)
58
69
 
59
- if resolution.missing.any?
60
- missing_fields = Riffer::Rig::ProviderSetup.for(provider).missing_fields(resolution.missing)
61
- ctx.host.notify("#{provider} still has no #{missing_fields}", level: :error)
62
- else
63
- ctx.runtime.merge_credentials(provider, resolution.values)
64
- ctx.say("Updated #{provider} credentials")
65
- end
70
+ ctx.runtime.merge_credentials(provider, values)
71
+ ctx.say("Updated #{provider} credentials")
66
72
  end
67
73
 
68
74
  # @rbs ctx: Riffer::Rig::Command::Context
69
75
  # @rbs identifier: String
76
+ # @rbs auth_path: String
77
+ # @rbs settings_path: String
70
78
  # @rbs return: void
71
- def remove(ctx, identifier)
79
+ def remove(ctx, identifier, auth_path:, settings_path:)
72
80
  return ctx.say(hint) unless Riffer::Providers::Repository.find(identifier)
73
81
 
74
- refusal = Riffer::Rig::Credentials.remove(identifier)
82
+ refusal = Riffer::Rig::Credentials.remove(identifier, auth_path:, settings_path:)
75
83
  return ctx.host.notify(refusal, level: :error) if refusal
76
84
 
77
85
  ctx.say("Removed stored #{identifier} credentials")
@@ -4,20 +4,27 @@ module Riffer::Rig::Commands::Model
4
4
  extend self
5
5
 
6
6
  SAVE = '--save' #: String
7
- SAVED_TO = '~/.riffer/settings.json' #: String
8
7
 
8
+ # @rbs env: Riffer::Rig::Env
9
+ # @rbs auth_path: String
10
+ # @rbs settings_path: String
11
+ # @rbs sdk: singleton(Riffer::Rig::SDK)
9
12
  # @rbs return: Riffer::Rig::Command
10
- def command
13
+ def command(env:, auth_path:, settings_path:, sdk: Riffer::Rig::SDK)
11
14
  Riffer::Rig::Command.new(
12
15
  'model',
13
16
  description: 'Switch the model for this session: /model provider/name, /model --save',
14
17
  extension: :core
15
- ) { |ctx| run(ctx) }
18
+ ) { |ctx| run(ctx, env:, auth_path:, settings_path:, sdk:) }
16
19
  end
17
20
 
18
21
  # @rbs ctx: Riffer::Rig::Command::Context
22
+ # @rbs env: Riffer::Rig::Env
23
+ # @rbs auth_path: String
24
+ # @rbs settings_path: String
25
+ # @rbs sdk: singleton(Riffer::Rig::SDK)
19
26
  # @rbs return: void
20
- def run(ctx)
27
+ def run(ctx, env:, auth_path:, settings_path:, sdk:)
21
28
  words = ctx.args.split
22
29
  save = words.delete(SAVE)
23
30
  model = words.join(' ')
@@ -25,21 +32,21 @@ module Riffer::Rig::Commands::Model
25
32
  if model.empty?
26
33
  return ctx.say("Model: #{ctx.runtime.model}") unless save
27
34
 
28
- refusal = Riffer::Rig::Settings::File.store_model(ctx.runtime.model)
35
+ refusal = Riffer::Rig::Settings::File.store_model(ctx.runtime.model, path: settings_path)
29
36
  return ctx.host.notify(refusal, level: :error) if refusal
30
37
 
31
- return ctx.say("Saved #{ctx.runtime.model} to #{SAVED_TO}")
38
+ return ctx.say("Saved #{ctx.runtime.model} to #{settings_path}")
32
39
  end
33
40
 
34
41
  provider = Riffer::Rig::Settings::ModelOptions.provider_for(model)
35
42
  return ctx.say(hint) unless provider && Riffer::Providers::Repository.find(provider)
36
- return unless ready?(ctx, model, provider)
43
+ return unless ready?(ctx, model, provider, env:, auth_path:, settings_path:, sdk:)
37
44
 
38
45
  ctx.runtime.model = model
39
- refusal = Riffer::Rig::Settings::File.store_model(model) if save
46
+ refusal = Riffer::Rig::Settings::File.store_model(model, path: settings_path) if save
40
47
  ctx.host.notify(refusal, level: :error) if refusal
41
48
  saved = save && !refusal
42
- ctx.say(saved ? "Model switched to #{model} and saved to #{SAVED_TO}" : "Model switched to #{model}")
49
+ ctx.say(saved ? "Model switched to #{model} and saved to #{settings_path}" : "Model switched to #{model}")
43
50
  end
44
51
 
45
52
  private
@@ -54,29 +61,36 @@ module Riffer::Rig::Commands::Model
54
61
  # @rbs ctx: Riffer::Rig::Command::Context
55
62
  # @rbs model: String
56
63
  # @rbs provider: String
64
+ # @rbs env: Riffer::Rig::Env
65
+ # @rbs auth_path: String
66
+ # @rbs settings_path: String
67
+ # @rbs sdk: singleton(Riffer::Rig::SDK)
57
68
  # @rbs return: bool
58
- def ready?(ctx, model, provider)
69
+ def ready?(ctx, model, provider, env:, auth_path:, settings_path:, sdk:)
59
70
  setup = Riffer::Rig::ProviderSetup.registered_setup(provider)
60
71
  return true unless setup
61
72
 
62
- sdk = setup.sdk
63
- if sdk
64
- message = Riffer::Rig::SDK.ensure(sdk[0], sdk[1], host: ctx.host)
73
+ gem = setup.sdk
74
+ if gem
75
+ message = sdk.ensure(gem[0], gem[1], host: ctx.host)
65
76
  if message
66
77
  ctx.host.notify(message, level: :error)
67
78
  return false
68
79
  end
69
80
  end
70
81
 
71
- credentials_ready?(ctx, model, provider, setup)
82
+ credentials_ready?(ctx, model, provider, setup, env:, auth_path:, settings_path:)
72
83
  end
73
84
 
74
85
  # @rbs ctx: Riffer::Rig::Command::Context
75
86
  # @rbs model: String
76
87
  # @rbs provider: String
77
88
  # @rbs setup: Riffer::Rig::ProviderSetup
89
+ # @rbs env: Riffer::Rig::Env
90
+ # @rbs auth_path: String
91
+ # @rbs settings_path: String
78
92
  # @rbs return: bool
79
- def credentials_ready?(ctx, model, provider, setup)
93
+ def credentials_ready?(ctx, model, provider, setup, env:, auth_path:, settings_path:)
80
94
  missing = setup.fields.select(&:required)
81
95
  .map(&:name)
82
96
  .reject { |name| ctx.runtime.credentials.dig(provider.to_sym, name) }
@@ -84,26 +98,15 @@ module Riffer::Rig::Commands::Model
84
98
 
85
99
  empty = {} #: Hash[Symbol, String]
86
100
  stored = ctx.runtime.credentials.fetch(provider.to_sym, empty)
87
- resolution = Riffer::Rig::Credentials.install(provider, host: ctx.host, known: stored)
88
- if resolution.is_a?(String)
89
- ctx.host.notify(resolution, level: :error)
90
- false
91
- elsif resolution.missing.any?
92
- refuse(ctx, model, provider, missing & resolution.missing)
93
- false
94
- else
95
- ctx.runtime.merge_credentials(provider.to_sym, stored.merge(resolution.values))
96
- true
101
+ values = Riffer::Rig::Credentials.install_values(
102
+ provider, host: ctx.host, env:, auth_path:, settings_path:, setup:, known: stored
103
+ )
104
+ if values.is_a?(String)
105
+ ctx.host.notify("Not switching to #{model}: #{values}", level: :error)
106
+ return false
97
107
  end
98
- end
99
108
 
100
- # @rbs ctx: Riffer::Rig::Command::Context
101
- # @rbs model: String
102
- # @rbs provider: String
103
- # @rbs missing: Array[Symbol]
104
- # @rbs return: void
105
- def refuse(ctx, model, provider, missing)
106
- wanted = Riffer::Rig::ProviderSetup.for(provider).missing_fields(missing)
107
- ctx.host.notify("Not switching to #{model}: #{provider} has no #{wanted}", level: :error)
109
+ ctx.runtime.merge_credentials(provider.to_sym, stored.merge(values))
110
+ true
108
111
  end
109
112
  end
@@ -4,43 +4,43 @@ module Riffer::Rig::Commands::Reasoning
4
4
  extend self
5
5
 
6
6
  SAVE = '--save' #: String
7
- SAVED_TO = '~/.riffer/settings.json' #: String
8
- DEFAULT = 'default' #: String
9
7
 
8
+ # @rbs settings_path: String
10
9
  # @rbs return: Riffer::Rig::Command
11
- def command
10
+ def command(settings_path:)
12
11
  Riffer::Rig::Command.new(
13
12
  'reasoning',
14
13
  description: 'Switch the reasoning level for this session: /reasoning high, /reasoning --save',
15
14
  extension: :core
16
- ) { |ctx| run(ctx) }
15
+ ) { |ctx| run(ctx, settings_path:) }
17
16
  end
18
17
 
19
18
  # @rbs ctx: Riffer::Rig::Command::Context
19
+ # @rbs settings_path: String
20
20
  # @rbs return: void
21
- def run(ctx)
21
+ def run(ctx, settings_path:)
22
22
  words = ctx.args.split
23
23
  save = words.delete(SAVE)
24
24
  level = words.join(' ')
25
25
 
26
26
  if level.empty?
27
27
  current = ctx.runtime.reasoning
28
- return ctx.say("Reasoning: #{current || DEFAULT}") unless save
28
+ return ctx.say("Reasoning: #{current || Riffer::Rig::Runtime::DEFAULT_REASONING_LABEL}") unless save
29
29
  return ctx.say('No reasoning level is in effect to save') unless current
30
30
 
31
- refusal = Riffer::Rig::Settings::File.store_reasoning(current)
31
+ refusal = Riffer::Rig::Settings::File.store_reasoning(current, path: settings_path)
32
32
  return ctx.host.notify(refusal, level: :error) if refusal
33
33
 
34
- return ctx.say("Saved #{current} to #{SAVED_TO}")
34
+ return ctx.say("Saved #{current} to #{settings_path}")
35
35
  end
36
36
 
37
37
  rejection = Riffer::Rig::Settings::ModelOptions.reasoning_rejection(level)
38
38
  return ctx.say(rejection) if rejection
39
39
 
40
40
  ctx.runtime.reasoning = level
41
- refusal = Riffer::Rig::Settings::File.store_reasoning(level) if save
41
+ refusal = Riffer::Rig::Settings::File.store_reasoning(level, path: settings_path) if save
42
42
  ctx.host.notify(refusal, level: :error) if refusal
43
43
  saved = save && !refusal
44
- ctx.say(saved ? "Reasoning switched to #{level} and saved to #{SAVED_TO}" : "Reasoning switched to #{level}")
44
+ ctx.say(saved ? "Reasoning switched to #{level} and saved to #{settings_path}" : "Reasoning switched to #{level}")
45
45
  end
46
46
  end
@@ -3,21 +3,21 @@
3
3
  module Riffer::Rig::Commands::Reload
4
4
  extend self
5
5
 
6
- # @rbs loader: Riffer::Rig::Loader
6
+ # @rbs reloader: Riffer::Rig::Loader::Reloader
7
7
  # @rbs return: Riffer::Rig::Command
8
- def command(loader)
8
+ def command(reloader)
9
9
  Riffer::Rig::Command.new(
10
10
  'reload',
11
11
  description: 'Reload the rig.rb files, settings, credentials and trust',
12
12
  extension: :core
13
- ) { |ctx| run(loader, ctx) }
13
+ ) { |ctx| run(reloader, ctx) }
14
14
  end
15
15
 
16
- # @rbs loader: Riffer::Rig::Loader
16
+ # @rbs reloader: Riffer::Rig::Loader::Reloader
17
17
  # @rbs ctx: Riffer::Rig::Command::Context
18
18
  # @rbs return: void
19
- def run(loader, ctx)
20
- reloaded = loader.reload(ctx.runtime, force: true)
19
+ def run(reloader, ctx)
20
+ reloaded = reloader.reload(ctx.runtime, force: true)
21
21
  ctx.say('Reloaded') if reloaded.is_a?(Riffer::Rig::Runtime)
22
22
  end
23
23
  end