module

Enable::Runtime

The coding CLIs enbl execute can run, what to start for each, and how to read what it printed. The argv and env match the enable_fleet gem's EnableFleet::Cli adapters, so Enable and enbl agree. Pure data-in / data-out: nothing here runs.

Enable sets each CLI's home (CLAUDE_CONFIG_DIR, CODEX_HOME, XDG_CONFIG_HOME, GROK_HOME, HOME) and its key (from a 0600 env file) before it starts enbl. The child inherits them unchanged. A key never goes on a command line: the runner is shared, and every Unix user on it can read ps.

Constants

KEYRING = "if command -v gnome-keyring-daemon >/dev/null; then printf enable | gnome-keyring-daemon --daemonize --unlock --components=secrets >/dev/null 2>&1; else echo \"gnome-keyring is not installed, so a Google sign-in to agy cannot be kept\" >&2; fi; exec \"$@\""

agy keeps its Google login only in a Secret Service on D-Bus. As the gem's agy adapter does, a run starts in a D-Bus session of its own with a gnome-keyring under HOME (the login file Enable restores), unlocked by a fixed password, then execs agy. Without gnome-keyring agy still runs: a key needs no keyring.

KEYRING_ARGV = ["dbus-run-session", "--", "sh", "-c", KEYRING, "agy"]
LINE = 160

How long one printed line of a running run may be.

NAMES = ["claude-code", "codex", "cursor", "grok", "agy"] of ::String
STATS = ["total_cost_usd", "duration_ms", "num_turns", "usage"] of ::String
TOKEN = /\b(?:sk-[\w-]{8,}|xai-[\w-]{8,}|key_[\w-]{8,}|AIza[\w-]{20,}|ya29\.[\w.-]+|eyJ[\w-]+\.[\w.-]+)/

What looks like a key or token, for keys enbl does not know by name. Codex prints a rejected key in its 401 message ("Incorrect API key provided: sk-...").

Instance methods

argv(runtime : String, prompt : String, budget : String = "2.00", session_id : String | Nil = nil, model : String | Nil = nil) : Array(String)

The command line for +runtime+. The prompt is an argument of its own, never shell text.

Source
describe(runtime : String, line : String, secrets : Enumerable(String) = [] of String) : Array(String)

The lines to show for one line a CLI printed, while the run works: claude-code's events in words, the other CLIs' lines as they are. Secrets are hidden, and each line is short.

Source
env(runtime : String, task_id : String, contract_id : String | Nil = nil, company_id : String | Nil = nil) : Hash(String, String)

The env enbl adds for the child. Process.run keeps the rest of enbl's own env, so the home and key that Enable set reach the CLI unchanged.

Source
error?(runtime : String, events : Array(JSON::Any), exit_code : Int32 | Nil) : Bool

Whether the CLI says the run failed although it may have exited 0.

Source
events(text : String) : Array(JSON::Any)

The JSON objects in +text+: the whole text when it is one object (claude-code, cursor), else each line that is one (codex, grok and agy print events or logs). agy's stream nests each event under its name ({"event":"result","result":{...}}), and the inner object is the event.

Source
redact(text : String, secrets : Enumerable(String)) : String
Source
result(runtime : String, stdout : String, exit_code : Int32 | Nil, stderr : String = "", secrets : Enumerable(String) = [] of String) : Result

Read one run. +stdout+ is posted as the output (Rails reads a JSON object with is_error: true as a failure). The end of +stderr+ stands in for a failed run that printed nothing on stdout.

Source
secrets(env = ENV) : Array(String)

The values to hide from anything enbl posts: every key and token in +env+.

Source
stats(events : Array(JSON::Any)) : Hash(String, JSON::Any)

total_cost_usd, duration_ms, num_turns and usage where the CLI reports them; the last event that has one wins. agy reports duration_seconds, and usage per step: a run stopped before its result has the sum of its steps' usage.

Source

Nested types