agent-cli-runtime 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59030c0c124438712ef78cc86c61a0d26ce929ff424ea468493e0c899f660599
4
- data.tar.gz: fe1ccdd4b7c38c75dbd5491b20eefde6d60e493e4ceec116cbc3c2181f159559
3
+ metadata.gz: 38323f74e355a6a9da1dfb45ccee48c253ffbd0c9693a16cd62910ced381c7cb
4
+ data.tar.gz: 2e38766e9b737df1e5360ac8013849cf2108dab3f8b937f24ec393c001aba669
5
5
  SHA512:
6
- metadata.gz: 4f794b419f2503d453e08c60e49a0c39d21eb7fd096a658fc18fd159c2b13803e5259f7c0bcb69f1033e1f964d818789db9713ada5eeaecd624710af65806506
7
- data.tar.gz: 6746e8807b5da2ee77b9e48828db7cb7de5aea48553961be867143a5b09110ca59de3ff40f3a34fbaeb38df6b763d728f9727586cfd5bb2126982a6c22e85327
6
+ metadata.gz: 95e1213e6586ea162e2a12294b350d4ea5190c8f09239e276fe4de17ceb1a7787c0c4979e3e71c6acb0f8e318facea099820a221130b123549d8a123c13e62b7
7
+ data.tar.gz: 1cca33e3af240a7abf5b8627d69256ee43647557fc2aeeab4f74692c7ba08ff74a0b0a5cdef202796f4559cc04d0498138762003fd14845c61029bd0b7603231
data/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.2.0 - 2026-08-15
6
+
7
+ - Add OpenCode `1.18.16+` as a fifth immutable built-in profile with exact
8
+ `provider/model` routing and faithful model-variant validation.
9
+ - Add route-aware offline probing for the required run/export flags, selected
10
+ authentication source, cached model inventory, and exact requested route.
11
+ - Add invocation-owned OpenCode config/data/cache/state overlays with
12
+ deny-first `read-only` and `workspace-write` policies, explicit credential
13
+ forwarding, owner-private resources, and idempotent cleanup.
14
+ - Add strict run/export correlation and typed outcomes for completion,
15
+ authentication/configuration/CLI failure, malformed output, cancellation,
16
+ and timeout while preserving requested versus actual route identity.
17
+ - Preserve unavailable separately from numeric zero for input, output,
18
+ cache-read, cache-write, reasoning, and cost evidence.
19
+ - Keep process spawning, streaming, timeout/cancellation supervision,
20
+ process-tree cleanup, retries, and post-run inspection execution with the
21
+ caller; the component returns commands and normalizes captured evidence.
22
+
23
+ ## 0.1.1 - 2026-08-11
24
+
25
+ - Expose each profile's immutable credential-environment key inventory so
26
+ orchestrators can isolate named subscription/session bindings without
27
+ embedding provider-specific compatibility tables.
28
+ - Expose each profile's configuration-directory override and home-relative
29
+ default so orchestrators can detect subscription-session aliases without
30
+ maintaining a second provider-specific table.
31
+ - Include Claude's ambient auth-token override in the isolation inventory so a
32
+ named subscription binding cannot be silently replaced by caller state.
33
+ - Preserve an optional immutable provider signal in observable results so a
34
+ trusted orchestrator can carry structured transport evidence without moving
35
+ provider-health classification into the compatibility package.
36
+
3
37
  ## 0.1.0 - 2026-07-26
4
38
 
5
39
  - Add immutable profiles for Claude Code, Codex CLI, Pi, and Grok CLI.
data/README.md CHANGED
@@ -1,25 +1,24 @@
1
1
  # Agent CLI Runtime
2
2
 
3
- `agent-cli-runtime` is a small Ruby library for tools that need to describe
4
- and inspect locally installed headless agent CLIs without owning an agent
5
- orchestration system.
6
-
7
- It ships immutable profiles for Claude Code, Codex CLI, Pi, and Grok CLI;
8
- compiles provider-neutral requests into argv/stdin; reports typed capability
9
- evidence; extracts usage from provider JSON events; and exposes an honest local
10
- diagnostic command.
3
+ `agent-cli-runtime` is a small Ruby library for tools that integrate with
4
+ locally installed headless agent CLIs. It ships immutable profiles for Claude
5
+ Code, Codex CLI, Pi, Grok CLI, and OpenCode and compiles provider-neutral
6
+ requests into argv/stdin. It also reports typed capability evidence, extracts
7
+ usage from provider JSON events, and exposes an honest local diagnostic
8
+ command. OpenCode adds an invocation-owned overlay and strict captured-result
9
+ normalizer while leaving process supervision with the caller.
11
10
 
12
11
  ## Install
13
12
 
14
13
  ```ruby
15
- gem "agent-cli-runtime", "~> 0.1.0"
14
+ gem "agent-cli-runtime", "~> 0.2.0"
16
15
  ```
17
16
 
18
17
  ```ruby
19
18
  require "agent_cli_runtime"
20
19
  ```
21
20
 
22
- Ruby 3.4 or newer is required. Version 0.1.x is tested on Linux and macOS.
21
+ Ruby 3.4 or newer is required. The 0.2.x line is tested on Linux and macOS.
23
22
 
24
23
  ## Compile an invocation
25
24
 
@@ -42,11 +41,11 @@ Compilation does not execute the returned command. Unsupported requested
42
41
  controls raise `AgentCliRuntime::UnsupportedCapability` with typed evidence
43
42
  instead of silently widening the request.
44
43
 
45
- `permission_mode: nil` deliberately preserves Hive's existing trusted,
46
- headless behavior: providers with a bypass flag receive that flag. Consumers
47
- that do not want this compatibility path should pass `"read-only"` or
48
- `"workspace-write"` explicitly. A profile raises instead of pretending to
49
- enforce a mode its CLI cannot represent.
44
+ `permission_mode: nil` selects the profile's default non-interactive permission
45
+ flags, which may include a provider's bypass flag. Pass `"read-only"` or
46
+ `"workspace-write"` explicitly when the integration requires that constraint.
47
+ A profile raises instead of pretending to enforce a mode its CLI cannot
48
+ represent.
50
49
 
51
50
  ## Public API
52
51
 
@@ -59,6 +58,14 @@ enforce a mode its CLI cannot represent.
59
58
  - `extract_usage(profile, event)` normalizes provider usage when present and
60
59
  returns `nil` when usage is absent or malformed.
61
60
  - `observe(profile, result)` normalizes bounded, redacted result metadata.
61
+ - `prepare!(open_code_preparation)` creates and probes an isolated OpenCode
62
+ overlay and returns a `PreparedInvocation`; it does not spawn OpenCode.
63
+ - `parse_run(profile, stdout:)` parses a successful OpenCode JSONL capture
64
+ into the session and terminal-message identity required for inspection.
65
+ - `prepare_inspection(prepared, parsed_run)` compiles the non-model sanitized
66
+ session-export command without executing it.
67
+ - `normalize(profile, captured, requested_route:)` returns one typed OpenCode
68
+ outcome from caller-captured run, termination, and inspection evidence.
62
69
 
63
70
  Provider arguments accept a built-in name or an `AgentCliRuntime::Profile`.
64
71
  Unknown built-in names raise `AgentCliRuntime::UnknownProvider`; they are not
@@ -76,6 +83,9 @@ profile = AgentCliRuntime::Profile.new(
76
83
  min_version: "1.2.0",
77
84
  prompt_style: :stdin,
78
85
  read_only_flags: ["--sandbox", "read-only"],
86
+ credential_environment_keys: ["ACME_API_KEY", "ACME_OAUTH_TOKEN"],
87
+ configuration_environment_key: "ACME_HOME",
88
+ default_configuration_directory: ".acme",
79
89
  cli_capabilities: {
80
90
  safe_mode: ["--safe-mode"]
81
91
  },
@@ -100,16 +110,155 @@ AgentCliRuntime.compile(request)
100
110
  Custom capability names cannot shadow the standard capability vocabulary.
101
111
  Capability checks use discrete argv, inspect the installed CLI's help, and
102
112
  fail closed when a declared option is not advertised.
113
+ `credential_environment_keys` is an immutable compatibility inventory for
114
+ orchestrators that isolate a named CLI session; it contains variable names,
115
+ never their values.
116
+ `configuration_environment_key` and `default_configuration_directory` describe
117
+ where the CLI owns its subscription/session state. `configuration_directory`
118
+ resolves that location from a caller-supplied home and environment without
119
+ reading credentials or deciding authentication policy.
120
+
121
+ ## Prepare and normalize OpenCode
122
+
123
+ OpenCode `1.18.16+` requires an exact `provider/model` route and an explicit,
124
+ read-only configuration source. Configuration may define providers and an
125
+ exact default model, but it must not contain credential values. Name the
126
+ credential environment variables the caller is allowed to forward instead.
127
+
128
+ ```ruby
129
+ require "agent_cli_runtime"
130
+ require "tmpdir"
131
+
132
+ route = "anthropic/claude-sonnet-4-5"
133
+ profile = AgentCliRuntime::Profiles.fetch(:opencode)
134
+ request = AgentCliRuntime::Request.new(
135
+ profile: profile,
136
+ prompt: "Make the requested atomic edit",
137
+ permission_mode: "workspace-write",
138
+ model: route,
139
+ effort: "high"
140
+ )
141
+ preparation = AgentCliRuntime::OpenCodePreparationRequest.new(
142
+ request: request,
143
+ working_directory: Dir.pwd,
144
+ invocation_root: File.join(Dir.tmpdir, "my-opencode-invocation"),
145
+ configuration: {
146
+ "model" => route,
147
+ "provider" => {
148
+ "anthropic" => { "npm" => "@ai-sdk/anthropic" }
149
+ }
150
+ },
151
+ credential_environment_keys: ["ANTHROPIC_API_KEY"],
152
+ additional_read_roots: [Dir.pwd],
153
+ additional_write_roots: [Dir.pwd]
154
+ )
155
+
156
+ prepared = AgentCliRuntime.prepare!(preparation)
157
+ begin
158
+ # The caller owns spawning, capture, timeout/cancellation, and process-tree
159
+ # cleanup. Forward only this selected environment to the child.
160
+ run_argv = prepared.invocation.argv
161
+ run_stdin = prepared.invocation.stdin_data
162
+ run_environment = prepared.environment_for(env: ENV)
163
+
164
+ # After a zero main-process exit, parse the captured JSONL and run the
165
+ # separately compiled, non-model sanitized export under the same overlay.
166
+ parsed = AgentCliRuntime.parse_run(:opencode, stdout: run_stdout)
167
+ inspection = AgentCliRuntime.prepare_inspection(prepared, parsed)
168
+ inspection_argv = inspection.argv
169
+ inspection_environment = inspection.environment_for(env: ENV)
170
+
171
+ captured = AgentCliRuntime::CapturedResult.new(
172
+ stdout: run_stdout,
173
+ stderr: run_stderr,
174
+ termination: AgentCliRuntime::TerminationEvidence.new(exit_code: 0),
175
+ inspection_output: sanitized_export_stdout
176
+ )
177
+ outcome = AgentCliRuntime.normalize(
178
+ :opencode, captured, requested_route: prepared.requested_route
179
+ )
180
+ ensure
181
+ prepared.cleanup! if prepared
182
+ end
183
+ ```
184
+
185
+ `run_stdout`, `run_stderr`, and `sanitized_export_stdout` above are captures
186
+ provided by the caller's process supervisor. Execute the inspection only after
187
+ a successful main run. For a timeout, cancellation, signal, or non-zero exit,
188
+ construct the matching `TerminationEvidence` and normalize without pretending
189
+ that incomplete output is a successful result.
190
+
191
+ Preparation creates owner-private config, data, cache, and state paths below
192
+ the fresh invocation root; redirects OpenCode into them; disables ambient
193
+ project/default discovery and remote model refresh; checks version, required
194
+ flags, selected auth, cached route, and requested variant locally; and returns
195
+ discrete argv/environment values. It never sends a prompt or model request.
196
+ `PreparedInvocation#cleanup!` removes only invocation-owned paths and is safe
197
+ to call twice. Call it from the process owner's `ensure` path after every
198
+ pre-spawn and post-spawn outcome.
199
+
200
+ `read-only` denies edits, shell, unsafe tools, and external writes.
201
+ `workspace-write` permits edits only under the declared write roots and still
202
+ denies unrestricted shell. A `nil` permission mode is rejected unless the
203
+ consumer supplies an explicit typed `OpenCodePermissionPolicy`; the ordinary
204
+ preparation API never silently falls back to a bypass. Plugin sources are
205
+ explicit, and `--pure` remains enabled when no plugin was selected.
206
+
207
+ A completed outcome contains one bounded final assistant message, requested
208
+ and sanitized-export-observed routes, and nullable input/output/cache
209
+ read/cache write/reasoning/cost fields. Missing evidence stays `nil`; numeric
210
+ zero stays zero. Other outcome kinds are `authentication_failure`,
211
+ `configuration_failure`, `cli_failure`, `malformed_output`, `cancelled`, and
212
+ `timed_out`. Diagnostics and unknown-event summaries are bounded and redacted.
213
+
214
+ Maintainers can run the installed-CLI offline contract without a prompt or
215
+ model request:
216
+
217
+ ```sh
218
+ bundle exec ruby -Itest test/opencode_offline_smoke_test.rb
219
+ ```
220
+
221
+ If an installation command is itself a package-manager shim, set
222
+ `AGENT_CLI_RUNTIME_OPENCODE_OFFLINE_BIN` to the already-installed native
223
+ OpenCode executable so the smoke cannot trigger shim installation or refresh
224
+ behavior.
225
+
226
+ The authenticated atomic-edit smoke is separately gated and refuses to run
227
+ without an explicit route, config path, credential variable name, opt-in, and
228
+ non-empty selected credential:
229
+
230
+ ```sh
231
+ AGENT_CLI_RUNTIME_OPENCODE_LIVE=1 \
232
+ AGENT_CLI_RUNTIME_OPENCODE_LIVE_ROUTE=anthropic/claude-sonnet-4-5 \
233
+ AGENT_CLI_RUNTIME_OPENCODE_LIVE_CONFIG=/absolute/path/opencode.json \
234
+ AGENT_CLI_RUNTIME_OPENCODE_LIVE_CREDENTIAL_ENV=ANTHROPIC_API_KEY \
235
+ ANTHROPIC_API_KEY=... \
236
+ bundle exec ruby -Itest test/opencode_live_test.rb
237
+ ```
238
+
239
+ The live test records only route, CLI version, outcome/usage availability, and
240
+ cleanup state. It does not print the credential or raw selected config. A
241
+ missing opt-in input is an explicit skip, not a deterministic-suite failure.
103
242
 
104
243
  ## Inspect local prerequisites
105
244
 
245
+ ```ruby
246
+ probe = AgentCliRuntime.probe(:codex)
247
+ probe.ready
248
+ probe.version
249
+ probe.auth_configuration.status
250
+
251
+ # Returns the ready probe or raises AgentCliRuntime::ProbeError.
252
+ AgentCliRuntime.prepare!(:codex)
253
+ ```
254
+
106
255
  ```sh
107
256
  agent-runtime probe codex
108
257
  agent-runtime probe --all --json
109
258
  ```
110
259
 
111
260
  The JSON contract is `{"schema_version":1,"probes":[...]}` and always orders
112
- all-provider output as `claude`, `codex`, `pi`, `grok`.
261
+ all-provider output as `claude`, `codex`, `pi`, `grok`, `opencode`.
113
262
 
114
263
  - Exit `0`: every requested local probe is ready.
115
264
  - Exit `1`: at least one requested local prerequisite is unavailable.
@@ -120,37 +269,48 @@ configuration presence, and declared capabilities. `configured` means a
120
269
  recognized local file or environment variable is present. It does not mean the
121
270
  credential is valid, the provider is online, or the account has quota.
122
271
 
123
- ## Scope and compatibility
272
+ ## Normalize provider output
124
273
 
125
- The library does not spawn or supervise agents, retry work, run workflows,
126
- accept artifacts, or interpret Hive state. Provider flags and event formats are
127
- SemVer-governed public behavior. Additive fields are compatible within 0.1.x;
128
- removing or changing an existing field or meaning requires a new minor version
129
- while the gem remains pre-1.0.
130
-
131
- Development remains in the Hive monorepo under
132
- `components/agent-cli-runtime`. Hive is the primary consumer and HiveBench is
133
- the first named external adopter. The Hive maintainer team owns compatibility,
134
- security response, and releases for the package.
135
-
136
- ## Development
137
-
138
- ```sh
139
- cd components/agent-cli-runtime
140
- bundle exec rake test
141
- gem build agent-cli-runtime.gemspec
274
+ ```ruby
275
+ usage = AgentCliRuntime.extract_usage(
276
+ :codex,
277
+ "type" => "turn.completed",
278
+ "usage" => {
279
+ "input_tokens" => 120,
280
+ "output_tokens" => 42
281
+ }
282
+ )
283
+ # => { input: 120, output: 42, cached: 0, model: nil }
284
+
285
+ result = AgentCliRuntime.observe(
286
+ :codex,
287
+ exit_code: 0,
288
+ timed_out: false,
289
+ status: :completed,
290
+ usage: usage,
291
+ final_message: "Review complete"
292
+ )
293
+ result.status # => :completed
294
+ result.usage # => the normalized usage hash
142
295
  ```
143
296
 
144
- The root suite includes these package tests plus Hive/package parity coverage;
145
- run `bundle exec rake test:agent_cli_runtime` from the repository root for the
146
- standalone component gate alone.
297
+ Malformed or unrelated events return `nil` from `extract_usage`; they do not
298
+ invent zero-token usage. `observe` returns a frozen
299
+ `AgentCliRuntime::ObservableResult` with bounded, redacted diagnostics. A
300
+ trusted caller may also supply an already-normalized `provider_signal`; the
301
+ runtime carries that optional immutable value but does not classify failures or
302
+ own provider-health policy.
303
+
304
+ ## Compatibility
147
305
 
148
- Release instructions live in the repository's `docs/RELEASING.md`. Releases
149
- use component-scoped tags and publish exact preverified gem bytes through
150
- RubyGems trusted publishing.
306
+ Provider flags, event formats, and public value-object fields are
307
+ SemVer-governed behavior. Additive fields are compatible within 0.2.x; removing
308
+ or changing an existing field or meaning requires a new minor version while the
309
+ gem remains pre-1.0.
151
310
 
152
311
  ## Security
153
312
 
154
313
  Diagnostics are bounded and redact common credential forms. The library never
155
314
  prints credential file contents or environment values. Report vulnerabilities
156
- through the Hive repository’s security policy.
315
+ privately through the
316
+ [package security policy](https://github.com/ivankuznetsov/agent-cli-runtime/security/policy).
@@ -9,22 +9,22 @@ Gem::Specification.new do |spec|
9
9
  spec.description = <<~DESC
10
10
  Agent CLI Runtime provides immutable profiles, invocation compilation,
11
11
  local prerequisite probes, capability evidence, usage extraction, and
12
- result normalization for Claude Code, Codex CLI, Pi, and Grok CLI. It does
13
- not spawn agents or claim live provider health, quota, or credential validity.
12
+ result normalization for Claude Code, Codex CLI, Pi, Grok CLI, and
13
+ OpenCode. It does not spawn agents or claim live provider health, quota,
14
+ or credential validity.
14
15
  DESC
15
- spec.homepage = "https://github.com/ivankuznetsov/hive"
16
+ spec.homepage = "https://github.com/ivankuznetsov/agent-cli-runtime"
16
17
  spec.license = "MIT"
17
18
  spec.required_ruby_version = ">= 3.4.0"
18
19
 
19
20
  spec.metadata = {
20
21
  "homepage_uri" => spec.homepage,
21
- "source_code_uri" =>
22
- "https://github.com/ivankuznetsov/hive/tree/main/components/agent-cli-runtime",
22
+ "source_code_uri" => spec.homepage,
23
23
  "changelog_uri" =>
24
- "https://github.com/ivankuznetsov/hive/blob/main/components/agent-cli-runtime/CHANGELOG.md",
24
+ "#{spec.homepage}/blob/main/CHANGELOG.md",
25
25
  "bug_tracker_uri" => "https://github.com/ivankuznetsov/hive/issues",
26
26
  "documentation_uri" =>
27
- "https://github.com/ivankuznetsov/hive/blob/main/components/agent-cli-runtime/README.md",
27
+ "#{spec.homepage}/blob/main/README.md",
28
28
  "rubygems_mfa_required" => "true"
29
29
  }
30
30
 
@@ -14,4 +14,11 @@ module AgentCliRuntime
14
14
  class ProbeError < Error; end
15
15
  class CompilationError < Error; end
16
16
  class UnknownProvider < Error; end
17
+ class PreparationError < Error; end
18
+ class ConfigurationError < PreparationError; end
19
+ class AuthenticationError < ProbeError; end
20
+ class RouteUnavailable < ProbeError; end
21
+ class UnsafePathError < PreparationError; end
22
+ class ResultError < Error; end
23
+ class MalformedOutput < ResultError; end
17
24
  end
@@ -0,0 +1,32 @@
1
+ module AgentCliRuntime
2
+ module OpenCode
3
+ module Inspection
4
+ module_function
5
+
6
+ def compile(prepared, parsed_run)
7
+ unless prepared.is_a?(PreparedInvocation)
8
+ raise ArgumentError,
9
+ "prepared must be an AgentCliRuntime::PreparedInvocation"
10
+ end
11
+ unless parsed_run.is_a?(ParsedRun)
12
+ raise ArgumentError, "parsed_run must be an AgentCliRuntime::ParsedRun"
13
+ end
14
+ unless prepared.invocation.provider == :opencode
15
+ raise ConfigurationError,
16
+ "sanitized export inspection requires an OpenCode invocation"
17
+ end
18
+
19
+ InspectionCommand.new(
20
+ argv: [
21
+ prepared.executable, "export", parsed_run.session_id, "--sanitize"
22
+ ],
23
+ stdin_data: nil,
24
+ environment: prepared.environment,
25
+ credential_environment_keys: prepared.credential_environment_keys,
26
+ session_id: parsed_run.session_id,
27
+ message_id: parsed_run.terminal_message_id
28
+ )
29
+ end
30
+ end
31
+ end
32
+ end