Skip to main content

Unified headless CLI for coding agents (codex, claude, copilot, gemini, opencode, goz). One interface, one JSONL output format, one resume mechanism.

Project description

heru

Unified headless CLI for coding agents. One interface, one JSONL event envelope, one resume mechanism — regardless of which underlying agent you use:

Why

Every coding-agent CLI has its own flags, its own event format, its own notion of "session", its own quota endpoint, and its own quirks. If you want to drive them headlessly from a script or higher-level orchestrator, you end up writing a separate wrapper for each — and when a new one ships you start over.

heru is that wrapper, once. Point it at any supported CLI and get the same shape out.

Usage

# Fresh run
heru codex "summarize the diff between main and my branch"

# Resume a prior session
heru codex --resume <session-id> "now also run the tests"

# Different engine, same CLI shape
heru claude "find the bug in src/foo.py"

# Apply a launch profile without changing the engine/parser
heru codex --profile zodex "run the subagent smoke test"

All output is streamed as unified JSONL by default. Pass --raw to get the engine's native JSON/JSONL stream back for debugging.

The engine name is positional. The legacy --engine CLI form was removed in v2.0.0; see CHANGELOG.md and docs/migrations/2.0.0-remove-legacy-engine-flag.md.

Cross-provider usage/quota reporting lives in quse. Use quse directly for normalized quota windows.

Launch Profiles

Use --profile (-p) when an engine should launch with a different wrapper, config directory, environment cleanup, or preflight command while keeping the same engine parser and unified event output.

Profiles are read from ~/.config/heru/profiles.toml, or from the path in HERU_PROFILES_FILE.

Example for a Z.AI-routed Codex profile managed by ~/git/.agents:

[profiles.zodex]
engine = "codex"
command = "codex"
unset_env_file = "~/git/.agents/config/codex/zodex_env_unset.txt"
preflight = [["~/git/.agents/scripts/zodex_start_proxy.sh"]]

[profiles.zodex.env]
CODEX_HOME = "~/.zodex"

If you have a real wrapper executable, use it as command = "zodex" instead.

Then run:

heru codex --profile zodex "spawn one Python subagent and summarize the result"

Claude-style profile wrappers work the same way:

[profiles.zlaude]
engine = "claude"
command = "claude"
unset_env_file = "~/git/.agents/config/claude/zlaude_env_unset.txt"

[profiles.zlaude.env]
CLAUDE_CONFIG_DIR = "~/.zlaude"

Unified Event Schema

Each stdout line is a UnifiedEvent JSON object with these common fields:

  • kind: message, tool_call, tool_result, usage, error, status, or continuation
  • engine: the emitting engine name (codex, claude, copilot, gemini, opencode, goz)
  • sequence: zero-based event order within the run
  • timestamp: ISO-8601 timestamp for the emitted event
  • role: optional role (assistant, user, system)
  • content: assistant or status text when present
  • tool_name: tool identifier for tool-related events
  • tool_input: serialized tool input when present
  • tool_output: serialized tool output when present
  • error: error text when present
  • usage_delta: per-event usage fields extracted from the provider payload
  • continuation_id: session/thread identifier when the engine emits one
  • raw: the original provider-native payload

Example:

{"kind":"message","engine":"claude","sequence":0,"timestamp":"2026-04-10T19:00:00+00:00","role":"assistant","content":"Done.","raw":{"type":"assistant","message":{"content":"Done."}}}

Install

pip install heru
# or
uv add heru

Status

v2.0.0 is a physical extraction from litehive, which uses heru as its engine execution layer. Several things from the vision above are still in flight:

  • Unified JSONL output format — heru now emits one documented event envelope across all supported engines by default.
  • Unified --resume across all engines.
  • Positional engine argument (heru codex <prompt>).
  • Standalone sandboxing — heru currently executes agents directly; sandbox integration is a follow-up.

Layout

heru/
  adapters/    per-engine CLI wrappers (codex, claude, copilot, gemini, opencode, goz)
  base.py      ExternalCLIAdapter base class + CLIInvocation
  types.py     shared engine/stream/usage types
  main.py      entrypoint for `heru <engine> <prompt>`

tests/         unit tests for adapters, stream parsing, inactivity timeout

tests/contract/ is the standalone public-API contract suite. Treat it as the "thou shalt not break" directory: changing assertions in that folder is a semver-major change and requires the full breaking-change checklist below.

API Contract

The names below are heru's stable public contract. Changes to their signature, return shape, required fields, or documented behavior are breaking changes and require a semver-major release.

Public entrypoints

  • heru.get_engine(name): resolves a stable engine name such as codex or claude to the adapter instance heru will execute.
  • heru.ENGINE_CHOICES: lists the stable engine names accepted by the CLI and get_engine.
  • heru.main.main(argv=None): provides the public heru CLI entrypoint and preserves the supported argument contract.
  • heru <engine> --profile <name> <prompt>: applies a launch profile from ~/.config/heru/profiles.toml or HERU_PROFILES_FILE without changing engine identity.

Base adapter contract

  • heru.base.ExternalCLIAdapter: base class callers may subclass against; heru preserves its constructor shape and override points as the adapter contract.
  • heru.base.CLIInvocation: immutable invocation description containing argv, cwd, env, and optional stdin payload for a launch.
  • heru.base.CLIExecutionResult: immutable execution record containing exit status, stdout/stderr, pid, and transcript accessors for a finished run.

Engine adapters

  • heru.adapters.codex.CodexCLIAdapter: stable adapter for Codex CLI command building, transcript rendering, usage extraction, and continuation discovery.
  • heru.adapters.claude.ClaudeCLIAdapter: stable adapter for Claude Code invocation and unified stream parsing.
  • heru.adapters.copilot.CopilotCLIAdapter: stable adapter for GitHub Copilot CLI invocation and stream parsing.
  • heru.adapters.gemini.GeminiCLIAdapter: stable adapter for Gemini CLI invocation, usage extraction, and continuation parsing.
  • heru.adapters.opencode.OpenCodeAdapter: stable adapter for OpenCode CLI invocation and stream parsing under the current exported class name.
  • heru.adapters.goz.GozCLIAdapter: stable adapter for Goz CLI invocation, transcript extraction, and continuation parsing.

heru.types models

  • EngineUsageWindow: normalized usage-window counters and reset metadata extracted from provider output.
  • EngineUsageObservation: normalized per-run usage or quota observation reported by adapters.
  • UnifiedEvent: stable JSONL event schema emitted across engines.
  • LiveEvent: a UnifiedEvent instance used inside live timelines.
  • LiveTimeline: ordered live-event container with summary counts and task/subagent metadata.
  • ResourceLimitEvent: normalized process-resource failure details attached to stage reports.
  • RuntimeEngineContinuation: stable continuation/session handle for resume flows across engines.
  • SubagentRef: stable reference to a spawned subagent as reported in pipeline payloads.

Internal modules

The modules below are internal implementation details and may change without notice in any release:

  • heru._continuation
  • heru.adapters._codex_impl
  • heru.adapters._claude_impl
  • heru.adapters._copilot_impl
  • heru.adapters._gemini_impl
  • heru.adapters._opencode_impl
  • heru.adapters._goz_impl

The same rule applies to private helpers and any _-prefixed name in a public module unless this README explicitly lists it as part of the stable contract.

Stability Matrix

heru version Public API breakage
v0.1.0 Initial extracted public contract. No documented breaking public API changes yet.
v1.0.0 Breaking change: provider-specific quota dataclasses and CLI fields replaced by unified short_term / long_term usage windows.
v2.0.0 Breaking change: removed the legacy prompt-first --engine form; positional heru <engine> <prompt> is now required.
v2.1.0 Breaking change: removed heru usage; use quse for normalized quota reporting.

Submitting A Breaking Change

If you need to break any public API listed above:

  1. Bump heru's semver major version in pyproject.toml.
  2. Add a CHANGELOG entry that names the broken public API and the replacement.
  3. Add a migration note for litehive and any other known consumer that relies on the changed contract.

Tests

uv run pytest

tests/contract/ is included in the default uv run pytest discovery. Those tests are fixture-only and are intended to pass in a fresh heru-only virtualenv without litehive, engine CLIs, or network access.

If a PR changes any assertion under tests/contract/, that PR is changing the documented public contract and must also:

  1. Bump heru's major version in pyproject.toml.
  2. Add a CHANGELOG entry for the break.
  3. Add a migration note describing what downstream users, including litehive, must change.

Development

Install the versioned git hook with:

./scripts/install-hooks.sh

The installer symlinks scripts/pre-commit.sh into your real git hook directory using git rev-parse --git-path hooks/pre-commit, so it works from normal checkouts and git worktrees.

When staged files touch heru/ or tests/, the hook treats that commit as a potential change to heru's public contract with litehive and runs this focused smoke suite in the litehive repo:

cd ../litehive && uv run pytest -q \
  tests/test_runner_workflow.py \
  tests/test_engine_variants_and_timeline.py \
  tests/test_heru_cli.py \
  tests/test_observability_and_status.py

The hook looks for litehive in a sibling checkout at ../litehive, then falls back to LITEHIVE_REPO. If neither exists, it prints a warning and skips the smoke run so standalone heru users are not blocked.

The smoke suite is intended to protect the editable-install contract between heru and litehive: litehive imports heru directly from your local checkout, so breaking heru method names, CLI argv shape, or event schema can regress litehive immediately. If you need to bypass the hook intentionally, commit with git commit --no-verify, but that should be the exception rather than the default workflow.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

heru-2.1.1.tar.gz (1.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

heru-2.1.1-py3-none-any.whl (52.7 kB view details)

Uploaded Python 3

File details

Details for the file heru-2.1.1.tar.gz.

File metadata

  • Download URL: heru-2.1.1.tar.gz
  • Upload date:
  • Size: 1.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for heru-2.1.1.tar.gz
Algorithm Hash digest
SHA256 34ad5570c4f2faf00d6c33b064b99555d04a9ed66124c528b7ffb648ce2da783
MD5 c71067d5eb90125270e404ad51bebe14
BLAKE2b-256 3ec1194725c29f12bf43a913e8fa4796711fe9e701e16ab716d30fa08b635a29

See more details on using hashes here.

File details

Details for the file heru-2.1.1-py3-none-any.whl.

File metadata

  • Download URL: heru-2.1.1-py3-none-any.whl
  • Upload date:
  • Size: 52.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for heru-2.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e10d361f96fb1b156d9b1ef55a7d79f43d59313c1cc4dc9c59e458e488e197e1
MD5 41bd37b689259445c22c9618fc5c0c61
BLAKE2b-256 03b4d90c9986143320e0fec779f0724d55ba6412a726fd0a79942520ea3916a5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page