roba 0.8.0

A sharp, focused sugaring of claude -p -- pipeable, composable, safe-by-default, session-re-enterable.
Documentation
# roba-config.sample.toml
# =========================
# A documented example roba.toml. Copy it to `roba.toml` (project root) or
# `~/.config/roba.toml` (user-wide) and uncomment/edit. `roba profile init`
# drops this file for you.
#
# DISCOVERY  roba reads ~/.config/roba.toml, then every roba.toml from the
#            git root down to the cwd. Closer-to-cwd files win per key; list
#            keys (prepend/append/attach/allow_tool/deny_tool/add_dir/
#            mcp_config) concatenate.
# PRECEDENCE highest wins: CLI flag > ROBA_<KEY> env var > active
#            [profile.NAME] > top-level keys below > roba's built-in defaults.
# MIRRORS    every key here is also a CLI flag (--key) and an env var
#            (ROBA_KEY). See `roba --help` for the flag/env reference.

# ===========================================================================
# Top-level defaults -- apply to every roba call in this file's scope.
# All commented out; uncomment what you want as a project/user-wide default.
# ===========================================================================

# --- Prompt composition ---
# prepend = ["CLAUDE.md"]         # files prepended to every prompt
# append  = ["CONVENTIONS.md"]    # files appended
# attach  = ["src/**/*.rs"]       # globbed files embedded with `File: PATH`
# editor_history = 1              # pre-fill -e with the last N responses (0 disables)
# git_diff   = true               # embed the working-tree diff
# git_log    = 5                  # embed the last N commits
# git_status = true               # embed `git status --short`

# --- Permissions (safe by default: Read, Glob, Grep only) ---
# readonly   = true               # explicit default (no-op marker)
# writable   = true               # add Edit + Write
# full_auto  = true               # bypass ALL tool checks (sandbox only)
# allow_tool = ["Bash(git:*)"]    # add specific tool patterns (repeatable)
# deny_tool  = ["WebFetch"]       # block patterns (deny wins over allow)
# add_dir    = ["../shared"]      # extra tool-access dirs (claude --add-dir;
#                                 #   forwarded verbatim, claude resolves them)
# permission_mode = "plan"        # claude's own mode: plan | acceptEdits
#                                 #   | dontAsk | auto | bypassPermissions | default

# --- Model / effort / system prompt ---
# model  = "haiku"                # alias (sonnet/opus/haiku) or full id
# fallback_model = "haiku"        # model to retry on if the primary is overloaded
# effort = "high"                 # low | medium | high | xhigh | max
# system_prompt = "..."           # replace the system prompt for the call
# append_system_prompt = "..."    # add to the default system prompt
# agent_notice = "..."            # replace the built-in single-turn advisory
#                                 #   text (roba injects it by default; disable
#                                 #   with --no-agent-notice or no_agent_notice).
#                                 #   "" injects nothing
# no_agent_notice = true          # suppress the built-in single-turn advisory

# --- Sessions ---
# continue = true                 # auto-continue the most recent session here
#                                 #   (or continue = "session-id" for a specific one)
# worktree = true                 # run in a fresh git worktree
# agent    = "reviewer"           # pin a claude-code subagent for the call
# no_session_persistence = true   # run without writing a resumable session record

# --- Output / rendering ---
# json  = true                    # structured --json envelope on stdout
# json_schema = "schema.json"     # path to a JSON Schema; constrains structured
#                                 #   output (claude --json-schema). roba reads the
#                                 #   file and inlines it; output surfaces under
#                                 #   .result.* in the --json envelope
# quiet = true                    # suppress metadata (footer/spinner/markers)
# plain = true                    # no markdown render / color / spinner
# stream = true                   # live TTY progress (TTY only)
# echo  = true                    # print the resolved prompt before the answer
# show_thinking = true            # render extended-thinking blocks (needs stream)
# no_dollars = true               # footer shows tokens only, no dollar figure
# trace = "trace.jsonl"           # write the spawned session's events as JSONL
# rates_file = "rates.toml"       # override the bundled per-model rate table

# --- Failure / agent-tier ---
# no_retry  = true                # surface transient failures immediately
# bare      = true                # minimal-overhead mode (ANTHROPIC_API_KEY auth)
# no_agent_check = true           # skip the --agent frontmatter permission check

# --- Limits (unattended guardrails; cap hit -> run errors, exit 1) ---
# max_turns      = 20             # cap the agentic turn count
# max_budget_usd = 5.0            # cap total spend in USD

# --- MCP servers (thin pass-through to claude --mcp-config; NOT a daemon) ---
# mcp_config = ["mcp.json"]       # load MCP servers from JSON files (forwarded
#                                 #   verbatim; claude reads them, roba does not)
# strict_mcp_config = true        # use ONLY the mcp_config servers, ignoring
#                                 #   all other configured MCP servers

# --- Template variables ---  substituted as {{KEY}} in prompts/prepends
# [vars]
# TEAM = "platform"
# STYLE = "imperative, concise"

# ===========================================================================
# Profiles -- named bundles of the keys above, activated with --profile NAME.
# A profile named `default` auto-applies when no --profile is given.
# Inspect with `roba profile {list,show,active,path}`.
# ===========================================================================

# Read-only review against the working-tree diff.
#   roba --profile review "is this safe to merge?"
[profile.review]
readonly = true
git_diff = true

# Read-only walkthrough; pair with --attach to focus on specific files.
#   roba --profile explain --attach 'src/foo.rs' "what does this do?"
[profile.explain]
readonly = true

# Commit message from staged work, with a profile-scoped template var.
#   roba --profile commit-msg "write a commit message"
[profile.commit-msg]
readonly = true
git_diff = true

[profile.commit-msg.vars]
STYLE = "imperative, concise, no marketing"

# Diagnose a failed build from piped output.
#   cargo build 2>&1 | roba --profile fix-build "what's broken?"
[profile.fix-build]
readonly = true
git_status = true

# ===========================================================================
# Aliases -- new verbs. `roba NAME [args]` expands a prompt template + flags
# and dispatches like a normal call. Inspect with `roba alias {list,show,path}`.
#
# Substitution inside `template`:
#   ${1} ${2} ...   positional args after NAME (1-based)
#   ${@}            all positional args, space-joined
#   ${name}         named arg, resolved via the `args` schema below
#   $$              a literal '$' (so dollar amounts / shell vars survive)
#   $(command)      shell substitution -- runs in YOUR shell (sh -c), stdout
#                   interpolated. This is NOT sandboxed; don't put a command
#                   you wouldn't run yourself. (Orthogonal to claude's perms.)
#                   Args interpolated into a $(...) region are shell-quoted:
#                   they are DATA, not shell code, so `roba review '1; rm -rf ~'`
#                   runs `gh pr diff '1; rm -rf ~'` (one inert arg). Put shell
#                   syntax in the template literal, not in an arg.
#
# `flags` merge BEFORE your CLI flags, so CLI wins (put extra flags after the
# positional args: `roba review 42 --full-auto`). A template-less alias is a
# flag-shortcut: your args become the prompt verbatim. Built-in names
# (cost/history/last/profile/alias) cannot be shadowed.
# ===========================================================================

# Pull a PR into a review prompt.  ->  roba review 42
[alias.review]
description = "Review a PR by number"
agent = "reviewer"
args = ["pr"]                     # positional 1 -> ${pr}
flags = ["--readonly"]
template = """
Review PR #${pr} in this repo.

$(gh pr view ${pr} --json title -q '.title')

Diff:
$(gh pr diff ${pr})
"""

# Conventional-commit message from the staged diff.  ->  roba commit-msg
[alias.commit-msg]
description = "Conventional-commit message from the staged diff"
flags = ["--quiet"]
template = "Write a conventional-commit message for:\n\n$(git diff --staged)"

# Flag-shortcut (no template): args become the prompt verbatim.
#   roba r "look at the auth module"  ->  --readonly --agent reviewer "look at the auth module"
[alias.r]
description = "Quick read-only review preset"
agent = "reviewer"
flags = ["--readonly"]

# Self-hosted "draft an alias" verb (pure config form).  ->  roba draft "..."
# The built-in `roba alias draft "..."` is the VALIDATED form -- it parses the
# generation against the real schema before printing (and `roba profile draft
# "..."` is its profile twin). This config alias is the unvalidated shortcut:
# it just asks claude, pipes nothing back through the deserializer. Reach for
# the built-in when you want the safety rail.
[alias.draft]
description = "Draft a roba alias from a description (unvalidated; see `roba alias draft`)"
flags = ["--quiet"]
template = "Write one roba `[alias.NAME]` TOML block for: ${@}. Fields: description, agent, template, flags, args. Output only the TOML block, no fences, no prose."

# ===========================================================================
# Named sessions -- bind a stable handle to a claude session id, then resume
# it with `roba --session NAME` (or ROBA_SESSION=NAME). roba only READS this
# map; it never writes it. To bind a name, run a session, grab its id from the
# JSON envelope (`roba --json ... | jq -r '.result.session_id'`), and paste it
# here. UUIDs are machine-local, so keep `[session]` in an untracked/local
# roba.toml (e.g. one git-ignored alongside the repo, or ~/.config/roba.toml).
# ===========================================================================

# [session]
# meta       = "0199aabb-ccdd-eeff-0011-223344556677"  # roba --session meta "..."
# this-repo  = "0199eeff-0011-2233-4455-66778899aabb"