riffer-rig 0.5.0 → 0.7.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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -0
  3. data/README.md +77 -7
  4. data/exe/riffer +1 -1
  5. data/lib/riffer/rig/bundled/agents_md.rb +7 -0
  6. data/lib/riffer/rig/bundled/bash.rb +5 -0
  7. data/lib/riffer/rig/bundled/edit.rb +5 -0
  8. data/lib/riffer/rig/bundled/mcp.rb +9 -0
  9. data/lib/riffer/rig/bundled/read.rb +5 -0
  10. data/lib/riffer/rig/bundled/skills.rb +10 -0
  11. data/lib/riffer/rig/bundled/write.rb +5 -0
  12. data/lib/riffer/rig/bundled.rb +7 -0
  13. data/lib/riffer/rig/cli/flags.rb +77 -0
  14. data/lib/riffer/rig/cli.rb +70 -102
  15. data/lib/riffer/rig/command/context.rb +70 -0
  16. data/lib/riffer/rig/command.rb +29 -0
  17. data/lib/riffer/rig/commands/model.rb +54 -0
  18. data/lib/riffer/rig/commands/skill.rb +27 -0
  19. data/lib/riffer/rig/commands.rb +4 -0
  20. data/lib/riffer/rig/credentials/resolution.rb +16 -0
  21. data/lib/riffer/rig/credentials.rb +151 -60
  22. data/lib/riffer/rig/directories.rb +20 -0
  23. data/lib/riffer/rig/env/invalid.rb +13 -0
  24. data/lib/riffer/rig/env.rb +40 -0
  25. data/lib/riffer/rig/events/after_response.rb +25 -0
  26. data/lib/riffer/rig/events/after_tool_call.rb +31 -0
  27. data/lib/riffer/rig/events/before_prompt.rb +25 -0
  28. data/lib/riffer/rig/events/before_request.rb +25 -0
  29. data/lib/riffer/rig/events/before_tool_call.rb +28 -0
  30. data/lib/riffer/rig/events/command_output.rb +28 -0
  31. data/lib/riffer/rig/events/notify.rb +28 -0
  32. data/lib/riffer/rig/events/session_end.rb +25 -0
  33. data/lib/riffer/rig/events/session_start.rb +28 -0
  34. data/lib/riffer/rig/events/skill_activated.rb +25 -0
  35. data/lib/riffer/rig/events/turn_end.rb +33 -0
  36. data/lib/riffer/rig/events.rb +4 -0
  37. data/lib/riffer/rig/extension/failure.rb +16 -0
  38. data/lib/riffer/rig/extension.rb +16 -5
  39. data/lib/riffer/rig/hosts/mirror.rb +59 -0
  40. data/lib/riffer/rig/hosts/null.rb +36 -0
  41. data/lib/riffer/rig/loader.rb +163 -0
  42. data/lib/riffer/rig/mcp/declaration.rb +23 -0
  43. data/lib/riffer/rig/mcp/server.rb +16 -0
  44. data/lib/riffer/rig/mcp.rb +12 -0
  45. data/lib/riffer/rig/prompts/agents_md.rb +32 -0
  46. data/lib/riffer/rig/prompts.rb +4 -0
  47. data/lib/riffer/rig/provider_setup/field.rb +24 -0
  48. data/lib/riffer/rig/provider_setup.rb +84 -0
  49. data/lib/riffer/rig/registrar.rb +134 -7
  50. data/lib/riffer/rig/runtime/blocked.rb +13 -0
  51. data/lib/riffer/rig/runtime/cancel_flag.rb +27 -0
  52. data/lib/riffer/rig/runtime/hooks.rb +78 -0
  53. data/lib/riffer/rig/runtime/request_guardrail.rb +24 -0
  54. data/lib/riffer/rig/runtime/tool_runtime.rb +44 -0
  55. data/lib/riffer/rig/runtime.rb +582 -70
  56. data/lib/riffer/rig/settings/document.rb +28 -12
  57. data/lib/riffer/rig/settings/pricing.rb +16 -6
  58. data/lib/riffer/rig/settings.rb +108 -81
  59. data/lib/riffer/rig/skills/sources.rb +28 -0
  60. data/lib/riffer/rig/skills.rb +12 -0
  61. data/lib/riffer/rig/support/equatable.rb +21 -0
  62. data/lib/riffer/rig/{ui → terminal}/animator.rb +6 -13
  63. data/lib/riffer/rig/{ui → terminal}/banner.rb +17 -21
  64. data/lib/riffer/rig/{ui → terminal}/cursor.rb +4 -6
  65. data/lib/riffer/rig/terminal/host.rb +85 -0
  66. data/lib/riffer/rig/{ui → terminal}/palette.rb +1 -1
  67. data/lib/riffer/rig/{ui → terminal}/renderer.rb +121 -87
  68. data/lib/riffer/rig/{ui → terminal}/smoother.rb +15 -23
  69. data/lib/riffer/rig/{ui → terminal}/theme.rb +12 -14
  70. data/lib/riffer/rig/terminal.rb +207 -0
  71. data/lib/riffer/rig/tools/bash.rb +36 -13
  72. data/lib/riffer/rig/tools/edit.rb +3 -2
  73. data/lib/riffer/rig/tools/read.rb +3 -2
  74. data/lib/riffer/rig/tools/write.rb +3 -2
  75. data/lib/riffer/rig/version.rb +1 -1
  76. data/lib/riffer/rig.rb +10 -15
  77. data/sig/generated/riffer/rig/bundled/agents_md.rbs +5 -0
  78. data/sig/generated/riffer/rig/bundled/bash.rbs +5 -0
  79. data/sig/generated/riffer/rig/bundled/edit.rbs +5 -0
  80. data/sig/generated/riffer/rig/bundled/mcp.rbs +5 -0
  81. data/sig/generated/riffer/rig/bundled/read.rbs +5 -0
  82. data/sig/generated/riffer/rig/bundled/skills.rbs +5 -0
  83. data/sig/generated/riffer/rig/bundled/write.rbs +5 -0
  84. data/sig/generated/riffer/rig/bundled.rbs +5 -0
  85. data/sig/generated/riffer/rig/cli/flags.rbs +40 -0
  86. data/sig/generated/riffer/rig/cli.rbs +30 -27
  87. data/sig/generated/riffer/rig/command/context.rbs +50 -0
  88. data/sig/generated/riffer/rig/command.rbs +23 -0
  89. data/sig/generated/riffer/rig/commands/model.rbs +27 -0
  90. data/sig/generated/riffer/rig/commands/skill.rbs +12 -0
  91. data/sig/generated/riffer/rig/commands.rbs +4 -0
  92. data/sig/generated/riffer/rig/credentials/resolution.rbs +13 -0
  93. data/sig/generated/riffer/rig/credentials.rbs +70 -41
  94. data/sig/generated/riffer/rig/directories.rbs +11 -0
  95. data/sig/generated/riffer/rig/env/invalid.rbs +10 -0
  96. data/sig/generated/riffer/rig/env.rbs +26 -0
  97. data/sig/generated/riffer/rig/events/after_response.rbs +18 -0
  98. data/sig/generated/riffer/rig/events/after_tool_call.rbs +24 -0
  99. data/sig/generated/riffer/rig/events/before_prompt.rbs +18 -0
  100. data/sig/generated/riffer/rig/events/before_request.rbs +18 -0
  101. data/sig/generated/riffer/rig/events/before_tool_call.rbs +21 -0
  102. data/sig/generated/riffer/rig/events/command_output.rbs +21 -0
  103. data/sig/generated/riffer/rig/events/notify.rbs +21 -0
  104. data/sig/generated/riffer/rig/events/session_end.rbs +18 -0
  105. data/sig/generated/riffer/rig/events/session_start.rbs +21 -0
  106. data/sig/generated/riffer/rig/events/skill_activated.rbs +18 -0
  107. data/sig/generated/riffer/rig/events/turn_end.rbs +24 -0
  108. data/sig/generated/riffer/rig/events.rbs +4 -0
  109. data/sig/generated/riffer/rig/extension/failure.rbs +13 -0
  110. data/sig/generated/riffer/rig/extension.rbs +14 -5
  111. data/sig/generated/riffer/rig/hosts/mirror.rbs +41 -0
  112. data/sig/generated/riffer/rig/hosts/null.rbs +26 -0
  113. data/sig/generated/riffer/rig/loader.rbs +77 -0
  114. data/sig/generated/riffer/rig/mcp/declaration.rbs +18 -0
  115. data/sig/generated/riffer/rig/mcp/server.rbs +13 -0
  116. data/sig/generated/riffer/rig/mcp.rbs +8 -0
  117. data/sig/generated/riffer/rig/prompts/agents_md.rbs +19 -0
  118. data/sig/generated/riffer/rig/prompts.rbs +4 -0
  119. data/sig/generated/riffer/rig/provider_setup/field.rbs +22 -0
  120. data/sig/generated/riffer/rig/provider_setup.rbs +30 -0
  121. data/sig/generated/riffer/rig/registrar.rbs +88 -6
  122. data/sig/generated/riffer/rig/runtime/blocked.rbs +10 -0
  123. data/sig/generated/riffer/rig/runtime/cancel_flag.rbs +19 -0
  124. data/sig/generated/riffer/rig/runtime/hooks.rbs +49 -0
  125. data/sig/generated/riffer/rig/runtime/request_guardrail.rbs +16 -0
  126. data/sig/generated/riffer/rig/runtime/tool_runtime.rbs +23 -0
  127. data/sig/generated/riffer/rig/runtime.rbs +262 -40
  128. data/sig/generated/riffer/rig/settings/document.rbs +13 -9
  129. data/sig/generated/riffer/rig/settings/pricing.rbs +7 -3
  130. data/sig/generated/riffer/rig/settings.rbs +56 -64
  131. data/sig/generated/riffer/rig/skills/sources.rbs +18 -0
  132. data/sig/generated/riffer/rig/skills.rbs +7 -0
  133. data/sig/generated/riffer/rig/support/equatable.rbs +15 -0
  134. data/sig/generated/riffer/rig/{ui → terminal}/animator.rbs +5 -13
  135. data/sig/generated/riffer/rig/terminal/banner.rbs +39 -0
  136. data/sig/generated/riffer/rig/terminal/cursor.rbs +31 -0
  137. data/sig/generated/riffer/rig/terminal/host.rbs +50 -0
  138. data/sig/generated/riffer/rig/{ui → terminal}/palette.rbs +2 -2
  139. data/sig/generated/riffer/rig/terminal/renderer.rbs +126 -0
  140. data/sig/generated/riffer/rig/terminal/smoother.rbs +59 -0
  141. data/sig/generated/riffer/rig/{ui → terminal}/theme.rbs +5 -7
  142. data/sig/generated/riffer/rig/terminal.rbs +85 -0
  143. data/sig/generated/riffer/rig/tools/bash.rbs +14 -1
  144. data/sig/generated/riffer/rig.rbs +4 -7
  145. data/sig/manual/riffer/rig/cli.rbs +2 -4
  146. data/sig/manual/riffer/rig/commands/model.rbs +6 -0
  147. data/sig/manual/riffer/rig/commands/skill.rbs +6 -0
  148. data/sig/manual/riffer/rig/credentials.rbs +2 -4
  149. data/sig/manual/riffer/rig/directories.rbs +6 -0
  150. data/sig/manual/riffer/rig/events.rbs +6 -0
  151. data/sig/manual/riffer/rig/hosts.rbs +12 -0
  152. data/sig/manual/riffer/rig/mcp.rbs +11 -0
  153. data/sig/manual/riffer/rig/prompts/agents_md.rbs +6 -0
  154. data/sig/manual/riffer/rig/settings.rbs +2 -4
  155. data/sig/manual/riffer/rig/skills.rbs +6 -0
  156. data/sig/manual/riffer/rig/support.rbs +8 -0
  157. data/sig/manual/riffer/rig/terminal/banner.rbs +6 -0
  158. metadata +173 -35
  159. data/lib/riffer/rig/coding_agent.rb +0 -53
  160. data/lib/riffer/rig/host.rb +0 -52
  161. data/lib/riffer/rig/repl.rb +0 -189
  162. data/lib/riffer/rig/token_tally.rb +0 -74
  163. data/sig/generated/riffer/rig/coding_agent.rbs +0 -9
  164. data/sig/generated/riffer/rig/host.rbs +0 -42
  165. data/sig/generated/riffer/rig/repl.rbs +0 -77
  166. data/sig/generated/riffer/rig/token_tally.rbs +0 -61
  167. data/sig/generated/riffer/rig/ui/banner.rbs +0 -42
  168. data/sig/generated/riffer/rig/ui/cursor.rbs +0 -33
  169. data/sig/generated/riffer/rig/ui/renderer.rbs +0 -118
  170. data/sig/generated/riffer/rig/ui/smoother.rbs +0 -72
  171. data/sig/manual/riffer/rig/ui/banner.rbs +0 -8
  172. data/sig/manual/riffer/rig/ui.rbs +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a1580a7e4f2ed5e6d1f18714ef469b7d4623bec94552510a836a5f5e5664504
4
- data.tar.gz: 7e871b6b1f8c9de6e3788979103f7afe65beea7a9c5fa7813e17b29e4e8eb528
3
+ metadata.gz: 52fc3da32f169222f7f3bac50e5ecb6d878fe46cf1955b00b218f26af043b2e9
4
+ data.tar.gz: a6a2c8c3c36fb9774550f2188d7cdfb529b5abb53805bd58eb31863f00c335c8
5
5
  SHA512:
6
- metadata.gz: db021260a12a48c02bb6ff92278819416bcc267bd78015f01f80bd3a9ed64c585e37d17573a3ba47ba28d99bd0ef586665a465782d36a6a85231c08ad4a4fd51
7
- data.tar.gz: 5ea0ed0a06d1a318267bd3d63b693f974ff8ca814e26be0eb5837cdac09527bea33a75357f7a1a11b419854ed72810a5d571c8508010e8bd62f327eb922f04eb
6
+ metadata.gz: 4fb9601edf932b84ee1c91b226d22814b8d80633e891ef3a3dc2436d59fc2ea617c53772ec2b9042362e937590a525f000dc6e85bb748b8bdd1ba32e017bc551
7
+ data.tar.gz: c51101a3560a43400edc5608e37f4615383cfdee22f8c2975b14e7d95a52bce08c063db1d2de108189e525ee1bf3f13377e4c3b22c9bef3b0a147298ac25f228
data/CHANGELOG.md CHANGED
@@ -1,5 +1,63 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.0](https://github.com/bottrall/riffer-rig/compare/v0.6.0...v0.7.0) (2026-09-30)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * Riffer::Rig::CodingAgent, REPL, TokenTally and the UI namespace are removed, and the old CLI is replaced by the dispatcher. Embed a Runtime through Riffer::Rig::Loader.runtime instead of CodingAgent; the terminal UI classes now live under Riffer::Rig::Terminal. The bash tool now errors when its context has no :cwd.
9
+ * DEFAULT_MODEL is gone; with no model the Loader asks the host to onboard and the null host's refusal is a configuration error. The old Settings module API changes shape.
10
+ * Riffer::Rig::Hosts::Base and Riffer::Rig::Events::Event are removed. A host is any object implementing the five host methods (Riffer::Rig::Hosts::_Host); drop the superclass. A custom event drops the superclass and super(), and includes Riffer::Rig::Events::Value for equality.
11
+
12
+ ### Features
13
+
14
+ * /model switches the model for the current Runtime ([#193](https://github.com/bottrall/riffer-rig/issues/193)) ([f888972](https://github.com/bottrall/riffer-rig/commit/f8889722f62499f97ee34158826e9d00dd3d92ef))
15
+ * bundle Agent Skills with /skill: commands over the skills seam ([#201](https://github.com/bottrall/riffer-rig/issues/201)) ([e14844d](https://github.com/bottrall/riffer-rig/commit/e14844d9c7c22dd85b3d4158892ec53bae736c6b))
16
+ * bundle AGENTS.md as the agents_md prompt section ([#198](https://github.com/bottrall/riffer-rig/issues/198)) ([e2e9141](https://github.com/bottrall/riffer-rig/commit/e2e914195f7c15c75ee2ae6bb55d772d754a2f37))
17
+ * bundle read, write, edit and bash as extensions ([#195](https://github.com/bottrall/riffer-rig/issues/195)) ([d5c9ffe](https://github.com/bottrall/riffer-rig/commit/d5c9ffe692abf5e5cab16af0686f96b040a6cbaa))
18
+ * bundle the MCP client over the mcp seam ([#202](https://github.com/bottrall/riffer-rig/issues/202)) ([c2fc42f](https://github.com/bottrall/riffer-rig/commit/c2fc42ff873af8205f3a8042069a3dd5f27420e6))
19
+ * isolate extension load errors and check requires ([#192](https://github.com/bottrall/riffer-rig/issues/192)) ([0aac3bf](https://github.com/bottrall/riffer-rig/commit/0aac3bf7d3b6ada84a4f38a40abc181f7d3f57c7)), closes [#114](https://github.com/bottrall/riffer-rig/issues/114)
20
+ * lifecycle, vetoable and observe hooks plus the stream passthrough ([#194](https://github.com/bottrall/riffer-rig/issues/194)) ([206d349](https://github.com/bottrall/riffer-rig/commit/206d349a4967081961372d1692b0946663f32a10))
21
+ * Loader builds a Runtime from settings, credentials and the bundle ([#205](https://github.com/bottrall/riffer-rig/issues/205)) ([01170fe](https://github.com/bottrall/riffer-rig/commit/01170fee282deae87aba1b8797cfe5cedc892deb)), closes [#123](https://github.com/bottrall/riffer-rig/issues/123)
22
+ * namespaced extension settings ([#196](https://github.com/bottrall/riffer-rig/issues/196)) ([dc16a84](https://github.com/bottrall/riffer-rig/commit/dc16a8424ea00ea0b39323db97309c9c94224443)), closes [#117](https://github.com/bottrall/riffer-rig/issues/117)
23
+ * rebuild the registrar in place ([#199](https://github.com/bottrall/riffer-rig/issues/199)) ([f4b742b](https://github.com/bottrall/riffer-rig/commit/f4b742b3f45a28f3c69d75a355c3a2880938de1b))
24
+ * runtime commands with run_command and the command context ([#190](https://github.com/bottrall/riffer-rig/issues/190)) ([159d007](https://github.com/bottrall/riffer-rig/commit/159d0077b34773fadb2cc6f5f66622b12e4b7a0e)), closes [#111](https://github.com/bottrall/riffer-rig/issues/111)
25
+ * snapshot and restore a Runtime ([#200](https://github.com/bottrall/riffer-rig/issues/200)) ([aaad579](https://github.com/bottrall/riffer-rig/commit/aaad5797e5e626b6f560b96a5f4cf1bf5bc379a3))
26
+ * Terminal drives a Loader-built Runtime ([#206](https://github.com/bottrall/riffer-rig/issues/206)) ([f805db6](https://github.com/bottrall/riffer-rig/commit/f805db625e69f90c2dd343bbb7ca016aba7cecf0))
27
+ * token tally and pricing behind the Runtime ([#189](https://github.com/bottrall/riffer-rig/issues/189)) ([1ccde0d](https://github.com/bottrall/riffer-rig/commit/1ccde0d1670feaa2a9b9c957bbe79fa80ca47d4f))
28
+
29
+
30
+ ### Bug Fixes
31
+
32
+ * run steep check without attaching to a language server ([#197](https://github.com/bottrall/riffer-rig/issues/197)) ([4f0b92e](https://github.com/bottrall/riffer-rig/commit/4f0b92eeafcb4e4fb6d054c29d707fcfb39766d5))
33
+
34
+
35
+ ### Code Refactoring
36
+
37
+ * interfaces over inheritance, POROs over hashes, new cops ([#204](https://github.com/bottrall/riffer-rig/issues/204)) ([7fda3e3](https://github.com/bottrall/riffer-rig/commit/7fda3e3ec79ea97479008127f0c153ce84b888d5))
38
+
39
+ ## [0.6.0](https://github.com/bottrall/riffer-rig/compare/v0.5.0...v0.6.0) (2026-09-27)
40
+
41
+
42
+ ### ⚠ BREAKING CHANGES
43
+
44
+ * the flat `{"anthropic": "sk-..."}` auth.json shape is no longer read, with no migration. Stored keys must be re-entered or rewritten as `{"anthropic": {"type": "api_key", "api_key": "sk-..."}}`.
45
+ * Riffer::Rig::Host is removed. A host subclasses Riffer::Rig::Hosts::Base and implements all five methods; the null host is Riffer::Rig::Hosts::Null.
46
+
47
+ ### Features
48
+
49
+ * add the rig event vocabulary and close ([#179](https://github.com/bottrall/riffer-rig/issues/179)) ([35bfa6e](https://github.com/bottrall/riffer-rig/commit/35bfa6e5dc1dc9825fd03843a4e69ec24e675b66))
50
+ * cancel a running turn from any thread ([#186](https://github.com/bottrall/riffer-rig/issues/186)) ([ec68188](https://github.com/bottrall/riffer-rig/commit/ec68188cc7dbcaf0cd56dd41d83cda216661fbe7))
51
+ * **deps:** upgrade riffer to 0.49.0 ([#188](https://github.com/bottrall/riffer-rig/issues/188)) ([821413f](https://github.com/bottrall/riffer-rig/commit/821413f5af65bfcb159ddad532deb3c15779112d))
52
+ * named prompt sections rendered every turn ([#184](https://github.com/bottrall/riffer-rig/issues/184)) ([d190a07](https://github.com/bottrall/riffer-rig/commit/d190a07dfa99764b82bba00745a07e7738c3532a))
53
+ * provider setup table and typed auth.json credentials ([#185](https://github.com/bottrall/riffer-rig/issues/185)) ([0a30c35](https://github.com/bottrall/riffer-rig/commit/0a30c357793716acfabcd1ddd41ea9f98c8a6a8f))
54
+ * store credentials on the Runtime ([#181](https://github.com/bottrall/riffer-rig/issues/181)) ([474a515](https://github.com/bottrall/riffer-rig/commit/474a51586011e413482f0275a7abd3a542e91914))
55
+
56
+
57
+ ### Code Refactoring
58
+
59
+ * make the host contract the abstract Hosts::Base ([#183](https://github.com/bottrall/riffer-rig/issues/183)) ([3fd4f14](https://github.com/bottrall/riffer-rig/commit/3fd4f14a193d546ae06e00776f209a52c0593854))
60
+
3
61
  ## [0.5.0](https://github.com/bottrall/riffer-rig/compare/v0.4.0...v0.5.0) (2026-09-19)
4
62
 
5
63
 
data/README.md CHANGED
@@ -26,19 +26,89 @@ Run the agent from any project directory:
26
26
  riffer
27
27
  ```
28
28
 
29
+ The first run asks which model to use, as `provider/name` (for example `anthropic/claude-sonnet-4-6`), and saves the answer as `model` in `~/.riffer/settings.json`; it then asks for any missing credential ([Authentication](#authentication)). After that, `riffer` opens straight into the prompt. [Getting started](docs/GETTING_STARTED.md) walks through a first session.
30
+
31
+ The model is the first of these that is set: `--model`, `RIFFER_MODEL`, `model` in `<cwd>/.riffer/settings.json`, then in `~/.riffer/settings.json` ([Configuration](docs/CONFIGURATION.md#model)).
32
+
33
+ ### Flags
34
+
35
+ | Flag | Effect |
36
+ | ----------------------- | -------------------------------------------------------------------------- |
37
+ | `--model provider/name` | the model for this session, over `RIFFER_MODEL` and the settings |
38
+ | `--no-extensions` | skips extension files; accepted now, with nothing to skip until they load |
39
+ | `--no-skills` | leaves out [Agent Skills](docs/SKILLS.md) |
40
+ | `--no-agents-md` | leaves out the [AGENTS.md](docs/INSTRUCTIONS.md#agentsmd) instructions |
41
+ | `--tools read,bash` | only these tools, by identifier ([Tools](docs/TOOLS.md)) |
42
+ | `--max-steps N` | stops a turn after `N` model calls |
43
+ | `-h`, `--help` | prints the usage |
44
+
45
+ `riffer -p` (one prompt, no terminal) and `riffer acp` (an editor's agent over stdio) are reserved: for now each prints the usage and exits with status 2.
46
+
47
+ ### In the session
48
+
49
+ Type a prompt and press Enter; the reply streams in, with each tool call and the first line of its result, and every turn ends with a token line (`↑in · ↓out · session N tok`, plus the session cost when the model is [priced](docs/CONFIGURATION.md#models)).
50
+
51
+ - Ctrl-C during a turn cancels it and returns to the prompt. Ctrl-C at the prompt asks for a second one, which exits.
52
+ - `/exit` or `/quit` ends the session, as does Ctrl-D.
53
+ - `/model provider/name` switches the model ([below](#switching-the-model)), and `/skill:<name> [text]` runs a skill ([Skills](docs/SKILLS.md)).
54
+ - Any other `/name args` runs the Runtime command of that name, such as one an extension registers ([Extensions](docs/EXTENSIONS.md)); an unknown name is reported and nothing is sent to the model.
55
+
29
56
  ### Authentication
30
57
 
31
- `riffer-rig` talks to Anthropic. Provide your API key in either of two ways:
58
+ `riffer-rig` talks to the provider named by the model's prefix — `anthropic/claude-sonnet-4-6` means Anthropic. Provide that provider's credentials in either of two ways:
59
+
60
+ - Set the provider's environment variables, or
61
+ - Run `riffer` and paste each missing value when prompted on first launch. Secrets are saved to `~/.riffer/auth.json` (file permissions `600`); plain values such as an endpoint or region are saved to the `providers` block in `~/.riffer/settings.json`.
62
+
63
+ Each value resolves on its own: environment variable first, then the stored value, then a provider-specific fallback (the AWS shared config for the Bedrock region), then the prompt. Optional values are never prompted for.
64
+
65
+ | Provider | Environment variables | Guide |
66
+ | ---------------- | ------------------------------------------------------------------------- | -------------------------------------------------- |
67
+ | `anthropic` | `ANTHROPIC_API_KEY` | [Anthropic](docs/PROVIDERS.md#anthropic) |
68
+ | `openai` | `OPENAI_API_KEY`; optional `OPENAI_BASE_URL` | [OpenAI](docs/PROVIDERS.md#openai) |
69
+ | `gemini` | `GEMINI_API_KEY` | [Gemini](docs/PROVIDERS.md#gemini) |
70
+ | `openrouter` | `OPENROUTER_API_KEY` | [OpenRouter](docs/PROVIDERS.md#openrouter) |
71
+ | `azure_openai` | `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY` | [Azure OpenAI](docs/PROVIDERS.md#azure-openai) |
72
+ | `amazon_bedrock` | `AWS_REGION` or `AWS_DEFAULT_REGION`; optional `AWS_BEARER_TOKEN_BEDROCK` | [Amazon Bedrock](docs/PROVIDERS.md#amazon-bedrock) |
73
+
74
+ `auth.json` holds one typed entry per provider, and a stored secret may be a literal, a `$ENV_VAR` reference or a `!shell command` whose stdout is the secret:
75
+
76
+ ```json
77
+ {
78
+ "anthropic": { "type": "api_key", "api_key": "sk-ant-…" },
79
+ "openai": { "type": "api_key", "api_key": "!security find-generic-password -s openai -w" }
80
+ }
81
+ ```
82
+
83
+ The flat `{"anthropic": "sk-…"}` shape earlier versions wrote is no longer read; paste the key again when prompted. [Providers](docs/PROVIDERS.md) has the full format.
32
84
 
33
- - Set the `ANTHROPIC_API_KEY` environment variable, or
34
- - Run `riffer` and paste your key when prompted on first launch. It is saved to `~/.riffer/auth.json` (file permissions `600`) for subsequent runs.
85
+ ### Switching the model
35
86
 
36
- Create a key at https://console.anthropic.com/settings/keys.
87
+ `/model provider/name` switches the model for the current session only, keeping the conversation so far — `/model openai/gpt-5`, say. The provider prefix is required: a bare name such as `/model sonnet` is rejected with the list of providers. `/model` alone shows the model in use. The switch is refused when the session has no credentials for the new provider, and it never changes `settings.json`; set `model` there to change the model new sessions start with ([Configuration](docs/CONFIGURATION.md#model)).
37
88
 
38
89
  ### Configuration
39
90
 
40
- - `RIFFER_MODEL` — override the default model.
41
- - `AGENTS.md` — if present, an `AGENTS.md` in the current working directory and/or a global `~/.riffer/AGENTS.md` is loaded as additional context.
91
+ - `AGENTS.md` — `~/.riffer/AGENTS.md` and an `AGENTS.md` in the current working directory or any directory above it, whichever exist, are re-read every turn as instructions that take precedence over the default norms ([Instructions](docs/INSTRUCTIONS.md#agentsmd)).
92
+ - Skills — Agent Skills in `.agents/skills/` from the current working directory up to the repository root, and in `~/.agents/skills/`, are offered to the model, and each can be run with `/skill:<name>` ([Skills](docs/SKILLS.md)).
93
+ - `RIFFER_MODEL` — the model for this run as `provider/name`, winning over the `model` setting. A bare name such as `sonnet` is rejected with the list of providers.
94
+ - `~/.riffer/settings.json` — optional user settings, and `<cwd>/.riffer/settings.json` for one project, merged key by key with the project winning. Every key is optional:
95
+
96
+ ```json
97
+ {
98
+ "model": "anthropic/claude-sonnet-4-6",
99
+ "reasoning": "low",
100
+ "models": {
101
+ "anthropic/claude-sonnet-4-6": {
102
+ "input": 3.0,
103
+ "output": 15.0,
104
+ "cache_write": 3.75,
105
+ "cache_read": 0.3
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ `model` is the model new sessions start with; a host built on the [Loader](docs/EMBEDDING.md#building-a-runtime-with-the-loader) asks for one when none is set and writes the answer here. `extensions.disabled` (for example `["mcp"]`) leaves bundled extensions out. `models` prices each model in USD per million tokens; [Configuration](docs/CONFIGURATION.md) has every key. `reasoning` is translated to the provider's own parameter — Anthropic accepts `low`, `medium`, `high`, `xhigh` and `max`; OpenAI and OpenRouter accept `low`, `medium`, `high` and `xhigh`. Omitting it, or supplying an unrecognised value, leaves the model's default reasoning behaviour unchanged.
42
112
 
43
113
  ## Development
44
114
 
@@ -62,7 +132,7 @@ PR titles are [conventional commits](https://www.conventionalcommits.org/) and a
62
132
 
63
133
  [release-please](https://github.com/googleapis/release-please) keeps a release PR open that bumps `lib/riffer/rig/version.rb` and writes `CHANGELOG.md`. Merging that PR tags `vX.Y.Z`, creates the GitHub Release and publishes the gem to RubyGems.org through Trusted Publishing; nothing is pushed by hand.
64
134
 
65
- riffer is pinned to one minor (`~> 0.45.0`) because its 0.x minors may break. When Dependabot opens the riffer bump, retitle it `feat(deps):` or `fix(deps):` before merging so it releases and appears in the changelog.
135
+ riffer is pinned to one minor (`~> 0.49.0`) because its 0.x minors may break. When Dependabot opens the riffer bump, retitle it `feat(deps):` or `fix(deps):` before merging so it releases and appears in the changelog.
66
136
 
67
137
  ## Contributing
68
138
 
data/exe/riffer CHANGED
@@ -5,4 +5,4 @@ require "bundler/setup" if File.exist?(File.expand_path("../Gemfile", __dir__))
5
5
 
6
6
  require_relative "../lib/riffer/rig"
7
7
 
8
- exit(Riffer::Rig::CLI.start)
8
+ exit(Riffer::Rig::CLI.start(ARGV))
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Riffer::Rig::Bundled
4
+ AgentsMd = Riffer::Rig::Extension.new('agents_md') do |rig|
5
+ rig.prompt(:agents_md) { |runtime| Riffer::Rig::Prompts::AgentsMd.section(runtime.cwd) }
6
+ end #: Riffer::Rig::Extension
7
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Riffer::Rig::Bundled
4
+ Bash = Riffer::Rig::Extension.new('bash') { |rig| rig.tool Riffer::Rig::Tools::Bash } #: Riffer::Rig::Extension
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Riffer::Rig::Bundled
4
+ Edit = Riffer::Rig::Extension.new('edit') { |rig| rig.tool Riffer::Rig::Tools::Edit } #: Riffer::Rig::Extension
5
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Riffer::Rig::Bundled
4
+ Mcp = Riffer::Rig::Extension.new('mcp') do |rig|
5
+ rig.settings[:servers].to_h.each do |name, server|
6
+ rig.mcp(name.to_s, url: server.fetch(:url), headers: server[:headers].to_h.transform_keys(&:to_s))
7
+ end
8
+ end #: Riffer::Rig::Extension
9
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Riffer::Rig::Bundled
4
+ Read = Riffer::Rig::Extension.new('read') { |rig| rig.tool Riffer::Rig::Tools::Read } #: Riffer::Rig::Extension
5
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Riffer::Rig::Bundled
4
+ Skills = Riffer::Rig::Extension.new('skills') do |rig|
5
+ rig.skills do |runtime|
6
+ Riffer::Skills::FilesystemBackend.new(*Riffer::Rig::Skills.directories(runtime.cwd))
7
+ end
8
+ rig.prompt(:skills) { nil }
9
+ end #: Riffer::Rig::Extension
10
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Riffer::Rig::Bundled
4
+ Write = Riffer::Rig::Extension.new('write') { |rig| rig.tool Riffer::Rig::Tools::Write } #: Riffer::Rig::Extension
5
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Riffer::Rig::Bundled
4
+ BY_NAME = {
5
+ read: Read, write: Write, edit: Edit, bash: Bash, agents_md: AgentsMd, skills: Skills, mcp: Mcp
6
+ }.freeze #: Hash[Symbol, Riffer::Rig::Extension]
7
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ class Riffer::Rig::CLI::Flags
6
+ BANNER = <<~TEXT.chomp #: String
7
+ Usage: riffer [options]
8
+ riffer -p [prompt] (not available yet)
9
+ riffer acp (not available yet)
10
+ TEXT
11
+
12
+ # @dynamic model, extensions, skills, agents_md, tools, max_steps, help
13
+ attr_reader :model #: String?
14
+ attr_reader :extensions, :skills, :agents_md, :help #: bool
15
+ attr_reader :tools #: Array[String]?
16
+ attr_reader :max_steps #: Integer?
17
+
18
+ # @rbs argv: Array[String]
19
+ # @rbs return: Riffer::Rig::CLI::Flags | String
20
+ def self.parse(argv)
21
+ values = {} #: Hash[Symbol, untyped]
22
+ rest = parser.parse(argv, into: values)
23
+ return "unexpected argument: #{rest.join(' ')}" unless rest.empty?
24
+
25
+ new(
26
+ model: values[:model],
27
+ extensions: !values.key?(:'no-extensions'),
28
+ skills: !values.key?(:'no-skills'),
29
+ agents_md: !values.key?(:'no-agents-md'),
30
+ tools: values[:tools],
31
+ max_steps: values[:'max-steps'],
32
+ help: values.key?(:help)
33
+ )
34
+ rescue OptionParser::ParseError => e
35
+ e.message
36
+ end
37
+
38
+ # @rbs return: String
39
+ def self.usage
40
+ parser.help
41
+ end
42
+
43
+ # @rbs return: OptionParser
44
+ def self.parser
45
+ OptionParser.new do |parser|
46
+ parser.banner = BANNER
47
+ parser.separator('')
48
+ parser.on('--model PROVIDER/NAME', String, 'Model for this session, over RIFFER_MODEL and settings')
49
+ parser.on('--no-extensions', 'Skip extension files and autoload; the bundle still loads')
50
+ parser.on('--no-skills', 'Leave out Agent Skills')
51
+ parser.on('--no-agents-md', 'Leave out AGENTS.md instructions')
52
+ parser.on('--tools NAME,NAME', Array, 'Only these tools, by identifier')
53
+ parser.on('--max-steps N', Integer, 'Stop a turn after N model calls')
54
+ parser.on('-h', '--help', 'Show this help')
55
+ end
56
+ end
57
+ private_class_method :parser
58
+
59
+ # @rbs model: String?
60
+ # @rbs extensions: bool
61
+ # @rbs skills: bool
62
+ # @rbs agents_md: bool
63
+ # @rbs tools: Array[String]?
64
+ # @rbs max_steps: Integer?
65
+ # @rbs help: bool
66
+ # @rbs return: void
67
+ def initialize(model: nil, extensions: true, skills: true, agents_md: true, tools: nil, max_steps: nil, help: false)
68
+ @model = model
69
+ @extensions = extensions
70
+ @skills = skills
71
+ @agents_md = agents_md
72
+ @tools = tools&.freeze
73
+ @max_steps = max_steps
74
+ @help = help
75
+ freeze
76
+ end
77
+ end
@@ -1,124 +1,92 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'io/console'
4
-
5
3
  module Riffer::Rig::CLI
6
4
  extend self
7
5
 
8
- PROVIDER_URLS = {
9
- 'anthropic' => 'https://console.anthropic.com/settings/keys',
10
- 'openai' => 'https://platform.openai.com/api-keys',
11
- 'gemini' => 'https://aistudio.google.com/app/apikey',
12
- 'openrouter' => 'https://openrouter.ai/keys'
13
- }.freeze #: Hash[String, String]
14
-
15
- # @rbs output: IO
16
- # @rbs input: IO
17
- # @rbs return: Integer
18
- def start(output: $stdout, input: $stdin)
19
- theme = Riffer::Rig::UI::Theme.for(output)
20
-
21
- model = Riffer::Rig::Settings.model
22
- provider = Riffer::Rig::Settings.provider_for(model)
23
-
24
- api_key = (provider && Riffer::Rig::Credentials.api_key_for(provider)) || onboard(provider, theme, output:, input:)
25
- return 1 if api_key.nil?
26
-
27
- configure_provider(provider, api_key)
6
+ PRINT_FLAGS = %w[-p --print].freeze #: Array[String]
28
7
 
29
- agent = Riffer::Rig::CodingAgent.new
30
- animator = Riffer::Rig::UI::Animator.new(io: output, theme:)
8
+ ACP_COMMAND = 'acp'
31
9
 
32
- reveal_banner(theme, animator, model)
10
+ USAGE_ERROR = 2
33
11
 
34
- tally = Riffer::Rig::TokenTally.new(pricing: Riffer::Rig::Settings.pricing_for(model))
35
- smoother = Riffer::Rig::UI::Smoother.new(io: output, theme:)
36
- renderer = Riffer::Rig::UI::Renderer.new(io: output, theme:, tally:, smoother:)
37
- Riffer::Rig::REPL.new(agent:, renderer:, animator:, theme:, smoother:, input:, output:).run
38
- 0
12
+ # @rbs argv: Array[String]
13
+ # @rbs input: IO
14
+ # @rbs output: IO
15
+ # @rbs error: IO
16
+ # @rbs env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
17
+ # @rbs cwd: String
18
+ # @rbs home: String
19
+ # @rbs return: Integer
20
+ def start(
21
+ argv = ARGV,
22
+ input: $stdin,
23
+ output: $stdout,
24
+ error: $stderr,
25
+ env: Riffer::Rig::Env.load,
26
+ cwd: Dir.pwd,
27
+ home: Dir.home
28
+ )
29
+ return unavailable("riffer #{ACP_COMMAND}", error) if argv.first == ACP_COMMAND
30
+ return unavailable('riffer -p', error) if argv.intersect?(PRINT_FLAGS)
31
+
32
+ flags = Flags.parse(argv)
33
+ return refuse(flags, error) if flags.is_a?(String)
34
+ return help(output) if flags.help
35
+
36
+ terminal(flags, input:, output:, env:, cwd:, home:)
39
37
  end
40
38
 
41
39
  private
42
40
 
43
- # @rbs provider: String?
44
- # @rbs api_key: String
45
- # @rbs return: void
46
- def configure_provider(provider, api_key)
47
- case provider
48
- when 'anthropic' then Riffer.configure { |c| c.anthropic.api_key = api_key }
49
- when 'openai' then Riffer.configure { |c| c.openai.api_key = api_key }
50
- when 'gemini' then Riffer.configure { |c| c.gemini.api_key = api_key }
51
- when 'openrouter' then Riffer.configure { |c| c.openrouter.api_key = api_key }
52
- end
53
- end
54
-
55
- # @rbs provider: String?
56
- # @rbs theme: Riffer::Rig::UI::Theme
57
- # @rbs output: IO
41
+ # @rbs flags: Riffer::Rig::CLI::Flags
58
42
  # @rbs input: IO
59
- # @rbs return: String?
60
- def onboard(provider, theme, output:, input:)
61
- url = (provider && PROVIDER_URLS[provider]) || 'your provider'
62
- name = provider ? provider.capitalize : 'provider'
63
-
64
- output.puts(theme.cyan('♪ welcome to riffer-rig ♪'))
65
- output.puts(theme.grey("No #{name} API key found. Create one at #{url}"))
66
- output.print("#{theme.pink('›')} Paste your #{name} API key #{theme.grey('(hidden)')}: ")
67
-
68
- key = read_secret(input).to_s.strip
69
- output.puts
70
-
71
- if key.empty?
72
- output.puts(theme.grey("No key entered. Set #{env_var_for(provider)} or re-run riffer to try again."))
73
- return nil
43
+ # @rbs output: IO
44
+ # @rbs env: Riffer::Rig::Env | Riffer::Rig::Env::Invalid
45
+ # @rbs cwd: String
46
+ # @rbs home: String
47
+ # @rbs return: Integer
48
+ def terminal(flags, input:, output:, env:, cwd:, home:)
49
+ Riffer::Rig::Terminal.for(
50
+ input: input,
51
+ output: output,
52
+ version: Riffer::Rig::VERSION,
53
+ no_color: env.is_a?(Riffer::Rig::Env) && env.no_color
54
+ ).run do |host|
55
+ Riffer::Rig::Loader.runtime(
56
+ cwd: cwd,
57
+ host: host,
58
+ env: env,
59
+ home: home,
60
+ model: flags.model,
61
+ extensions: flags.extensions,
62
+ skills: flags.skills,
63
+ agents_md: flags.agents_md,
64
+ tools: flags.tools,
65
+ max_steps: flags.max_steps
66
+ )
74
67
  end
75
-
76
- Riffer::Rig::Credentials.save_api_key(provider, key) if provider
77
- output.puts(theme.grey("Saved to #{Riffer::Rig::Credentials::PATH} (permissions 600)."))
78
- key
79
- end
80
-
81
- # @rbs provider: String?
82
- # @rbs return: String
83
- def env_var_for(provider)
84
- (provider && Riffer::Rig::Credentials::PROVIDER_ENV_VARS[provider]) || 'the appropriate API key env var'
85
68
  end
86
69
 
87
- # @rbs input: IO
88
- # @rbs return: String?
89
- def read_secret(input)
90
- return input.noecho(&:gets) if input.respond_to?(:noecho) && input.tty?
91
-
92
- input.gets
70
+ # @rbs name: String
71
+ # @rbs error: IO
72
+ # @rbs return: Integer
73
+ def unavailable(name, error)
74
+ refuse("#{name} is not available yet", error)
93
75
  end
94
76
 
95
- # @rbs theme: Riffer::Rig::UI::Theme
96
- # @rbs animator: Riffer::Rig::UI::Animator
97
- # @rbs model: String
98
- # @rbs return: void
99
- def reveal_banner(theme, animator, model)
100
- loaded = [Riffer::Rig::CodingAgent::GLOBAL_AGENTS_FILE, File.join(Dir.pwd, 'AGENTS.md')].select { |path| File.file?(path) }
101
- context = loaded.empty? ? 'none' : loaded.join(', ')
102
-
103
- animator.reveal(
104
- [Riffer::Rig::UI::Banner.lines(
105
- theme,
106
- model: model,
107
- cwd: Dir.pwd,
108
- context: context,
109
- skills: count_skills,
110
- version: Riffer::Rig::VERSION
111
- )]
112
- )
77
+ # @rbs message: String
78
+ # @rbs error: IO
79
+ # @rbs return: Integer
80
+ def refuse(message, error)
81
+ error.puts("riffer: #{message}")
82
+ error.puts(Flags.usage)
83
+ USAGE_ERROR
113
84
  end
114
85
 
115
- # @rbs return: String
116
- def count_skills
117
- dirs = [Riffer::Rig::CodingAgent::GLOBAL_SKILLS_DIR, Riffer::Rig::CodingAgent::PROJECT_SKILLS_DIR.call]
118
- backend = Riffer::Skills::FilesystemBackend.new(*dirs)
119
- count = backend.list_skills.length
120
- count.zero? ? 'none' : count.to_s
121
- rescue StandardError
122
- 'none'
86
+ # @rbs output: IO
87
+ # @rbs return: Integer
88
+ def help(output)
89
+ output.puts(Flags.usage)
90
+ 0
123
91
  end
124
92
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Riffer::Rig::Command::Context
4
+ # @rbs @command: String
5
+ # @rbs @emit: ^(Riffer::Rig::Events::_Event) -> void
6
+ # @rbs @turn: ^(String) -> void
7
+
8
+ # @dynamic args, runtime, host, settings
9
+ attr_reader :args #: String
10
+ attr_reader :runtime #: Riffer::Rig::Runtime
11
+ attr_reader :host #: Riffer::Rig::Hosts::_Host
12
+ attr_reader :settings #: Hash[Symbol, untyped]
13
+
14
+ # @rbs command: String
15
+ # @rbs args: String
16
+ # @rbs runtime: Riffer::Rig::Runtime
17
+ # @rbs host: Riffer::Rig::Hosts::_Host
18
+ # @rbs settings: Hash[Symbol, untyped]
19
+ # @rbs emit: ^(Riffer::Rig::Events::_Event) -> void
20
+ # @rbs turn: ^(String) -> void
21
+ # @rbs return: void
22
+ def initialize(command, args, runtime:, host:, settings:, emit:, turn:)
23
+ @command = command
24
+ @args = args
25
+ @runtime = runtime
26
+ @host = host
27
+ @settings = settings
28
+ @emit = emit
29
+ @turn = turn
30
+ freeze
31
+ end
32
+
33
+ # @rbs text: String
34
+ # @rbs return: nil
35
+ def say(text)
36
+ emit(Riffer::Rig::Events::CommandOutput.new(@command, text))
37
+ end
38
+
39
+ # @rbs event: Riffer::Rig::Events::_Event
40
+ # @rbs return: nil
41
+ def emit(event)
42
+ @emit.call(event)
43
+ nil
44
+ end
45
+
46
+ # @rbs text: String
47
+ # @rbs return: nil
48
+ def prompt(text)
49
+ @turn.call(text)
50
+ nil
51
+ end
52
+
53
+ # @rbs question: String?
54
+ # @rbs options: Array[String]?
55
+ # @rbs secret: bool
56
+ # @rbs return: String?
57
+ def ask(question = nil, options: nil, secret: false)
58
+ return unless host.capabilities.include?(:ask)
59
+
60
+ host.ask(question, options: options, secret: secret)
61
+ end
62
+
63
+ # @rbs question: String?
64
+ # @rbs return: bool
65
+ def confirm(question = nil)
66
+ return false unless host.capabilities.include?(:confirm)
67
+
68
+ host.confirm(question)
69
+ end
70
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Riffer::Rig::Command
4
+ # @rbs @block: ^(Riffer::Rig::Command::Context) -> void
5
+
6
+ # @dynamic name, description, extension
7
+ attr_reader :name #: String
8
+ attr_reader :description #: String
9
+ attr_reader :extension #: String
10
+
11
+ # @rbs name: String
12
+ # @rbs description: String
13
+ # @rbs extension: String
14
+ # @rbs &block: (Riffer::Rig::Command::Context) -> void
15
+ # @rbs return: void
16
+ def initialize(name, description:, extension:, &block)
17
+ @name = name
18
+ @description = description
19
+ @extension = extension
20
+ @block = block
21
+ freeze
22
+ end
23
+
24
+ # @rbs ctx: Riffer::Rig::Command::Context
25
+ # @rbs return: void
26
+ def call(ctx)
27
+ @block.call(ctx)
28
+ end
29
+ end