riffer-rig 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -0
- data/README.md +37 -4
- data/exe/riffer +1 -1
- data/lib/riffer/rig/bundled/agents_md.rb +7 -0
- data/lib/riffer/rig/bundled/bash.rb +5 -0
- data/lib/riffer/rig/bundled/edit.rb +5 -0
- data/lib/riffer/rig/bundled/mcp.rb +9 -0
- data/lib/riffer/rig/bundled/read.rb +5 -0
- data/lib/riffer/rig/bundled/skills.rb +10 -0
- data/lib/riffer/rig/bundled/write.rb +5 -0
- data/lib/riffer/rig/bundled.rb +7 -0
- data/lib/riffer/rig/cli/flags.rb +77 -0
- data/lib/riffer/rig/cli.rb +69 -106
- data/lib/riffer/rig/command/context.rb +70 -0
- data/lib/riffer/rig/command.rb +29 -0
- data/lib/riffer/rig/commands/model.rb +54 -0
- data/lib/riffer/rig/commands/skill.rb +27 -0
- data/lib/riffer/rig/commands.rb +4 -0
- data/lib/riffer/rig/credentials.rb +2 -2
- data/lib/riffer/rig/directories.rb +20 -0
- data/lib/riffer/rig/env/invalid.rb +13 -0
- data/lib/riffer/rig/env.rb +12 -1
- data/lib/riffer/rig/events/after_response.rb +25 -0
- data/lib/riffer/rig/events/after_tool_call.rb +31 -0
- data/lib/riffer/rig/events/before_prompt.rb +25 -0
- data/lib/riffer/rig/events/before_request.rb +25 -0
- data/lib/riffer/rig/events/before_tool_call.rb +28 -0
- data/lib/riffer/rig/events/command_output.rb +3 -2
- data/lib/riffer/rig/events/notify.rb +3 -2
- data/lib/riffer/rig/events/session_end.rb +3 -2
- data/lib/riffer/rig/events/session_start.rb +3 -2
- data/lib/riffer/rig/events/skill_activated.rb +3 -2
- data/lib/riffer/rig/events/turn_end.rb +3 -2
- data/lib/riffer/rig/extension/failure.rb +16 -0
- data/lib/riffer/rig/extension.rb +16 -1
- data/lib/riffer/rig/hosts/mirror.rb +6 -7
- data/lib/riffer/rig/hosts/null.rb +1 -1
- data/lib/riffer/rig/loader.rb +163 -0
- data/lib/riffer/rig/mcp/declaration.rb +23 -0
- data/lib/riffer/rig/mcp/server.rb +16 -0
- data/lib/riffer/rig/mcp.rb +12 -0
- data/lib/riffer/rig/prompts/agents_md.rb +32 -0
- data/lib/riffer/rig/prompts.rb +4 -0
- data/lib/riffer/rig/registrar.rb +120 -5
- data/lib/riffer/rig/runtime/blocked.rb +13 -0
- data/lib/riffer/rig/runtime/hooks.rb +78 -0
- data/lib/riffer/rig/runtime/request_guardrail.rb +24 -0
- data/lib/riffer/rig/runtime/tool_runtime.rb +44 -0
- data/lib/riffer/rig/runtime.rb +506 -57
- data/lib/riffer/rig/settings/document.rb +27 -16
- data/lib/riffer/rig/settings/pricing.rb +16 -5
- data/lib/riffer/rig/settings.rb +73 -43
- data/lib/riffer/rig/skills/sources.rb +28 -0
- data/lib/riffer/rig/skills.rb +12 -0
- data/lib/riffer/rig/{events/event.rb → support/equatable.rb} +2 -11
- data/lib/riffer/rig/{ui → terminal}/animator.rb +4 -4
- data/lib/riffer/rig/{ui → terminal}/banner.rb +17 -21
- data/lib/riffer/rig/{ui → terminal}/cursor.rb +4 -4
- data/lib/riffer/rig/terminal/host.rb +85 -0
- data/lib/riffer/rig/{ui → terminal}/palette.rb +1 -1
- data/lib/riffer/rig/{ui → terminal}/renderer.rb +115 -66
- data/lib/riffer/rig/{ui → terminal}/smoother.rb +4 -4
- data/lib/riffer/rig/{ui → terminal}/theme.rb +12 -12
- data/lib/riffer/rig/terminal.rb +207 -0
- data/lib/riffer/rig/tools/bash.rb +6 -4
- data/lib/riffer/rig/tools/edit.rb +3 -2
- data/lib/riffer/rig/tools/read.rb +3 -2
- data/lib/riffer/rig/tools/write.rb +3 -2
- data/lib/riffer/rig/version.rb +1 -1
- data/lib/riffer/rig.rb +8 -8
- data/sig/generated/riffer/rig/bundled/agents_md.rbs +5 -0
- data/sig/generated/riffer/rig/bundled/bash.rbs +5 -0
- data/sig/generated/riffer/rig/bundled/edit.rbs +5 -0
- data/sig/generated/riffer/rig/bundled/mcp.rbs +5 -0
- data/sig/generated/riffer/rig/bundled/read.rbs +5 -0
- data/sig/generated/riffer/rig/bundled/skills.rbs +5 -0
- data/sig/generated/riffer/rig/bundled/write.rbs +5 -0
- data/sig/generated/riffer/rig/bundled.rbs +5 -0
- data/sig/generated/riffer/rig/cli/flags.rbs +40 -0
- data/sig/generated/riffer/rig/cli.rbs +30 -35
- data/sig/generated/riffer/rig/command/context.rbs +50 -0
- data/sig/generated/riffer/rig/command.rbs +23 -0
- data/sig/generated/riffer/rig/commands/model.rbs +27 -0
- data/sig/generated/riffer/rig/commands/skill.rbs +12 -0
- data/sig/generated/riffer/rig/commands.rbs +4 -0
- data/sig/generated/riffer/rig/credentials.rbs +4 -4
- data/sig/generated/riffer/rig/directories.rbs +11 -0
- data/sig/generated/riffer/rig/env/invalid.rbs +10 -0
- data/sig/generated/riffer/rig/env.rbs +7 -1
- data/sig/generated/riffer/rig/events/after_response.rbs +18 -0
- data/sig/generated/riffer/rig/events/after_tool_call.rbs +24 -0
- data/sig/generated/riffer/rig/events/before_prompt.rbs +18 -0
- data/sig/generated/riffer/rig/events/before_request.rbs +18 -0
- data/sig/generated/riffer/rig/events/before_tool_call.rbs +21 -0
- data/sig/generated/riffer/rig/events/command_output.rbs +3 -1
- data/sig/generated/riffer/rig/events/notify.rbs +3 -1
- data/sig/generated/riffer/rig/events/session_end.rbs +3 -1
- data/sig/generated/riffer/rig/events/session_start.rbs +3 -1
- data/sig/generated/riffer/rig/events/skill_activated.rbs +3 -1
- data/sig/generated/riffer/rig/events/turn_end.rbs +3 -1
- data/sig/generated/riffer/rig/extension/failure.rbs +13 -0
- data/sig/generated/riffer/rig/extension.rbs +14 -1
- data/sig/generated/riffer/rig/hosts/mirror.rbs +9 -9
- data/sig/generated/riffer/rig/hosts/null.rbs +1 -1
- data/sig/generated/riffer/rig/loader.rbs +77 -0
- data/sig/generated/riffer/rig/mcp/declaration.rbs +18 -0
- data/sig/generated/riffer/rig/mcp/server.rbs +13 -0
- data/sig/generated/riffer/rig/mcp.rbs +8 -0
- data/sig/generated/riffer/rig/prompts/agents_md.rbs +19 -0
- data/sig/generated/riffer/rig/prompts.rbs +4 -0
- data/sig/generated/riffer/rig/registrar.rbs +78 -4
- data/sig/generated/riffer/rig/runtime/blocked.rbs +10 -0
- data/sig/generated/riffer/rig/runtime/hooks.rbs +49 -0
- data/sig/generated/riffer/rig/runtime/request_guardrail.rbs +16 -0
- data/sig/generated/riffer/rig/runtime/tool_runtime.rbs +23 -0
- data/sig/generated/riffer/rig/runtime.rbs +225 -22
- data/sig/generated/riffer/rig/settings/document.rbs +11 -11
- data/sig/generated/riffer/rig/settings/pricing.rbs +7 -2
- data/sig/generated/riffer/rig/settings.rbs +36 -23
- data/sig/generated/riffer/rig/skills/sources.rbs +18 -0
- data/sig/generated/riffer/rig/skills.rbs +7 -0
- data/sig/generated/riffer/rig/support/equatable.rbs +15 -0
- data/sig/generated/riffer/rig/{ui → terminal}/animator.rbs +5 -5
- data/sig/generated/riffer/rig/terminal/banner.rbs +39 -0
- data/sig/generated/riffer/rig/{ui → terminal}/cursor.rbs +5 -5
- data/sig/generated/riffer/rig/terminal/host.rbs +50 -0
- data/sig/generated/riffer/rig/{ui → terminal}/palette.rbs +2 -2
- data/sig/generated/riffer/rig/terminal/renderer.rbs +126 -0
- data/sig/generated/riffer/rig/{ui → terminal}/smoother.rbs +5 -5
- data/sig/generated/riffer/rig/{ui → terminal}/theme.rbs +5 -5
- data/sig/generated/riffer/rig/terminal.rbs +85 -0
- data/sig/generated/riffer/rig/tools/bash.rbs +2 -1
- data/sig/generated/riffer/rig.rbs +4 -0
- data/sig/manual/riffer/rig/commands/model.rbs +6 -0
- data/sig/manual/riffer/rig/commands/skill.rbs +6 -0
- data/sig/manual/riffer/rig/directories.rbs +6 -0
- data/sig/manual/riffer/rig/events.rbs +6 -0
- data/sig/manual/riffer/rig/hosts.rbs +7 -0
- data/sig/manual/riffer/rig/mcp.rbs +11 -0
- data/sig/manual/riffer/rig/prompts/agents_md.rbs +6 -0
- data/sig/manual/riffer/rig/{ui/banner.rbs → skills.rbs} +2 -2
- data/sig/manual/riffer/rig/support.rbs +8 -0
- data/sig/manual/riffer/rig/terminal/banner.rbs +6 -0
- metadata +140 -27
- data/lib/riffer/rig/coding_agent.rb +0 -53
- data/lib/riffer/rig/hosts/base.rb +0 -36
- data/lib/riffer/rig/repl.rb +0 -188
- data/lib/riffer/rig/token_tally.rb +0 -54
- data/sig/generated/riffer/rig/coding_agent.rbs +0 -9
- data/sig/generated/riffer/rig/events/event.rbs +0 -20
- data/sig/generated/riffer/rig/hosts/base.rbs +0 -26
- data/sig/generated/riffer/rig/repl.rbs +0 -69
- data/sig/generated/riffer/rig/token_tally.rbs +0 -42
- data/sig/generated/riffer/rig/ui/banner.rbs +0 -42
- data/sig/generated/riffer/rig/ui/renderer.rbs +0 -94
- data/sig/manual/riffer/rig/ui.rbs +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 52fc3da32f169222f7f3bac50e5ecb6d878fe46cf1955b00b218f26af043b2e9
|
|
4
|
+
data.tar.gz: a6a2c8c3c36fb9774550f2188d7cdfb529b5abb53805bd58eb31863f00c335c8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4fb9601edf932b84ee1c91b226d22814b8d80633e891ef3a3dc2436d59fc2ea617c53772ec2b9042362e937590a525f000dc6e85bb748b8bdd1ba32e017bc551
|
|
7
|
+
data.tar.gz: c51101a3560a43400edc5608e37f4615383cfdee22f8c2975b14e7d95a52bce08c063db1d2de108189e525ee1bf3f13377e4c3b22c9bef3b0a147298ac25f228
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
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
|
+
|
|
3
39
|
## [0.6.0](https://github.com/bottrall/riffer-rig/compare/v0.5.0...v0.6.0) (2026-09-27)
|
|
4
40
|
|
|
5
41
|
|
data/README.md
CHANGED
|
@@ -26,9 +26,36 @@ 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 the provider named by the model's prefix — `anthropic/claude-sonnet-4-6
|
|
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:
|
|
32
59
|
|
|
33
60
|
- Set the provider's environment variables, or
|
|
34
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`.
|
|
@@ -55,10 +82,16 @@ Each value resolves on its own: environment variable first, then the stored valu
|
|
|
55
82
|
|
|
56
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.
|
|
57
84
|
|
|
85
|
+
### Switching the model
|
|
86
|
+
|
|
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)).
|
|
88
|
+
|
|
58
89
|
### Configuration
|
|
59
90
|
|
|
60
|
-
- `AGENTS.md` —
|
|
61
|
-
- `~/.
|
|
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:
|
|
62
95
|
|
|
63
96
|
```json
|
|
64
97
|
{
|
|
@@ -75,7 +108,7 @@ The flat `{"anthropic": "sk-…"}` shape earlier versions wrote is no longer rea
|
|
|
75
108
|
}
|
|
76
109
|
```
|
|
77
110
|
|
|
78
|
-
`
|
|
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.
|
|
79
112
|
|
|
80
113
|
## Development
|
|
81
114
|
|
data/exe/riffer
CHANGED
|
@@ -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,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,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
|
data/lib/riffer/rig/cli.rb
CHANGED
|
@@ -1,129 +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
|
-
|
|
9
|
-
# @rbs input: IO
|
|
10
|
-
# @rbs model: String
|
|
11
|
-
# @rbs env: Riffer::Rig::Env
|
|
12
|
-
# @rbs return: Integer
|
|
13
|
-
def start(output: $stdout, input: $stdin, model: Riffer::Rig::Settings.model, env: Riffer::Rig::Env.new)
|
|
14
|
-
theme = Riffer::Rig::UI::Theme.for(output, env:)
|
|
6
|
+
PRINT_FLAGS = %w[-p --print].freeze #: Array[String]
|
|
15
7
|
|
|
16
|
-
|
|
8
|
+
ACP_COMMAND = 'acp'
|
|
17
9
|
|
|
18
|
-
|
|
19
|
-
values = credentials_for(provider, theme, output:, input:, env:)
|
|
20
|
-
return 1 if values.nil?
|
|
10
|
+
USAGE_ERROR = 2
|
|
21
11
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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:)
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
private
|
|
38
40
|
|
|
39
|
-
# @rbs
|
|
40
|
-
# @rbs theme: Riffer::Rig::UI::Theme
|
|
41
|
-
# @rbs output: IO
|
|
41
|
+
# @rbs flags: Riffer::Rig::CLI::Flags
|
|
42
42
|
# @rbs input: IO
|
|
43
|
-
# @rbs env: Riffer::Rig::Env
|
|
44
|
-
# @rbs return: Hash[Symbol, String]?
|
|
45
|
-
def credentials_for(provider, theme, output:, input:, env:)
|
|
46
|
-
resolution = Riffer::Rig::Credentials.resolve(provider, host: Riffer::Rig::Hosts::Null.new, env:)
|
|
47
|
-
return resolution.values if resolution.missing.empty?
|
|
48
|
-
|
|
49
|
-
onboard(provider, resolution, theme, output:, input:)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# @rbs provider: String
|
|
53
|
-
# @rbs resolution: Riffer::Rig::Credentials::Resolution
|
|
54
|
-
# @rbs theme: Riffer::Rig::UI::Theme
|
|
55
43
|
# @rbs output: IO
|
|
56
|
-
# @rbs
|
|
57
|
-
# @rbs
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
+
)
|
|
70
67
|
end
|
|
71
|
-
|
|
72
|
-
Riffer::Rig::Credentials.store(provider, answers)
|
|
73
|
-
output.puts(theme.grey("Saved under #{File.dirname(Riffer::Rig::Credentials::PATH)} (auth.json permissions 600)."))
|
|
74
|
-
resolution.values.merge(answers)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# @rbs provider: String
|
|
78
|
-
# @rbs field: Riffer::Rig::ProviderSetup::Field
|
|
79
|
-
# @rbs theme: Riffer::Rig::UI::Theme
|
|
80
|
-
# @rbs output: IO
|
|
81
|
-
# @rbs input: IO
|
|
82
|
-
# @rbs return: String
|
|
83
|
-
def prompt(provider, field, theme, output:, input:)
|
|
84
|
-
hint = field.secret ? " #{theme.grey('(hidden)')}" : ''
|
|
85
|
-
output.print("#{theme.pink('›')} Paste your #{provider} #{field.name}#{hint}: ")
|
|
86
|
-
|
|
87
|
-
answer = (field.secret ? read_secret(input) : input.gets).to_s.strip
|
|
88
|
-
output.puts
|
|
89
|
-
answer
|
|
90
68
|
end
|
|
91
69
|
|
|
92
|
-
# @rbs
|
|
93
|
-
# @rbs
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
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)
|
|
98
75
|
end
|
|
99
76
|
|
|
100
|
-
# @rbs
|
|
101
|
-
# @rbs
|
|
102
|
-
# @rbs
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
animator.reveal(
|
|
109
|
-
[Riffer::Rig::UI::Banner.lines(
|
|
110
|
-
theme,
|
|
111
|
-
model: model,
|
|
112
|
-
cwd: Dir.pwd,
|
|
113
|
-
context: context,
|
|
114
|
-
skills: count_skills,
|
|
115
|
-
version: Riffer::Rig::VERSION
|
|
116
|
-
)]
|
|
117
|
-
)
|
|
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
|
|
118
84
|
end
|
|
119
85
|
|
|
120
|
-
# @rbs
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
count.zero? ? 'none' : count.to_s
|
|
126
|
-
rescue StandardError
|
|
127
|
-
'none'
|
|
86
|
+
# @rbs output: IO
|
|
87
|
+
# @rbs return: Integer
|
|
88
|
+
def help(output)
|
|
89
|
+
output.puts(Flags.usage)
|
|
90
|
+
0
|
|
128
91
|
end
|
|
129
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
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Riffer::Rig::Commands::Model
|
|
4
|
+
extend self
|
|
5
|
+
|
|
6
|
+
HINT = 'Use /model provider/name, with a provider from: ' \
|
|
7
|
+
"#{Riffer::Rig::Settings::PROVIDERS.join(', ')}".freeze #: String
|
|
8
|
+
|
|
9
|
+
SAVE_UNAVAILABLE = '/model --save is not available in this host'
|
|
10
|
+
|
|
11
|
+
# @rbs return: Riffer::Rig::Command
|
|
12
|
+
def command
|
|
13
|
+
Riffer::Rig::Command.new(
|
|
14
|
+
'model',
|
|
15
|
+
description: 'Switch the model for this session: /model provider/name',
|
|
16
|
+
extension: 'core'
|
|
17
|
+
) { |ctx| run(ctx) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @rbs ctx: Riffer::Rig::Command::Context
|
|
21
|
+
# @rbs return: void
|
|
22
|
+
def run(ctx)
|
|
23
|
+
model = ctx.args.strip
|
|
24
|
+
return ctx.host.notify(SAVE_UNAVAILABLE, level: :error) if model.split.include?('--save')
|
|
25
|
+
return ctx.say("Model: #{ctx.runtime.model}") if model.empty?
|
|
26
|
+
|
|
27
|
+
provider = Riffer::Rig::Settings.provider_for(model)
|
|
28
|
+
return ctx.say(HINT) unless provider && Riffer::Providers::Repository.find(provider)
|
|
29
|
+
|
|
30
|
+
missing = missing_credentials(provider, ctx.runtime.credentials)
|
|
31
|
+
return ctx.host.notify(refusal(model, provider, missing), level: :error) unless missing.empty?
|
|
32
|
+
|
|
33
|
+
ctx.runtime.model = model
|
|
34
|
+
ctx.say("Model: #{model}")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
# @rbs model: String
|
|
40
|
+
# @rbs provider: String
|
|
41
|
+
# @rbs missing: Array[Symbol]
|
|
42
|
+
# @rbs return: String
|
|
43
|
+
def refusal(model, provider, missing)
|
|
44
|
+
"Not switching to #{model}: #{provider} has no #{missing.join(', ')} in credentials"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @rbs provider: String
|
|
48
|
+
# @rbs credentials: Hash[Symbol, Hash[Symbol, String]]
|
|
49
|
+
# @rbs return: Array[Symbol]
|
|
50
|
+
def missing_credentials(provider, credentials)
|
|
51
|
+
required = Riffer::Rig::ProviderSetup[provider]&.fields&.select(&:required) || []
|
|
52
|
+
required.map(&:name).reject { |name| credentials.dig(provider.to_sym, name) }
|
|
53
|
+
end
|
|
54
|
+
end
|