riffer-rig 0.15.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 +28 -0
  3. data/README.md +25 -204
  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 +36 -7
  50. data/lib/riffer/rig/terminal/count.rb +6 -0
  51. data/lib/riffer/rig/terminal/frame.rb +12 -4
  52. data/lib/riffer/rig/terminal/message_queue.rb +4 -4
  53. data/lib/riffer/rig/terminal/opening.rb +1 -0
  54. data/lib/riffer/rig/terminal/picker.rb +5 -2
  55. data/lib/riffer/rig/terminal/repl.rb +2 -2
  56. data/lib/riffer/rig/terminal/session.rb +0 -2
  57. data/lib/riffer/rig/terminal/status_bar.rb +20 -12
  58. data/lib/riffer/rig/terminal/theme.rb +1 -0
  59. data/lib/riffer/rig/terminal/ticker.rb +8 -3
  60. data/lib/riffer/rig/terminal/turn_report.rb +1 -1
  61. data/lib/riffer/rig/tools/bash/kill.rb +1 -1
  62. data/lib/riffer/rig/tools/bash/output.rb +3 -2
  63. data/lib/riffer/rig/tools/bash.rb +3 -4
  64. data/lib/riffer/rig/version.rb +1 -1
  65. data/sig/generated/riffer/rig/acp/mapper.rbs +4 -0
  66. data/sig/generated/riffer/rig/acp/servers.rbs +21 -0
  67. data/sig/generated/riffer/rig/acp.rbs +4 -19
  68. data/sig/generated/riffer/rig/cli/flags.rbs +2 -0
  69. data/sig/generated/riffer/rig/commands/auth.rbs +18 -5
  70. data/sig/generated/riffer/rig/commands/model.rbs +19 -13
  71. data/sig/generated/riffer/rig/commands/reasoning.rbs +4 -6
  72. data/sig/generated/riffer/rig/commands/reload.rbs +4 -4
  73. data/sig/generated/riffer/rig/credentials.rbs +18 -1
  74. data/sig/generated/riffer/rig/directories.rbs +5 -0
  75. data/sig/generated/riffer/rig/loader/reasoning.rbs +13 -0
  76. data/sig/generated/riffer/rig/loader/reloader.rbs +50 -0
  77. data/sig/generated/riffer/rig/loader.rbs +13 -88
  78. data/sig/generated/riffer/rig/mcp/auth.rbs +6 -9
  79. data/sig/generated/riffer/rig/mcp/declaration.rbs +0 -3
  80. data/sig/generated/riffer/rig/mcp.rbs +0 -4
  81. data/sig/generated/riffer/rig/provider_setup.rbs +10 -5
  82. data/sig/generated/riffer/rig/runtime/assembly.rbs +74 -0
  83. data/sig/generated/riffer/rig/{tools/bash → runtime}/job.rbs +8 -8
  84. data/sig/generated/riffer/rig/{tools/bash → runtime}/job_log.rbs +2 -2
  85. data/sig/generated/riffer/rig/runtime/job_notices.rbs +46 -0
  86. data/sig/generated/riffer/rig/runtime/jobs.rbs +86 -0
  87. data/sig/generated/riffer/rig/runtime/snapshot.rbs +12 -0
  88. data/sig/generated/riffer/rig/runtime/stop_reason.rbs +13 -0
  89. data/sig/generated/riffer/rig/runtime/turn.rbs +74 -0
  90. data/sig/generated/riffer/rig/runtime.rbs +45 -148
  91. data/sig/generated/riffer/rig/sdk.rbs +5 -5
  92. data/sig/generated/riffer/rig/settings/file.rbs +2 -2
  93. data/sig/generated/riffer/rig/settings/model_options.rbs +12 -0
  94. data/sig/generated/riffer/rig/stores/entries.rbs +2 -2
  95. data/sig/generated/riffer/rig/stores/recorder.rbs +2 -8
  96. data/sig/generated/riffer/rig/stores/timestamp.rbs +4 -0
  97. data/sig/generated/riffer/rig/terminal/banner.rbs +10 -3
  98. data/sig/generated/riffer/rig/terminal/count.rbs +4 -0
  99. data/sig/generated/riffer/rig/terminal/frame.rbs +7 -3
  100. data/sig/generated/riffer/rig/terminal/message_queue.rbs +5 -5
  101. data/sig/generated/riffer/rig/terminal/picker.rbs +4 -1
  102. data/sig/generated/riffer/rig/terminal/status_bar.rbs +11 -5
  103. data/sig/generated/riffer/rig/terminal/ticker.rbs +4 -1
  104. data/sig/generated/riffer/rig/tools/bash.rbs +0 -2
  105. data/sig/manual/riffer/rig/{terminal/tally.rbs → acp/servers.rbs} +2 -2
  106. data/sig/manual/riffer/rig/{terminal/spinner.rbs → loader/reasoning.rbs} +2 -2
  107. data/sig/manual/riffer/rig/mcp.rbs +0 -5
  108. data/sig/manual/riffer/rig/{loader/credentials.rbs → runtime/stop_reason.rbs} +2 -2
  109. metadata +24 -20
  110. data/lib/riffer/rig/loader/credentials.rb +0 -23
  111. data/lib/riffer/rig/terminal/message_queue/message.rb +0 -13
  112. data/lib/riffer/rig/terminal/spinner.rb +0 -13
  113. data/lib/riffer/rig/terminal/tally.rb +0 -12
  114. data/lib/riffer/rig/tools/bash/monotonic_clock.rb +0 -8
  115. data/sig/generated/riffer/rig/loader/credentials.rbs +0 -12
  116. data/sig/generated/riffer/rig/terminal/message_queue/message.rbs +0 -10
  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: e1743d133b2e91f50f10a376bbb8cd6f2d21f4d6c2f90600d5415d4ec9047b26
4
- data.tar.gz: c8131bfda29574b5c86acb1b1ebebfe0ec1dc5cc82ff7c8c8cca5410bb7b34d0
3
+ metadata.gz: 9b1648704ba63c4826d30aec44988486557c4a2fccc5b3b527fef879fc407cef
4
+ data.tar.gz: 574c73fcb0f3512f486d6d4c355b69add66d54de82c87ed170c8409821c4b3f1
5
5
  SHA512:
6
- metadata.gz: 46930dae154e4a07916c0f1391e3f44419a7e385f5d31f02509a9740276ca6bfc993efb4f0e95695ad439045f69d9c3a74e344a43af7f83a75afdb6bd94f1e58
7
- data.tar.gz: 18ce4fe1003d1767a234936fae81fb921c49461cefb7f6516bf051047734b68580bfc124803d353296a71daa9919f5f77805c83bc3c2599c9e17e8b2650b4b1d
6
+ metadata.gz: 0da1408916c461365dc8d61c07dca3b00ad5f2d22d77e42d036d379ce6231c5cff0ae3ade79a2d8f907439c8e70e33753ae3e5b91786905e5366bf414d0f3521
7
+ data.tar.gz: 994802e5749d8708313eda67386af949ff4112b9b2275b6687a2078ea2f4332b8147916608cdbf8adb5f90c632bea9b395d5929b9024907f4811e2ecca75eff1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
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
+
20
+ ## [0.16.0](https://github.com/bottrall/riffer-rig/compare/v0.15.0...v0.16.0) (2026-10-18)
21
+
22
+
23
+ ### Features
24
+
25
+ * **terminal:** show the reasoning level in the banner and status bar ([#565](https://github.com/bottrall/riffer-rig/issues/565)) ([ead3588](https://github.com/bottrall/riffer-rig/commit/ead358834580a4fcab18432cbe5414e3e21b7feb))
26
+
3
27
  ## [0.15.0](https://github.com/bottrall/riffer-rig/compare/v0.14.0...v0.15.0) (2026-10-18)
4
28
 
5
29
 
@@ -15,6 +39,10 @@
15
39
  ## [0.14.0](https://github.com/bottrall/riffer-rig/compare/v0.13.0...v0.14.0) (2026-10-18)
16
40
 
17
41
 
42
+ ### Upgrading
43
+
44
+ * the `models` pricing table in `settings.json` is no longer read. Move each entry into a [model catalog](https://riffer.ai/guides/catalog/) file's `pricing` — the same four rates, USD per million tokens — and list the file in `catalog_files`.
45
+
18
46
  ### Features
19
47
 
20
48
  * **headless:** print the session id on stderr ([#556](https://github.com/bottrall/riffer-rig/issues/556)) ([2816938](https://github.com/bottrall/riffer-rig/commit/2816938f66d32ccf557d83a33e40300e68a68032))
data/README.md CHANGED
@@ -2,245 +2,66 @@
2
2
 
3
3
  **riffer, in your terminal and in your process.**
4
4
 
5
- `riffer-rig` is a general-purpose agent for Ruby developers who want to own their harness. It runs as a terminal agent and, because the runtime is separate from the terminal, it also runs inside any Ruby process: a Rails app, a script, a job. You extend it in Ruby: tools, commands, providers and hooks are ordinary Ruby classes on [riffer](https://github.com/janeapp/riffer)'s primitives, and the same objects work whether the host is the terminal or your app. It ships with a coding toolkit (read, write, edit, bash) as the default bundle, because a shell and file access are the most efficient way to get almost any task done — but the prompt gives it no coding identity. [Overview](docs/OVERVIEW.md) has the full thesis.
5
+ `riffer-rig` is a general-purpose agent for Ruby developers who want to own their harness. It runs in your terminal, and because the runtime is separate from the terminal it also runs inside any Ruby process: a Rails app, a script, a job. You extend it in Ruby — tools, commands, providers and hooks are ordinary classes on [riffer](https://riffer.ai)'s primitives, and the same objects work in the terminal and in your app.
6
6
 
7
- ## Requirements
7
+ It ships with a coding toolkit (read, write, edit, bash) because a shell and file access get almost any task done, but nothing in its prompt makes it a coding agent. Tools run without asking for permission; see [what runs without asking](docs/TOOLS.md#what-runs-without-asking).
8
8
 
9
- - Ruby >= 4.0
9
+ ## Install
10
10
 
11
- ## Installation
12
-
13
- Install the gem:
11
+ Requires Ruby 4.0 or later.
14
12
 
15
13
  ```bash
16
14
  gem install riffer-rig
17
15
  ```
18
16
 
19
- This provides a `riffer` executable. No provider SDK is installed with it: `gemini/…` models work out of the box, and every other provider's SDK gem is either installed on its first use — `riffer-rig` asks, then installs it in-process — or added to a Gemfile ahead of time:
20
-
21
- ```ruby
22
- gem 'anthropic', '~> 1.69'
23
- ```
24
-
25
- [Providers](docs/PROVIDERS.md#sdk-gems) lists each provider's gem and how the install offer works.
26
-
27
- ## Usage
28
-
29
- Run the agent from any project directory:
17
+ ## Quickstart
30
18
 
31
19
  ```bash
20
+ cd my-project
32
21
  riffer
33
22
  ```
34
23
 
35
- 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.
36
-
37
- 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)). The reasoning effort resolves the same way: `--reasoning`, `RIFFER_REASONING`, then `reasoning` in the settings ([Configuration](docs/CONFIGURATION.md#reasoning)).
38
-
39
- ### Flags
40
-
41
- | Flag | Effect |
42
- | ----------------------- | -------------------------------------------------------------------------- |
43
- | `--model provider/name` | the model for this session, over `RIFFER_MODEL` and the settings |
44
- | `--reasoning LEVEL` | the reasoning effort for this session, over `RIFFER_REASONING` and the settings |
45
- | `--no-extensions` | skips the [`rig.rb` files](docs/EXTENSIONS.md#the-rigrb-files) and gem autoload; the bundle still loads |
46
- | `--no-skills` | leaves out [Agent Skills](docs/SKILLS.md) |
47
- | `--no-agents-md` | leaves out the [AGENTS.md](docs/INSTRUCTIONS.md#agentsmd) instructions |
48
- | `--tools read,bash` | only these tools, by identifier ([Tools](docs/TOOLS.md)) |
49
- | `--max-steps N` | stops a turn after `N` model calls |
50
- | `--no-save` | does not save this session to the [store](docs/SESSIONS.md) |
51
- | `-c`, `--continue` | continues the most recent session in this directory ([Sessions](docs/SESSIONS.md#resuming)) |
52
- | `-r [ID]`, `--resume [ID]` | resumes the session with id `ID`; bare `-r` opens the [picker](docs/SESSIONS.md#the-picker) ([Sessions](docs/SESSIONS.md#resuming)) |
53
- | `-h`, `--help` | prints the usage |
54
- | `-v`, `--version` | prints the version |
55
-
56
- An editor registers the agent as `riffer acp`, the same Runtimes over the [Agent Client Protocol](https://agentclientprotocol.com) on stdio ([ACP](docs/ACP.md)).
57
-
58
- ### Headless
59
-
60
- Run one prompt with no terminal ([Headless](docs/HEADLESS.md)):
61
-
62
- ```bash
63
- riffer -p "what does this repo do"
64
- git diff | riffer -p "review this diff" # piped stdin appended as context
65
- echo "explain this diff" | riffer -p # piped stdin alone is the prompt
66
- ```
67
-
68
- Everything loads exactly as the REPL does, the shared flags work the same — `-c` and `-r` included: `riffer -p -c "now run the tests"` continues the most recent session in this directory, and a missing session exits 2 ([Headless](docs/HEADLESS.md)). The session is saved like any other unless `--no-save`. Only the assistant's text is printed, streamed to stdout; errors and warnings go to stderr, and `--verbose` adds a one-line trace of each tool call there. With `--json`, stdout is instead one JSON object per line for every Runtime event — the NDJSON stream ([Headless](docs/HEADLESS.md#the-ndjson-stream---json)). A run whose model leaves background jobs running waits for them, giving the model a further turn as each one exits, and kills any left after `--job-idle-timeout` seconds (600 by default) without an exit ([Headless](docs/HEADLESS.md#background-jobs)). Headless never prompts: a missing model, credential, or SDK prints the reason on stderr and exits 2.
69
-
70
- The exit code says how the turn ended: `0` finished, `1` runtime error, `2` usage or configuration, `3` the turn ended without completing (`--max-steps`, a full context window, a provider content filter) or its background jobs passed the idle timeout, `130` SIGINT (the turn is cancelled first).
71
-
72
- ### In the session
73
-
74
- Type a prompt and press Enter. While the turn runs, the status line under the input shows its time, steps, tool calls, tokens and cost, and Ctrl-T peeks at what the model is writing; the reply is printed when the turn ends, followed by a stat line (`N steps · mm:ss · ↑in · ↓out · session N tok`, plus the turn's cost and the session's when the model is [priced](docs/CONFIGURATION.md#catalog_files)). The input line stays open throughout: a message sent during a turn is queued and runs when the turn ends. [Terminal](docs/TERMINAL.md) has every key.
75
-
76
- - Ctrl-C during a turn cancels it. Ctrl-C with no turn running asks for a second one, which exits.
77
- - `/exit` or `/quit` ends the session, as does Ctrl-D on an empty line.
78
- - `/help` lists the session's slash commands. `/resume [--all]`, `/new`, `/cost`, `/trace`, `/skill:<name>` and the others are described in [Terminal](docs/TERMINAL.md#commands); `/model` is covered [below](#switching-the-model), and `/reasoning` works the same way for the reasoning effort.
79
-
80
- ### Switching the model
81
-
82
- `/model provider/name` switches the model for the current session only, keeping the conversation so far, and confirms it in one line: `/model openai/gpt-5` prints `Model switched to openai/gpt-5`. 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. Switching runs the same setup for the new provider as startup does: its SDK gem is offered for install (or refused with the exact Gemfile line), and missing credentials are resolved — you are prompted for them when the host can ask, and the values apply to the running session immediately. The switch is refused with one error when the host cannot supply what is missing, and the model is left unchanged. `/model --save` writes the model in effect to the home `settings.json`, so new sessions start with it; `/model provider/name --save` switches and saves in one step, printing `Model switched to openai/gpt-5 and saved to ~/.riffer/settings.json` (a bare `/model --save` prints `Saved openai/gpt-5 to ~/.riffer/settings.json`), and a refused switch saves nothing ([Configuration](docs/CONFIGURATION.md#model)).
24
+ The first run asks for a model as `provider/name` — for example `anthropic/claude-sonnet-4-6` — and then for that provider's API key, and saves both under `~/.riffer/`. `gemini/…` models work straight away; other providers' SDK gems are [offered for install](docs/PROVIDERS.md#sdk-gems) on first use.
83
25
 
84
- ### Switching the reasoning level
26
+ Type a prompt and press Enter. `/help` lists the commands, and [Getting started](docs/GETTING_STARTED.md) walks through a first session.
85
27
 
86
- `/reasoning level` switches the reasoning effort for the current session only, keeping the conversation so far: `/reasoning high` prints `Reasoning switched to high`, `/reasoning off` turns reasoning off, and `/reasoning` alone shows the level in effect. A level riffer does not accept is rejected naming the levels it accepts. `/reasoning --save` writes the level in effect to the home `settings.json` (`/reasoning high --save` switches and saves in one step), and a home file that no longer parses is reported instead of written. The switch survives a resume, and a `--reasoning` chosen for the run wins over it for that run only ([Configuration](docs/CONFIGURATION.md#reasoning)).
28
+ ## Extend it
87
29
 
88
- ## Authentication
89
-
90
- `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:
91
-
92
- - Set the provider's environment variables, or
93
- - 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`.
94
-
95
- 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.
96
-
97
- | Provider | Environment variables | Guide |
98
- | ---------------- | ------------------------------------------------------------------------- | -------------------------------------------------- |
99
- | `anthropic` | `ANTHROPIC_API_KEY` | [Anthropic](docs/PROVIDERS.md#anthropic) |
100
- | `openai` | `OPENAI_API_KEY`; optional `OPENAI_BASE_URL` | [OpenAI](docs/PROVIDERS.md#openai) |
101
- | `gemini` | `GEMINI_API_KEY` | [Gemini](docs/PROVIDERS.md#gemini) |
102
- | `openrouter` | `OPENROUTER_API_KEY` | [OpenRouter](docs/PROVIDERS.md#openrouter) |
103
- | `azure_openai` | `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY` | [Azure OpenAI](docs/PROVIDERS.md#azure-openai) |
104
- | `amazon_bedrock` | `AWS_REGION` or `AWS_DEFAULT_REGION`; optional `AWS_BEARER_TOKEN_BEDROCK` | [Amazon Bedrock](docs/PROVIDERS.md#amazon-bedrock) |
105
-
106
- `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:
107
-
108
- ```json
109
- {
110
- "anthropic": { "type": "api_key", "api_key": "sk-ant-…" },
111
- "openai": { "type": "api_key", "api_key": "!security find-generic-password -s openai -w" }
112
- }
113
- ```
114
-
115
- 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.
116
-
117
- Inside a session, `/auth` lists every provider with where its secret comes from — `env`, `stored`, `chain` (the SDK's own credential chain, for Bedrock) or `missing`. `/auth <provider>` re-runs that provider's setup, which is how you rotate a key, and applies the new values to the current session, so the next request uses them. `/auth remove <provider>` deletes the provider's `auth.json` entry and its `providers` block in settings.
118
-
119
- ## Configuration
120
-
121
- Settings live in two scopes, both optional: `~/.riffer/settings.json` for every project, and `<cwd>/.riffer/settings.json` for one project, merged key by key with the project winning ([Configuration](docs/CONFIGURATION.md)). Every core key is optional:
122
-
123
- | Key | What it sets |
124
- | -------------- | -------------------------------------------------------------------------------- |
125
- | `model` | the model new sessions start with, as `provider/name` |
126
- | `reasoning` | the reasoning effort, which riffer translates to the provider's own parameter |
127
- | `catalog_files`| the model catalog JSON files holding pricing and reasoning maps |
128
- | `providers` | non-secret provider fields such as an endpoint or region (home file only) |
129
- | `extensions` | `disabled` — bundled extensions to leave out; `autoload` — gem extension autoload |
130
- | `reload` | `"auto"` (default) or `"manual"` — the hot-reload trigger |
131
- | `sessions` | `save: false` stops saving sessions to the store |
132
- | `tools` | the provider-native tool switches (`native`), off by default |
133
-
134
- - `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.
135
- - `RIFFER_REASONING` — the reasoning effort for this run, winning over the `reasoning` setting; a level riffer does not accept is rejected naming the levels it accepts.
136
- - `AGENTS.md` — `~/.riffer/AGENTS.md` and an `AGENTS.md` in the current working directory or any directory above it are re-read every turn as instructions that take precedence over the default norms ([Instructions](docs/INSTRUCTIONS.md#agentsmd)).
137
- - Skills — Agent Skills in `.agents/skills/` from the working directory up to the repository root, and in `~/.agents/skills/`, are offered to the model, and `/skill:<name> [text]` runs one ([Skills](docs/SKILLS.md)).
138
- - MCP servers — declared under the `mcp` key in either settings file and registered with the Runtime ([MCP](docs/MCP.md)).
139
-
140
- ## Extending
141
-
142
- An extension is a named registrar block in a `rig.rb` file — `~/.riffer/rig.rb` runs in every project, `<project>/.riffer/rig.rb` in one, and the first run of a project file asks you to trust it:
30
+ An extension is a Ruby block in `~/.riffer/rig.rb` (every project) or `.riffer/rig.rb` (one project):
143
31
 
144
32
  ```ruby
145
33
  Riffer::Rig.extension('git') do |rig|
146
- # A tool the model can call
147
- rig.tool GitLog
148
-
149
- # A /log command in the session
150
- rig.command('log', description: 'Recent commits') do |ctx|
151
- ctx.say `git log --oneline -n #{ctx.args}`
152
- end
153
-
154
- # A section appended to the system message, re-read every turn
155
- rig.prompt(:branch) { |ctx| "Branch: #{`git branch --show-current`}" }
34
+ rig.command('log', description: 'Recent commits') { |ctx| ctx.say `git log --oneline -n 10` }
35
+ rig.prompt(:branch) { "Current branch: #{`git branch --show-current`}" }
156
36
  end
157
37
  ```
158
38
 
159
- Tools, commands, prompt sections, event handlers, skills, MCP servers and providers register the same way, and the same objects work whether the host is the terminal or your app. [Extensions](docs/EXTENSIONS.md) has all eight seams, error isolation, and packaging an extension as a gem.
39
+ That adds a `/log` command and puts the current branch in the system prompt on every turn. Extensions can also add tools, hooks, skills, MCP servers, settings and providers — [Extensions](docs/EXTENSIONS.md) starts with a complete tool.
160
40
 
161
- ## Embedding
162
-
163
- The runtime is separate from the terminal, so the same agent runs inside any Ruby process:
41
+ ## Embed it
164
42
 
165
43
  ```ruby
166
44
  runtime = Riffer::Rig::Loader.runtime(cwd: Dir.pwd, host: Riffer::Rig::Hosts::Null.new)
167
- response = runtime.ask('why is this test failing?')
168
- puts response.content
45
+ puts runtime.ask('why is this test failing?').content
169
46
  ```
170
47
 
171
- `Loader.runtime` applies the same filesystem conventions as the terminal — settings, credentials, `rig.rb`, skills. [Embedding](docs/EMBEDDING.md) has the streaming `prompt`, snapshots, and rebuilding after a code reload.
48
+ This builds the same agent the terminal does, from the same settings, credentials and extensions. [Embedding](docs/EMBEDDING.md) covers streaming, cancelling, snapshots and reloading.
172
49
 
173
50
  ## Documentation
174
51
 
175
- The guides, in reading order:
176
-
177
- Start here:
178
-
179
- - [Overview](https://riffer.bottrall.dev/guides/overview/) — What riffer-rig is, the four tiers, the runtime and host layers
180
- - [Getting started](https://riffer.bottrall.dev/guides/getting-started/) — Install, pick a model, first session
181
-
182
- Using the terminal:
183
-
184
- - [Terminal](https://riffer.bottrall.dev/guides/terminal/) — The input line and its keys, queueing, the status line, peek, resume replay
185
- - [Configuration](https://riffer.bottrall.dev/guides/configuration/) — Settings scopes and every core key
186
- - [Instructions](https://riffer.bottrall.dev/guides/instructions/) — How the system message is built; AGENTS.md
187
- - [Tools](https://riffer.bottrall.dev/guides/tools/) — The bundled toolkit and provider-native tools
188
- - [Skills](https://riffer.bottrall.dev/guides/skills/) — Agent Skills directories and activation
189
- - [MCP](https://riffer.bottrall.dev/guides/mcp/) — Declaring MCP servers
190
- - [Sessions](https://riffer.bottrall.dev/guides/sessions/) — Saving, resuming, the picker, the JSONL store
191
- - [Reloading](https://riffer.bottrall.dev/guides/reloading/) — The /reload command, what reloads and what does not
192
- - [Headless](https://riffer.bottrall.dev/guides/headless/) — riffer -p, NDJSON, exit codes
193
- - [ACP](https://riffer.bottrall.dev/guides/acp/) — riffer acp, the ACP agent over stdio
52
+ The guides are at **<https://riffer.bottrall.dev>**, and their sources are in [`docs/`](docs/). Good places to start:
194
53
 
195
- Extending and embedding:
196
-
197
- - [Extensions](https://riffer.bottrall.dev/guides/extensions/) — rig.rb, the registrar, the eight seams, commands, handlers, gems
198
- - [Embedding](https://riffer.bottrall.dev/guides/embedding/) — Runtime and Loader from Ruby; snapshots; rebuild
199
- - [Hosts](https://riffer.bottrall.dev/guides/hosts/) — The Host interface and writing a host
200
-
201
- Providers:
202
-
203
- - [Providers](https://riffer.bottrall.dev/guides/providers/overview/) — Model strings, precedence, credentials, /auth
204
- - [Custom providers](https://riffer.bottrall.dev/guides/providers/custom/) — Registering a provider from an extension; the setup, credentials, listings
205
-
206
- The guide sources are in `docs/`. To preview the site locally, run `bin/docs serve` and open <http://localhost:8000>.
207
-
208
- ## Development
209
-
210
- Every project chore is a script in `bin/`. The Rakefile behind them is an implementation detail; you never need to call rake directly.
211
-
212
- | Script | What it does |
213
- | --------------- | ---------------------------------------------------------------------------------------------- |
214
- | `bin/setup` | Install dependencies on a fresh checkout (gems + rbs collection) |
215
- | `bin/test` | Run the test suite. Pass files and/or Minitest flags: `bin/test test/foo_test.rb -n /pattern/` |
216
- | `bin/lint` | Run RuboCop. Arguments are forwarded, e.g. `bin/lint -a` |
217
- | `bin/typecheck` | Check the rbs collection lockfile and `sig/generated` are current, then type-check with Steep |
218
- | `bin/rbs` | Regenerate `sig/generated` from the inline annotations in `lib/` |
219
- | `bin/rbs-watch` | Regenerate `sig/generated` whenever `lib/` changes |
220
- | `bin/ci` | Run everything CI runs, serially. Use before pushing |
221
- | `bin/build` | Build the gem into `pkg/`; the publish workflow runs this before `gem push` |
222
- | `bin/docs` | Build the docs site and API reference into `_site/`; `bin/docs serve` serves it at http://localhost:8000 |
223
-
224
- ## Releasing
225
-
226
- PR titles are [conventional commits](https://www.conventionalcommits.org/) and are linted in CI: `feat:` bumps the minor version, `fix:` bumps the patch, and `feat!:` marks a breaking change (also a minor bump while we are on 0.x). `chore:`, `docs:`, `ci:`, `refactor:` and `test:` never release. Squash-merging makes the title the commit on `main`.
227
-
228
- A PR whose code nothing in the live path reaches yet is titled `refactor:`, including fixes to such code; the PR that wires it in carries the `feat:` or `fix:`, titled for what the user gets. The changelog and GitHub Release are generated from titles, so a `feat:` on unreachable code advertises a feature nobody can use.
229
-
230
- [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.
54
+ - [Overview](docs/OVERVIEW.md) — what riffer-rig is and how it fits together
55
+ - [Configuration](docs/CONFIGURATION.md) — settings and where each value comes from
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`
59
+ - [Extensions](docs/EXTENSIONS.md) — your own tools, commands and hooks
231
60
 
232
61
  ## Contributing
233
62
 
234
- 1. Fork the repository and create your branch: `git checkout -b feature/foo`
235
- 2. Run the tests and linters locally.
236
- 3. Submit a pull request with a clear description of the change.
237
-
238
- 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).
239
64
 
240
65
  ## License
241
66
 
242
- Licensed under the MIT License. See [`LICENSE.txt`](LICENSE.txt) for details.
243
-
244
- ## Maintainer
245
-
246
- - Jake Bottrall - https://github.com/bottrall
67
+ MIT — see [`LICENSE.txt`](LICENSE.txt). Maintained by [Jake Bottrall](https://github.com/bottrall).
@@ -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