Horu CLI

MVP

The portable game build toolkit.

One Go binary and one horu.yaml, dropped into a build lane you already have. It checks the environment before an expensive stage, runs the target matrix, collapses engine log noise into a failure anchor, reports what the cache did, sanitises the package, writes manifests with checksums, and emits a report. Nothing above this line requires a Horu server.

Quickstart

first run, local machine
horu init --engine unity --platform windows macos --template starter
horu build --dry-run
horu build --platform windows --configuration release
horu report --format text,json,html --out ./out/horu

How to read the MVP badge

The command surface above is implemented and exercised by our own smoke tests. The binary is not on a public download page yet, and diagnostics beyond the baseline rule packs are still shallow. Flags and configuration keys may change before 1.0. If that is an acceptable trade for the problem you have, evaluate it now and tell us where it breaks. If it is not, the roadmap will tell you when to look again.

Mental model

Horu CLI is a build engine, not an API client.

That single sentence decides most of the design. A thin client would need a server, an account and a network path before it did anything, and would fail closed when any of those were missing. An engine does the work locally and treats upload as an extra.

It also means the comparison set is Terraform, Make and Docker rather than a CI plugin: a small number of stable commands, with the interesting behaviour in flags and a config file. Adding a feature should usually mean adding a flag, not a seventh verb.

And it is why Horu CI is not a separate implementation. The runner executes the same build modules, so a run started by a person on a laptop and a run started by a queue produce records of the same shape.

Command surface

Six commands, kept deliberately small.

Help output is treated as public surface area: horu --help gives a grouped overview, and horu <command> --help documents that command's flags. Both are updated whenever behaviour changes.

The v1 command surface.
Command What it does
horu init Create a project horu.yaml from an engine, template and platform profile, and run environment checks against the selected profile.
horu build Execute configured targets from build.targets, filtered by host OS compatibility, with cache restore and save attempted by default.
horu run Wrap a build stage. Capture timing, logs and exit state, and classify the failure if there is one.
horu cache Restore, save, invalidate and report cache behaviour, including hit and miss state, miss reason, size and duration.
horu package Sanitise, validate, checksum and manifest build outputs before they are published or promoted.
horu report Emit text, JSON, HTML and CI-native annotations, with optional upload to Horu CI.

Deliberately not commands yet

horu login is only meaningful when connecting to a Horu CI server, and local value must not require a login. Upload stays a flag on report and package until its behaviour is complex enough to deserve a top-level verb.

Why init and not doctor

doctor reads as an informal support tool. init communicates the real job: verifying the machine before an expensive or release-critical operation begins. Naming is part of the interface.

Configuration

Building up horu.yaml, one block at a time.

YAML rather than a new DSL, because CI users already read it and game teams should not have to learn a language to describe a build. Below is the same file assembled in five steps.

  1. 01 project

    Name the project and the engine.

    The engine selects which rule packs, scaffold templates and package defaults apply.

    horu.yaml
    project:
      name: space-racer
      engine: unity
  2. 02 profiles

    Declare what has to exist before a long stage starts.

    Required tools, required environment variables and a disk floor. This is what horu init checks. Profile selection is automatic when omitted: ci when CI=true and that profile exists, otherwise default.

    horu.yaml
    profiles:
      unity-windows:
        tools:
          required:
            - git
            - unity
            - rsync
        environment:
          required:
            - UNITY_LICENSE
        disk:
          min_free_gb: 40
  3. 03 logs

    Collapse the noise, then classify what is left.

    collapse_patterns folds away repeating engine chatter. failure_packs selects the classifiers used to find a failure anchor and suggest a remediation.

    horu.yaml
    logs:
      unity:
        engine: unity
        collapse_patterns:
          - "^Refreshing native plugins"
          - "^Asset import worker"
        failure_packs:
          - unity-licensing
          - unity-il2cpp
          - unity-addressables
  4. 04 cache

    Derive the key from files you name.

    Paths are cached per entry rather than as one archive. exclude keeps high-churn directories out of the key and out of the payload. When an Accelerator endpoint is configured, its flags are passed to Unity while Horu still owns restore and save.

    horu.yaml
    cache:
      unity-library:
        paths:
          - Library
        exclude:
          - Library/Bee
          - Library/ShaderCache
        key:
          files:
            - Packages/manifest.json
            - ProjectSettings/ProjectVersion.txt
            - ProjectSettings/EditorBuildSettings.asset
        accelerator_endpoint: 10.0.0.15:10080
        accelerator_namespace_prefix: space-racer-macos
        accelerator_enable_download: true
        accelerator_enable_upload: true
        compression: none
  5. 05 packages

    Decide what is allowed to leave the building.

    remove strips junk. forbid fails the run. mode: fail makes that the default posture rather than a warning nobody reads.

    horu.yaml
    packages:
      steam-release:
        path: Builds/Windows
        mode: fail
        remove:
          - "**/.DS_Store"
          - "**/Thumbs.db"
          - "**/*.tmp"
          - "**/*.log"
        forbid:
          - "**/InternalOnly/**"
          - "**/*Development*.json"
        manifest: true
        checksum: sha256

Existing CI

Keep the orchestrator. Delete the glue.

In connected use, the Jenkinsfile and the workflow YAML still exist; they just get thinner, because the defensive scripting moves into commands that are versioned and tested. Below are the two integrations we document today.

Jenkinsfilegroovy
pipeline {
  agent any

  stages {
    stage('Preflight') {
      steps { sh 'horu init --engine unity --platform windows macos' }
    }

    stage('Build Matrix') {
      steps { sh 'horu build --platform windows --configuration release' }
    }

    stage('Build') {
      steps { sh 'horu run --stage build --profile unity-il2cpp -- ./ci/build.sh' }
    }

    stage('Package') {
      steps {
        sh 'horu package --path ./Builds/Windows --profile steam-release \
              --sanitize --manifest --validate'
      }
    }
  }

  post {
    always {
      sh 'horu report --format text,json,html --out ./out/horu'
      archiveArtifacts artifacts: 'out/horu/**'
    }
  }
}
.github/workflowsyaml
jobs:
  build:
    runs-on: windows-latest

    steps:
      - uses: actions/checkout@v4
      - run: horu init --engine unity --platform windows macos
      - run: horu build --platform windows --configuration release
      - run: horu run --stage build --profile unity-il2cpp -- ./ci/build.ps1
      - run: |
          horu package --path ./Builds/Windows --profile steam-release \
            --sanitize --manifest --validate
      - if: always()
        run: horu report --format github,json,html --out ./out/horu

TeamCity and GitLab follow the same shape: the orchestrator supplies the agent and the trigger, Horu CLI supplies the build behaviour and the records. On GitHub the github report format puts the failure anchor on the run as an annotation.

Cache behaviour

The defaults, stated plainly.

Caching is where build tools most often trade correctness for a graph that looks good. These are the choices we made and the reasoning behind each, so you can disagree with them on purpose rather than discover them in an incident.

See where cache evidence goes next

on by default
Restore and save are attempted whenever cache profiles are present in the config. horu build --clean disables both for a single run, which is how you take a cold baseline.
path-split entries
One entry per configured path instead of a single monolithic archive, so a change in one tree does not invalidate the rest.
warm-workspace no-op
Restore is skipped when the paths already exist and are compatible. Save is skipped when that key is already stored. Doing nothing correctly is the fastest available option.
compression: none
The default, because archive and extract overhead frequently exceeds the transfer saved on a large Unity Library tree.
exclude patterns
High-churn directories such as Library/Bee are kept out deliberately. Caching a directory that always changes is a cost with no return.
Accelerator hybrid
An Accelerator endpoint and Horu caching are not mutually exclusive. When the endpoint is configured, Horu passes the Accelerator flags through and still owns restore and save.
workspace_affinity_key
Printed on every build so an external scheduler can route a repeat job back to a machine that already holds a warm workspace.

Output contract

Every command writes into one report directory.

This is the part we treat as an interface rather than an implementation detail, because pipelines and later products depend on it.

Required, always

  • A console summary short enough to read in a CI log
  • JSON records suitable for upload
  • Stable exit codes
  • Artifact and package manifest files

Optional, on request

  • A local HTML report
  • GitHub Actions annotations
  • Upload of the same records to Horu CI

Non-negotiable

The CLI stays useful when there is no Horu CI server in the picture. Upload is an additive capability, never a prerequisite. A studio that runs Horu CLI for a year and never opens an account should still have got the value it evaluated.

Boundary

What the CLI owns, and what it refuses to.

Horu CLI owns

  • Local build instrumentation
  • Profile execution and environment verification
  • Rule-pack behaviour for logs and packages
  • Report generation in every supported format

Horu CI owns

  • Orchestration, runners and scheduling
  • Build history and dashboards
  • Artifact storage and retention
  • Team-level analytics and release gates

Existing CI systems stay valid launchers throughout, and existing source control stays authoritative unless a narrow slice of source context is needed for a build record.

Bring us your worst build lane.

Not the tidy one. The lane where failures are noisy, cache behaviour is a mystery, and the workflow file has a comment in it that says do not touch. We onboard a small number of studios at a time, and early reports are read by the people who wrote the tool.