Skip to content

harmont-dev/harmont-cli

Repository files navigation

Repository layout. This repo is a Cargo workspace. crates/hm is the CLI binary. crates/hm-plugin-protocol and crates/hm-plugin-sdk are the public API for writing third-party plugins. crates/hm-plugin-* are the bundled plugins (Docker executor, output formatters, cloud client). examples/ contains sample pipeline repos you can hm run --local against.

This repo is a mirror of the cli/ and examples/ directories of the private Harmont monorepo. Open issues and PRs against this repo; maintainers will land them upstream and a CI mirror sync replays the result back here.

harmont-cli

license

Command-line client for the Harmont CI platform. Run CI pipelines on your own machine, in Docker, from a Python pipeline definition checked into your repo.

Pipelines are written with the companion harmont-py DSL.

Install

harmont-cli is not yet published to crates.io. Install from source:

git clone https://github.com/harmont-dev/harmont-cli
cd harmont-cli
cargo build --release
install -m 0755 target/release/hm /usr/local/bin/hm   # or any directory on $PATH

Verify:

hm --version

Requirements

hm run --local shells out to Docker and to Python:

  • Docker — the local executor boots a fresh container per chain.
  • Python 3.11+ — used to render the pipeline definition to JSON.
  • harmont-py — the Python package that defines the pipeline DSL. Not yet on PyPI; install from git:
git clone https://github.com/harmont-dev/harmont-py
pip install -e ./harmont-py

Quickstart

1. Write a pipeline

Pipelines live in .harmont/<slug>.py inside your repo. Each file uses the @hm.pipeline("slug") decorator to register one or more named pipelines. Save the following as .harmont/hello.py:

import harmont as hm


@hm.pipeline("hello")
def hello() -> hm.Step:
    return (
        hm.sh("echo 'hello from harmont'", label="hello")
          .sh("uname -a", label="env")
    )

The DSL is small:

  • hm.sh(cmd, label=...) — start a chain with one shell command (shorthand for hm.scratch().sh(...)).
  • .sh(cmd, label=..., cwd=...) — chain another command. Chained .sh calls share filesystem state inside the same container. cwd="path" prepends cd <path> && to the command.
  • .fork(label=...) — branch into parallel work from a shared base.
  • hm.wait() — explicit synchronization barrier.
  • @hm.target() — reusable, memoized building block; compose into pipelines via fixture-style typed params (Target[T], Annotated[Step, BaseImage("...")]).

A two-branch variant:

@hm.pipeline("ci")
def ci() -> hm.Step:
    setup = hm.sh(
        "apt-get update && apt-get install -y curl",
        label="apt",
    )
    fetch = setup.fork(label="branch-a").sh(
        "curl -fsSL https://example.com",
        label="fetch",
    )
    work = setup.fork(label="branch-b").sh(
        "echo independent work",
        label="other",
    )
    return hm.pipeline(fetch, work, default_image="ubuntu:24.04")

For larger pipelines, compose with @hm.target and typed fixture params:

from typing import Annotated


@hm.target()
def apt_base(base: Annotated[hm.Step, hm.BaseImage("ubuntu:24.04")]) -> hm.Step:
    return base.sh("apt-get update && apt-get install -y curl", label="apt")


@hm.target()
def smoke(apt_base: hm.Target[hm.Step]) -> hm.Step:
    return apt_base.sh("curl -fsSL https://example.com", label="smoke")


@hm.pipeline("ci")
def ci(smoke: hm.Target[hm.Step]) -> hm.Step:
    return smoke

For the full DSL surface (cache policies, matrix axes, soft-fail, timeouts), see the upstream harmont-py repo.

2. Run it

From the repo root:

hm run hello --local

The CLI walks .harmont/*.py, resolves the hello slug, renders the pipeline to JSON, and schedules the chains across Docker containers. Each chain inherits state from its parent; forks run in parallel up to --parallelism N (defaults to the host's available parallelism).

If the repo declares only one pipeline, the slug is optional:

hm run --local

3. Useful flags

hm run --local --parallelism 4         # cap concurrent chains
hm run --local --env FOO=bar           # inject env vars
hm run --local --dir path/to/source    # run against a different source root
hm run --help                          # full flag reference

Cloud

hm cloud <verb> talks to the hosted Harmont API at api.harmont.dev. Every cloud verb is delivered by the embedded hm-plugin-cloud WASM plugin (no separate install step):

hm cloud login                  # browser-loopback OAuth (or --paste to
                                # paste a token directly)
hm cloud logout
hm cloud whoami                 # who am I + active org
hm cloud org list               # orgs you belong to
hm cloud org use <slug>         # set the active org (persisted)
hm cloud pipeline list
hm cloud build list             # builds for the active org
hm cloud build show <id>
hm cloud build watch <id>       # poll until terminal
hm cloud job show <id>
hm cloud billing show
hm cloud run [--plan-file PATH] # submit a pre-rendered plan JSON
                                # (defaults to .harmont/plan.json)

Tokens are stored in the OS keyring. The active org slug is persisted per-user under ~/.config/harmont/state/cloud.kv. Source-archive upload for cloud run is plan-5 work — pre-render your pipeline to .harmont/plan.json first.

Build from source

git clone https://github.com/harmont-dev/harmont-cli
cd harmont-cli
cargo build
cargo test                          # Docker-dependent tests in `local_*` need a running daemon
cargo clippy --all-targets -- -D warnings
cargo fmt --check

The OpenAPI client is generated at build time from the vendored openapi.json via progenitor. The snapshot ships with the crate.

See also

  • harmont-py — the Python DSL used to define pipelines that this CLI runs.

License

Dual-licensed under either of

at your option.

Plugin authoring

hm is plugin-driven via Extism. To write a plugin:

cargo new --lib my-plugin
cd my-plugin
cargo add --git https://github.com/harmont-dev/harmont-cli hm-plugin-sdk

Implement one of StepExecutor, SubcommandPlugin, LifecycleHook, or OutputFormatter, declare a PluginManifest, and call register_plugin!(...). Build with:

cargo build --target wasm32-wasip1 --release

The output .wasm can be installed with:

hm plugin install ./target/wasm32-wasip1/release/my_plugin.wasm

See crates/hm-fixtures/src/bin/ for minimal working examples.

Output formatter

Implement OutputFormatter::on_event to render each BuildEvent. Plugins emit bytes via host::write_stdout or host::write_stderr. Built-in formatters: human (default), json. Select with hm run --format <name>.

About

Command-line client for the Harmont CI platform

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages