Skip to main content

Developer CLI framework with YAML-based command definitions

Project description

hogli

A developer CLI framework for defining commands in YAML. Think of it as "GitHub Actions for your local dev environment" - declarative, composable, and easy to maintain.

Why hogli?

  • Declarative: Define commands in YAML instead of scattered shell scripts
  • Composable: Chain commands together with steps
  • Discoverable: Auto-generated help with categories, --help on every command
  • Extensible: Add complex commands in Python when YAML isn't enough

Installation

pip install hogli

Quick Start

Create hogli.yaml in your repo root:

config:
  scripts_dir: bin # Where bin_script looks for scripts (default: bin/)

metadata:
  categories:
    - key: dev
      title: Development
    - key: test
      title: Testing

dev:
  dev:start:
    cmd: docker compose up -d && npm run dev
    description: Start development environment

  dev:reset:
    steps:
      - dev:stop
      - dev:clean
      - dev:start
    description: Full environment reset

test:
  test:unit:
    cmd: pytest tests/unit
    description: Run unit tests

Run commands:

hogli dev:start
hogli --help        # Shows all commands grouped by category
hogli dev:start -h  # Help for specific command

Command Types

Shell commands (cmd)

Run shell commands directly. Supports shell operators (&&, ||, |):

build:
  cmd: npm run build && npm run test
  description: Build and test

Script delegation (bin_script)

Delegate to scripts in your scripts_dir:

deploy:
  bin_script: deploy.sh
  description: Deploy to production

Composite commands (steps)

Chain multiple hogli commands:

release:
  steps:
    - test:all
    - build
    - deploy
  description: Full release pipeline

Steps can also include inline commands:

setup:
  steps:
    - name: Install deps
      cmd: npm install
    - name: Build
      cmd: npm run build
    - test:unit

Command Options

my:command:
  cmd: echo "hello"
  description: Short description for --help
  destructive: true # Prompts for confirmation before running
  hidden: true # Hides from --help (still runnable)

Python Commands

For complex logic, define plain Click commands and reference them from hogli.yaml with click: module.path:attribute.

Click command modules are lazy-loaded. Top-level hogli --help uses the manifest description without importing Python command modules; hogli <command> --help and command execution import the target on demand.

The Click command name must match the manifest key — drift surfaces as a ClickException on resolution. The framework follows Click's recommendation that lazy loading be paired with a test that runs --help on each subcommand; PostHog's test suite parametrizes over every click: entry in hogli.yaml to do exactly that.

Mark commands hidden via hidden: true in hogli.yaml. Don't use @click.command(hidden=True) — the manifest is the single source of truth.

Minimal: one importable package

your-repo/
├── hogli.yaml
└── tools/
    └── hogli_commands/
        ├── __init__.py
        └── db.py
# hogli.yaml
config:
  commands_dir: tools/hogli_commands

db:
  db:migrate:
    click: hogli_commands.db:db_migrate
    description: Run database migrations
# tools/hogli_commands/db.py
import click

@click.command(name="db:migrate")
@click.option("--dry-run", is_flag=True, help="Show SQL without executing")
def db_migrate(dry_run: bool) -> None:
    """Run database migrations."""
    if dry_run:
        click.echo("Would run migrations...")
    else:
        # Your migration logic here
        pass

commands_dir is optional and explicit: hogli only uses it when configured. It must be a relative path to an existing directory. hogli puts that directory's parent on sys.path, so the directory should be an importable package or module tree. It must not be named hogli, since that shadows the installed framework.

Full: project package with submodules

For a larger command surface, keep the distribution/project directory separate from the import package:

your-repo/
├── hogli.yaml
└── tools/
    └── hogli-commands/
        ├── pyproject.toml
        └── hogli_commands/    # underscored: this is the import name
            ├── __init__.py
            ├── build.py
            ├── db.py
            └── deploy.py
# hogli.yaml
config:
  commands_dir: tools/hogli-commands/hogli_commands

build:
  build:
    click: hogli_commands.build:build
    description: Run build pipelines

The dashed outer dir + underscored inner package follows PEP 8 (dashed project name, underscored import name). This is the layout PostHog itself uses.

Extension Hooks

Extensions can inject behavior at three framework call sites without forking. Register hooks from modules listed in config.boot_modules; those modules are imported once at startup, before command dispatch. Keep boot modules cheap to import and move heavy work inside hook functions. Exceptions raised by hooks are swallowed, so one extension can't break another.

config:
  commands_dir: tools/hogli_commands
  boot_modules:
    - hogli_commands.boot

Prechecks

Run validation before a command executes, keyed by type: in a prechecks: entry in hogli.yaml. Return False to abort, True/None to continue.

from hogli.hooks import register_precheck

def check_migrations(check: dict, yes: bool) -> bool | None:
    # inspect check config, prompt user, decide
    return None

register_precheck("migrations", check_migrations)
dev:start:
  cmd: docker compose up -d
  prechecks:
    - type: migrations

Telemetry properties

Inject extra key/value pairs into the command_completed telemetry event. Receives the invoked command name.

from hogli.hooks import register_telemetry_properties

def env_props(command: str | None) -> dict[str, object]:
    return {"in_my_env": True}

register_telemetry_properties(env_props)

Post-command hooks

Run after every command completes, regardless of success. Good for contextual hints, cleanup, or notifications.

from hogli.hooks import register_post_command_hook

def maybe_show_hint(command: str | None, exit_code: int) -> None:
    if exit_code == 0:
        ...

register_post_command_hook(maybe_show_hint)

Configuration Reference

config:
  commands_dir: path/to/commands # Optional local Python command package
  boot_modules:
    - package.boot # Optional eager hook registration modules
  scripts_dir: scripts # For bin_script resolution (default: bin/)

metadata:
  categories:
    - key: dev
      title: Development Commands
    - key: test
      title: Test Commands

Built-in Commands

  • hogli quickstart - Getting started guide
  • hogli meta:check - Validate manifest against bin scripts (for CI)
  • hogli meta:concepts - Show infrastructure concepts (if defined)

Requirements

  • Python 3.10+
  • click
  • pyyaml

Releasing

Releases are published to PyPI via .github/workflows/publish-hogli.yml, triggered by pushing a hogli-v* tag from master.

  1. Bump version in tools/hogli/pyproject.toml and merge to master.

  2. From master, tag and push:

    git tag hogli-v0.1.1
    git push origin hogli-v0.1.1
    

The workflow verifies the tag matches the pyproject.toml version, builds the sdist and wheel with uv build, smoke-tests the wheel in a fresh venv, publishes via PyPI trusted publishing (OIDC) — no API tokens — and creates a GitHub Release with auto-generated notes from the commits since the previous tag.

To re-trigger after a failed publish, dispatch the workflow against the existing tag — no need to retag:

gh workflow run publish-hogli.yml --ref hogli-v0.1.1

The publish job is guarded by if: startsWith(github.ref, 'refs/tags/hogli-v'), so dispatches from a branch are no-ops.

License

MIT

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

hogli-0.1.0.tar.gz (34.1 kB view details)

Uploaded Source

Built Distribution

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

hogli-0.1.0-py3-none-any.whl (26.2 kB view details)

Uploaded Python 3

File details

Details for the file hogli-0.1.0.tar.gz.

File metadata

  • Download URL: hogli-0.1.0.tar.gz
  • Upload date:
  • Size: 34.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for hogli-0.1.0.tar.gz
Algorithm Hash digest
SHA256 4f87f1a99189a99d0a9d4e651557f16fbb799ff36a7d1c5380f2f0d70a293b72
MD5 1b71799bc13c9900041031a211a08082
BLAKE2b-256 069851aab015762b2f4a9dfd45890609f35b338b6a5309123de7076e514ad555

See more details on using hashes here.

File details

Details for the file hogli-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: hogli-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 26.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for hogli-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d8284ad47f432cb6bc8a1326e68d9d3169c40ad4f32138f756541a5be69d0f2b
MD5 9f9cec6bd45c893d48791d37445415c3
BLAKE2b-256 1d095fd50c6843ba67d3abf3f7cbeb9e9c01d2ba34debb2675d9bcfb4596bed1

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