Package Management

Note: Publishing packages requires the registry backend to be deployed. Installing public packages works immediately.

HORUS provides a comprehensive package management system for sharing and discovering robotics components. Create reusable nodes, message types, and algorithms that the community can use.

Overview

The package system allows you to:

  • Install packages from multiple sources (HORUS registry, crates.io, PyPI)
  • Publish your work for others to use
  • Manage dependencies automatically
  • Version control with semantic versioning
  • Search and discover community packages

Package Sources

HORUS supports installing packages from multiple sources:

SourceDescriptionExample
HORUS RegistryCurated robotics packageshorus install pid-controller
crates.ioRust ecosystem packageshorus install serde
PyPIPython ecosystem packageshorus install numpy
GitGit repositories (via horus.toml)See Configuration
Local PathLocal filesystem (via horus.toml)See Configuration

Quick Start

Installing a Package

# Install from HORUS registry
horus install pid-controller

# Install from crates.io (auto-detected)
horus install serde
horus install tokio

# Install from PyPI (auto-detected)
horus install numpy
horus install opencv-python

# Install specific version
horus install serde@1.0.200

# Install globally — this is the default; there is no prompt
horus install sensor-drivers

# Install into a specific workspace instead
horus install sensor-drivers -t my-workspace

Automatic Source Detection

HORUS automatically detects the package source:

  1. First checks HORUS registry
  2. Then checks both PyPI and crates.io
  3. If found in multiple sources, prompts you to choose:
Package 'package_name' found in BOTH PyPI and crates.io

Which package source do you want to use?
  [1] [PYTHON] PyPI (Python package)
  [2] [RUST] crates.io (Rust binary)
  [3] [FAIL] Cancel installation

Choice [1-3]:

System Package Detection

If a package is already installed system-wide, HORUS offers to reuse it:

crates.io ripgrep found in system (version: 14.0.0)

What would you like to do?
  [1] Use system package (create reference)
  [2] Install to HORUS (isolated environment)
  [3] Cancel installation

Choice [1-3]:

What happens during installation:

  1. Detects package source (HORUS registry, crates.io, or PyPI)
  2. Downloads package from the appropriate source
  3. Resolves dependencies automatically
  4. Caches locally in ~/.cache/horus/ or .horus/packages/
  5. Makes package available for use

Using an Installed Package

// In your main.rs or any file
use pid_controller::PIDNode;
use horus::prelude::*;

fn main() {
    let mut scheduler = Scheduler::new();

    // Use the installed package
    let pid = PIDNode::new(1.0, 0.1, 0.01);
    scheduler.add(pid).order(5).done();

    scheduler.run().expect("Scheduler failed");
}

Publishing Your Package

# 1. Authenticate first (one-time)
horus auth login

# 2. Navigate to your project
cd my-awesome-controller

# 3. Publish
horus publish

Package Locations

Local Packages

Project-local (only with -t <workspace-name>):

my_project/
── .horus/
   ── packages/
       ── pid-controller/            # HORUS registry
       ── serde/                     # crates.io
       ── numpy/                     # PyPI
── src/
    ── main.rs

Project-local package directories are not version-suffixed — only the global cache uses <name>@<version> names.

Why use local:

  • Different projects can use different versions
  • Clean separation per project
  • Easy to delete with project

Global Packages

System-wide (the default — horus install is global unless you pass -t <workspace-name>):

~/.horus/
── cache/
    ── pid-controller@1.0.0/         # HORUS registry
    ── cratesio_serde@1.0.200/       # crates.io
    ── pypi_numpy@1.24.0/            # PyPI packages

Naming conventions by source:

SourceDirectory FormatExample
HORUS Registry<name>@<version>/pid-controller@1.0.0/
crates.iocratesio_<name>@<version>/cratesio_serde@1.0.200/
PyPIpypi_<name>@<version>/pypi_numpy@1.24.0/

Git dependencies are not cached by HORUS. They are written straight into the generated .horus/Cargo.toml as git = "<url>" (with any branch/tag/rev), or into .horus/pyproject.toml as <name> @ git+<url>, and fetched by the underlying toolchain into its own cache — Cargo clones into ~/.cargo/git, pip handles Python.

Why use global:

  • Share common packages across all projects
  • Save disk space (one copy for everything)
  • Faster install after first download

Priority Order & Smart Dependency Resolution

When resolving packages, HORUS checks in this order:

1. Project-local .horus/packages/ (highest priority)

  • Checked first, ALWAYS wins
  • Can be symlink to global OR real directory
  • Enables local override of broken global packages

2. Global cache ~/.cache/horus/

  • Only checked if not found locally
  • Shared across all projects
  • Version-specific directories (e.g., cratesio_serde@1.0.228/)

Smart Installation Behavior:

When you run horus install, HORUS automatically chooses the best strategy:

# Default behavior (no flags)
horus install serde

# If package exists in global cache:
#    Install to global cache
#    Create symlink: .horus/packages/serde -> ~/.cache/horus/cratesio_serde@1.0.228/
#    Disk efficient!

# If package NOT in global cache:
#    Install directly to .horus/packages/serde/
#    No symlink, real directory
#    Isolated from global!

Override Broken Global Cache:

Local packages always win, so you can override corrupted global packages:

# Scenario: Global cache has broken serde@1.0.228
~/.cache/horus/cratesio_serde@1.0.228/  # Corrupted

# Fix: Install working version locally
rm .horus/packages/serde  # Remove symlink to broken global
horus install serde@1.0.150  # Install working version locally

# Result:
.horus/packages/serde/  # Real directory, not symlink
# horus run will use this, ignoring broken global!

Benefits:

  • Local override - Bypass broken global packages
  • Version isolation - Different projects can use different versions
  • Disk efficient - Shares global cache when possible
  • Zero config - Works automatically

See Environment Management for more details on how this solves dependency hell.

Package Commands

horus install

Install packages from multiple sources (HORUS registry, crates.io, PyPI).

Usage:

horus install <package> [OPTIONS]

Options:

  • <package>@<version> - Install a specific version (e.g. horus install serde@1.0.200); default is latest
  • --plugin - Install as a CLI plugin
  • -t, --target <NAME> - Target workspace/project name
  • --json - Output as JSON

Examples:

# From HORUS registry
horus install pid-controller
horus install motion-planner@2.0.1

# From crates.io (auto-detected)
horus install serde
horus install tokio@1.35.0
horus install clap

# From PyPI (auto-detected)
horus install numpy
horus install opencv-python@4.8.0
horus install torch

# Global installation: run outside a workspace and pick
# Global is the default; use -t <workspace> to install locally instead
horus install serde

# Install to specific workspace
horus install pid-controller -t my-project

Installing from crates.io

For binary crates HORUS shells out to cargo install --root; for library crates inside a workspace it runs cargo add to add the dependency to Cargo.toml and records it in .horus/packages/<name>.crates-io.json:

horus install ripgrep

Output:

Installing ripgrep from crates.io...
  Compiling ripgrep...
  Installing with cargo...

Package installed: ripgrep@14.0.0
Location: ~/.cache/horus/cratesio_ripgrep@14.0.0/

Requirements:

  • Rust toolchain must be installed (rustup)
  • cargo must be available in PATH

Installing from PyPI

When installing Python packages from PyPI, HORUS uses pip install --target to isolate packages:

horus install numpy

Output:

Installing numpy from PyPI...
  Downloading numpy-1.24.0...
  Installing to .horus/packages/numpy/

Package installed: numpy@1.24.0
Location: .horus/packages/numpy/

Requirements:

  • Python 3.x must be installed
  • pip must be available in PATH

Using Python Packages

After installing a PyPI package, use it in your Python nodes:

# In your Python node
import sys
sys.path.insert(0, '.horus/packages/numpy')

import numpy as np
# Or HORUS automatically adds package paths when using horus run

When using horus run, Python package paths are automatically configured.

Installing from the HORUS Registry

Packages that resolve to the HORUS registry are downloaded, extracted into .horus/packages/, and built with their dependencies:

Output:

Installing pid-controller@1.2.0...
 Downloaded (245 KB)
 Extracted to .horus/packages/pid-controller/
 Installed dependencies: control-utils@1.0.0
 Build successful

Package installed: pid-controller@1.2.0
Location: .horus/packages/pid-controller/

Usage:
  use pid_controller::PIDNode;

horus uninstall

Uninstall a standalone package or plugin.

Usage:

horus uninstall <package> [--purge]

Options:

  • --purge - Also purge cached files

Examples:

# Uninstall a package
horus uninstall motion-planner

# Uninstall and purge its cached files
horus uninstall motion-planner --purge

To remove a dependency entry from horus.toml (rather than uninstall an installed package), use horus remove <name>, which also accepts --purge to clean unused packages from the cache.

horus list

List installed packages. To search the registry, use horus search <QUERY> (documented below) instead — horus list takes no query argument.

Usage:

horus list [OPTIONS]

Options:

  • -g, --global - List global cache packages
  • -a, --all - List all (local + global)

List Local Packages:

horus list

Output:

Local packages:
  pid-controller 1.2.0
  motion-planner 2.0.1
  sensor-drivers 1.5.0

List Global Cache:

horus list -g

Search the registry for packages and plugins.

Usage:

horus search <QUERY>

Options:

  • -c, --category <CATEGORY> - Filter by category (camera, lidar, imu, motor, servo, bus, gps, simulation, cli)
  • --json - Output as JSON

Example:

# Search the registry by keyword
horus search sensor

Output:

Found 3 plugins matching 'sensor':

  sensor-fusion v2.1.0 [REGISTRY]
    Kalman filter fusion
  sensor-drivers v1.5.0 [REGISTRY] [prebuilt]
    LIDAR/IMU/camera drivers
  sensor-calibration v1.0.0 [CRATES.IO]
    Calibration tools

Each result carries its source — REGISTRY, CRATES.IO, GIT or LOCAL — and [prebuilt] when a binary is available instead of a source build. Narrow the search with -c <category>.

horus update

Update installed packages to their latest versions.

Usage:

horus update [PACKAGE] [OPTIONS]

Options:

  • -g, --global - Update global cache packages
  • --dry-run - Show what would be updated without making changes

Examples:

# Update all local packages
horus update

# Update a specific package
horus update pid-controller

# Update global packages
horus update -g

# Preview updates without applying
horus update --dry-run

horus unpublish

Remove a package version from the registry (irreversible!).

Usage:

horus unpublish <package>@<version> [OPTIONS]

Options:

  • -y, --yes - Skip confirmation prompt

Examples:

# Unpublish a specific version
horus unpublish my-package@1.0.0

# Skip confirmation prompt
horus unpublish my-package@1.0.0 -y

Output:

Unpublishing my-package v1.0.0...

Warning: This action is IRREVERSIBLE and will:
  • Delete my-package v1.0.0 from the registry
  • Make this version unavailable for download
  • Cannot be undone

Type the package name 'my-package' to confirm: my-package

 Successfully unpublished my-package v1.0.0
   The package is no longer available on the registry

Note: Detailed package information can be viewed on the registry web interface at https://api.horusrobotics.dev

Authentication (for Publishing)

Note: Registry publishing and private resources require the registry backend to be deployed. GitHub authentication is fully functional.

Authentication is required for publishing packages and accessing private registry resources. HORUS uses GitHub OAuth for interactive login and API keys for automated systems.

Authentication Overview

Authentication methods:

  • GitHub OAuth - Interactive login via browser (recommended for development)
  • API Keys - Long-lived tokens for CI/CD and automation
  • Credentials file - auth.json written from a secret store, for containers and CI

What requires authentication:

  • Publishing packages (horus publish)
  • Accessing private packages
  • Managing your published packages

What doesn't require authentication:

  • Installing public packages (horus install)
  • Searching registry (horus search)
  • Using installed packages

Quick Authentication Setup

Interactive Login:

# Login with GitHub
horus auth login

What happens:

  1. Opens browser to GitHub OAuth page
  2. You authorize HORUS Registry
  3. Token saved to ~/.config/horus/auth.json
  4. Ready to publish!

Check Authentication:

# Verify you're logged in
horus auth whoami

Logout:

# Remove credentials
horus auth logout

GitHub OAuth Login

First-Time Setup:

# Run login command
horus auth login

Output:

Opening GitHub OAuth page in browser...

If browser doesn't open automatically, visit:
  https://github.com/login/oauth/authorize?client_id=...

Waiting for authorization...

In browser:

  1. See "Authorize HORUS Registry" page
  2. Review permissions requested:
    • Read user profile
    • Read email address
  3. Click "Authorize horus-registry"
  4. Redirected to success page

Back in terminal:

Authorization successful!
Token saved to ~/.config/horus/auth.json

Authenticated as: your-username
Email: you@example.com

You can now publish packages with:
  horus publish

What Gets Stored:

Credentials file: ~/.config/horus/auth.json

The location is platform-dependent:

  • Linux: ~/.config/horus/auth.json (or $XDG_CONFIG_HOME/horus/auth.json when set)
  • macOS: ~/Library/Application Support/horus/auth.json
  • Windows: %APPDATA%\horus\auth.json
{
  "api_key": "horus_key_abc123def456...",
  "registry_url": "https://api.horusrobotics.dev",
  "github_username": "your-username"
}

Security:

  • File permissions: 0600 (read/write owner only)
  • Revocable via GitHub settings or registry web interface

Token Permissions:

Required scopes:

  • read:user - Read your GitHub profile
  • user:email - Read your email address

Not requested:

  • No write access to repositories
  • No access to private repositories
  • No access to organizations

Revoking Access:

Via GitHub:

  1. Go to https://github.com/settings/applications
  2. Find "HORUS Registry" under "Authorized OAuth Apps"
  3. Click "Revoke"

Via CLI:

horus auth logout

API Keys (for CI/CD)

API keys are long-lived tokens for automated systems like CI/CD pipelines.

Generating API Keys:

# Interactive generation
horus auth api-key

The key itself is generated by the registry web dashboard, not by the CLI — horus auth api-key walks you to the dashboard, then reads the key back and saves it to auth.json.

Interactive prompts:

Generating API key...

Note: This requires you to be logged in via GitHub first.
  If you haven't logged in yet, run: horus auth login

After logging in via GitHub, the registry will show an API key generation page.
Visit: https://api.horusrobotics.dev/dashboard/keys

Generate a key with:
  Name: CI/CD Pipeline
  Environment: production

Enter the generated API key: horus_key_abc123def456ghi789jkl012mno345pqr678stu901

API key saved successfully!
  Registry: https://api.horusrobotics.dev
  Config saved to: /home/you/.config/horus/auth.json

Tip: You can now publish packages with: horus publish

The key must start with horus_key_ or it is rejected.

With flags:

horus auth api-key \
  --name "GitHub Actions" \
  --environment "production"

Using API Keys:

HORUS reads the key from auth.json and nowhere else — there is no HORUS_API_KEY environment variable. For non-interactive environments, write the credentials file from your secret store at the start of the job:

mkdir -p ~/.config/horus
printf '%s' "$HORUS_AUTH_JSON" > ~/.config/horus/auth.json
chmod 600 ~/.config/horus/auth.json

Managing API Keys:

# List all API keys
horus auth keys list

# Revoke a specific key
horus auth keys revoke horus_key_abc123...

CI/CD Integration

GitHub Actions:

Workflow file (.github/workflows/publish.yml):

name: Publish to HORUS Registry

on:
  push:
    tags:
      - 'v*'

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Install Rust
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable

      - name: Install HORUS
        run: |
          git clone https://github.com/softmata/horus.git /tmp/horus
          cd /tmp/horus && ./install.sh

      - name: Publish Package
        env:
          HORUS_AUTH_JSON: ${{ secrets.HORUS_AUTH_JSON }}
        run: |
          mkdir -p ~/.config/horus
          printf '%s' "$HORUS_AUTH_JSON" > ~/.config/horus/auth.json
          chmod 600 ~/.config/horus/auth.json
          horus publish

Setup:

  1. Generate API key: horus auth api-key --name "GitHub Actions"
  2. Copy the resulting credentials file: cat ~/.config/horus/auth.json
  3. Add to GitHub secrets:
    • Go to repository Settings → Secrets → Actions
    • New repository secret: HORUS_AUTH_JSON
    • Paste the file contents
  4. Push tag: git tag v1.0.0 && git push origin v1.0.0

GitLab CI:

.gitlab-ci.yml:

publish:
  stage: deploy
  image: rust:latest
  before_script:
    - git clone https://github.com/softmata/horus.git /tmp/horus
    - cd /tmp/horus && ./install.sh
    - mkdir -p ~/.config/horus
    - cp "$HORUS_AUTH_JSON" ~/.config/horus/auth.json
    - chmod 600 ~/.config/horus/auth.json
  script:
    - horus publish
  only:
    - tags

Setup:

  1. Generate key: horus auth api-key --name "GitLab CI"
  2. Add to GitLab:
    • Settings → CI/CD → Variables
    • Key: HORUS_AUTH_JSON
    • Type: File
    • Value: the contents of ~/.config/horus/auth.json

Docker:

Dockerfile:

FROM rust:1.70

# Install HORUS
RUN git clone https://github.com/softmata/horus.git /tmp/horus \
    && cd /tmp/horus && ./install.sh \
    && rm -rf /tmp/horus

# Copy project
COPY . /app
WORKDIR /app

# Build and publish. The credentials file is mounted as a build secret so it
# never lands in an image layer.
RUN --mount=type=secret,id=horus_auth \
    mkdir -p ~/.config/horus \
    && cp /run/secrets/horus_auth ~/.config/horus/auth.json \
    && chmod 600 ~/.config/horus/auth.json \
    && horus publish

Build:

docker build \
  --secret id=horus_auth,src=$HOME/.config/horus/auth.json \
  -t my-horus-app .

Jenkins:

Jenkinsfile:

pipeline {
    agent any

    stages {
        stage('Setup') {
            steps {
                sh 'git clone https://github.com/softmata/horus.git /tmp/horus && cd /tmp/horus && ./install.sh'
            }
        }

        stage('Build') {
            steps {
                sh 'horus build --release'
            }
        }

        stage('Publish') {
            when {
                buildingTag()
            }
            steps {
                withCredentials([file(credentialsId: 'horus-auth-json', variable: 'HORUS_AUTH_JSON')]) {
                    sh '''
                        mkdir -p ~/.config/horus
                        cp "$HORUS_AUTH_JSON" ~/.config/horus/auth.json
                        chmod 600 ~/.config/horus/auth.json
                        horus publish
                    '''
                }
            }
        }
    }
}

Setup:

  1. Generate key: horus auth api-key --name "Jenkins"
  2. Jenkins monitor → Manage Jenkins → Credentials
  3. Add a Secret file credential with ID horus-auth-json, uploading your ~/.config/horus/auth.json

Environment Variables

Available Variables:

HORUS_REGISTRY_URL

  • Override registry URL for self-hosted registries
  • Only needed if running your own registry instance

Note: There is deliberately no HORUS_API_KEY environment variable — nothing in HORUS reads one. Credentials always come from auth.json. For non-interactive environments, write that file from your secret store (see API Keys).

Example usage:

export HORUS_REGISTRY_URL=https://registry.company.internal

horus publish

Security Best Practices

Token Security - Do's:

  • Use API keys for CI/CD (not OAuth tokens)
  • Rotate keys every 90 days
  • Use different keys for different environments
  • Store keys in CI/CD secrets management
  • Revoke unused keys immediately
  • Write auth.json from your secret store in CI (never pass keys as command-line flags)

Token Security - Don'ts:

  • Never commit credentials to git
  • Never share API keys between team members
  • Never log API keys
  • Never use same key for prod and dev
  • Never hardcode keys in source code

Credentials File Security:

File permissions:

# Verify permissions
ls -la ~/.config/horus/auth.json
# Should show: -rw------- (600)

# Fix if needed
chmod 600 ~/.config/horus/auth.json

Backup:

# Backup credentials (encrypted)
gpg -c ~/.config/horus/auth.json
# Creates ~/.config/horus/auth.json.gpg

# Restore
gpg -d ~/.config/horus/auth.json.gpg > ~/.config/horus/auth.json
chmod 600 ~/.config/horus/auth.json

Key Rotation:

Regular rotation schedule:

# 1. Generate new key
horus auth api-key --name "Production 2025-Q4"

# 2. Update CI/CD secrets with the new ~/.config/horus/auth.json
# (Do this manually in your CI/CD platform)

# 3. Test new key
horus auth whoami

# 4. Revoke old key
horus auth keys revoke horus_key_old_key_id...

Authentication Troubleshooting

Authentication Failed:

Error:

Error: Authentication required
  Run: horus auth login

Solutions:

# Check current status
horus auth whoami

# Re-authenticate
horus auth login

# Verify credentials file exists
ls -la ~/.config/horus/auth.json

Token Expired:

Error:

Error: Token expired
  Please re-authenticate

Solutions:

# Re-login (auto-refreshes token)
horus auth login

# Or use API key instead
horus auth api-key

Invalid API Key:

Error:

Error: Invalid API key
  Status: 401 Unauthorized

Causes:

  • Key was revoked
  • Key expired
  • Typo in key value
  • Wrong registry URL

Solutions:

# Verify key format in the credentials file
grep api_key ~/.config/horus/auth.json
# Should start with: horus_key_

# Check key status via registry web interface
# Visit https://api.horusrobotics.dev

# Generate new key
horus auth api-key

Permission Denied:

Error:

Error: Permission denied
  You don't have permission to publish to this package

Causes:

  • Package owned by another user
  • Not logged in
  • Insufficient permissions

Solutions:

# Verify authentication
horus auth whoami

# Check package ownership via registry web interface
# Visit https://api.horusrobotics.dev

# For package ownership transfer, contact registry support

GitHub OAuth Failed:

Error:

Error: OAuth authorization failed
  Could not complete GitHub authentication

Solutions:

# Re-login
horus auth login

# Check browser popup blockers

# Verify GitHub account
curl https://api.github.com/user \
  -H "Authorization: Bearer <your-github-token>"

Advanced Authentication Topics

Self-Hosted Registry:

Configure custom registry:

# Set registry URL
export HORUS_REGISTRY_URL=https://registry.company.internal

# Authenticate
horus auth login

# Use as normal
horus publish

Multiple Accounts:

Switch between accounts:

# Save current credentials
mv ~/.config/horus/auth.json ~/.config/horus/auth.json.account1

# Login with second account
horus auth login

# Switch back
mv ~/.config/horus/auth.json ~/.config/horus/auth.json.account2
mv ~/.config/horus/auth.json.account1 ~/.config/horus/auth.json

Publishing Packages

Prerequisites

Before publishing:

  1. Authenticate with the registry (see Authentication section above):

    horus auth login
    
  2. Complete horus.toml metadata:

    [package]
    name = "my-awesome-package"
    version = "1.0.0"
    description = "Brief description of your package"
    license = "MIT"
    

    During publishing, HORUS will interactively prompt you for optional metadata like categories, package type, documentation URL, and source repository.

  3. Test your package locally:

    horus run --release
    

horus publish

Usage:

horus publish [OPTIONS]

Options:

  • --dry-run - Validate the package without actually publishing

Publishing Workflow

# 1. Navigate to package directory
cd my-awesome-package

# 2. Verify everything builds
horus build --release

# 3. Publish (or dry-run first)
horus publish --dry-run
horus publish

Output:

 Detected horus.toml manifest
 Publishing my-awesome-package v1.0.0...
 Uploaded to registry

Published: my-awesome-package@1.0.0
  View at: https://api.horusrobotics.dev/packages/my-awesome-package

Before the upload, HORUS interactively prompts for optional metadata (categories, package type, documentation, source repository) and attaches your answers to the uploaded package. The prompts are skipped with --dry-run or when stdin is not a TTY.

Before the upload, you'll be prompted to add optional metadata to help users discover and use your package:

Documentation Options

External Documentation URL: Link to your hosted documentation website (e.g., GitHub Pages, ReadTheDocs, custom site):

Documentation
   Add documentation? (y/n): y

   Documentation options:
     1. External URL - Link to online documentation
     2. Local /docs - Bundle markdown files in a /docs folder

   Choose option (1/2/skip): 1
   Enter documentation URL: https://my-package-docs.example.com
    Documentation URL: https://my-package-docs.example.com

Local Documentation (Bundled Markdown): Include markdown files directly in your package for built-in documentation viewing:

Documentation
    Found local /docs folder with markdown files
   Add documentation? (y/n): y

   Documentation options:
     1. External URL - Link to online documentation
     2. Local /docs - Bundle markdown files in a /docs folder

   [i] Your /docs folder should contain .md files organized as:
      /docs/README.md          (main documentation)
      /docs/getting-started.md (guides)
      /docs/api.md             (API reference)

   Choose option (1/2/skip): 2
    Will bundle local /docs folder with package

Local Docs Structure:

my-package/
── docs/
   ── README.md           # Main documentation page
   ── getting-started.md  # Installation and setup guide
   ── api.md              # API reference
   ── examples.md         # Usage examples
── src/
   ── lib.rs
── horus.toml

Benefits of Local Docs:

  • Users can view docs directly from the registry
  • Works offline
  • Version-specific documentation
  • Automatic rendering with syntax highlighting
  • No external hosting required

Source Repository

Link to your GitHub, GitLab, or other repository:

Source Repository
    Auto-detected: https://github.com/username/my-package
   Add source repository? (y/n): y
   Use detected URL? (y/n): y
    Source repository: https://github.com/username/my-package

Manual Entry: If auto-detection doesn't work or you want to use a different URL:

Source Repository
   Add source repository? (y/n): y

   [i] Enter the URL where your code is hosted:
      • GitHub: https://github.com/username/repo
      • GitLab: https://gitlab.com/username/repo
      • Other: Any public repository URL

   Enter source repository URL: https://gitlab.com/robotics/my-package
    Source repository: https://gitlab.com/robotics/my-package

Complete Publishing Example

$ cd my-sensor-package
$ horus publish

 Detected horus.toml manifest
 Publishing my-sensor-package v1.0.0...

Package Metadata (optional)
   Help users discover and use your package by adding:

Documentation
    Found local /docs folder with markdown files
   Add documentation? (y/n): y

   Documentation options:
     1. External URL - Link to online documentation
     2. Local /docs - Bundle markdown files in a /docs folder

   [i] Your /docs folder should contain .md files organized as:
      /docs/README.md          (main documentation)
      /docs/getting-started.md (guides)
      /docs/api.md             (API reference)

   Choose option (1/2/skip): 2
    Will bundle local /docs folder with package

Source Repository
    Auto-detected: https://github.com/robotics-lab/my-sensor-package
   Add source repository? (y/n): y
   Use detected URL? (y/n): y
    Source repository: https://github.com/robotics-lab/my-sensor-package

 Uploaded to registry

Published: my-sensor-package@1.0.0
   View at: https://api.horusrobotics.dev/packages/my-sensor-package

When the prompts are skipped, the categories, package type, and source repository recorded in horus.toml are used as-is.

How Users See Your Links

On the registry, your package will display:

Loading diagram...
Registry package display with documentation and source links
  • Docs Button: Only appears if you added documentation
    • External URL: Opens in new tab
    • Local docs: Opens built-in markdown viewer
  • Source Button: Only appears if you added source URL
    • Opens repository in new tab

Version Management

Semantic Versioning:

  • 1.0.0 - Major.Minor.Patch
  • 1.0.01.0.1 - Patch: Bug fixes only
  • 1.0.01.1.0 - Minor: New features (backward compatible)
  • 1.0.02.0.0 - Major: Breaking changes

Publishing new version:

# 1. Update version in horus.toml
# [package]
# version = "1.1.0"

# 2. Publish
horus publish

Version constraints in dependencies:

[dependencies]
pid-controller = "=1.2.0"     # Exact version (a bare "1.2.0" means ^1.2.0)
motion-planner = "^2.0"       # Compatible (2.x.x, not 3.0.0)
sensor-drivers = "~1.5.0"     # Patch updates (1.5.x)

Dependency Management

Automatic Resolution

HORUS automatically resolves and installs dependencies:

horus install robot-controller

Output:

Resolving dependencies...
  robot-controller@1.0.0
  ── motion-planner@2.0.1
     ── pathfinding-utils@1.2.0
  ── pid-controller@1.2.0
      ── control-utils@1.0.0

Installing 5 packages...
 All dependencies installed

Specifying Dependencies

In your horus.toml:

[dependencies]
horus = "*"
pid-controller = "1.2"
motion-planner = "2.0"
serde = { version = "1", source = "crates.io", features = ["derive"] }

See Configuration Reference for all dependency formats including git, path, and prefixed dependencies.

Package Structure

Minimal Package

my-package/
── horus.toml          # Package metadata
── src/
   ── lib.rs          # Library entry point
   ── nodes/
       ── my_node.rs  # Your node implementation
── examples/
   ── demo.rs         # Usage example
── README.md           # Documentation

Library Package (lib.rs)

// src/lib.rs
pub mod nodes;
pub mod messages;
pub mod utils;

// Re-export commonly used items
pub use nodes::MyControllerNode;
pub use messages::MyMessage;

Node Implementation

// src/nodes/my_node.rs
use horus::prelude::*;

pub struct MyControllerNode {
    pub input: Topic<f64>,
    pub output: Topic<f64>,
    gain: f64,
}

impl MyControllerNode {
    pub fn new(gain: f64) -> Self {
        Self {
            input: Topic::new("input").expect("Failed to create input topic"),
            output: Topic::new("output").expect("Failed to create output topic"),
            gain,
        }
    }
}

impl Node for MyControllerNode {
    fn name(&self) -> &str {
        "MyController"
    }

    fn tick(&mut self) {
        if let Some(value) = self.input.recv() {
            let result = value * self.gain;
            self.output.send(result);
        }
    }
}

Example Usage

// examples/demo.rs
use my_package::MyControllerNode;
use horus::prelude::*;

fn main() {
    let mut scheduler = Scheduler::new();

    let controller = MyControllerNode::new(2.5);
    scheduler.add(controller).order(5).done();

    scheduler.run().expect("Scheduler failed");
}

Test the example:

horus run examples/demo.rs --release

Best Practices

Package Design

Single Responsibility:

# Good: Focused packages
pid-controller          # Just PID control
motion-planner          # Just path planning
sensor-fusion           # Just sensor fusion

# Bad: Kitchen sink package
robotics-everything     # Too broad, hard to maintain

Clear Interfaces:

// Good: Simple, clear API
pub struct PIDController {
    pub fn new(kp: f64, ki: f64, kd: f64) -> Self { ... }
    pub fn update(&mut self, error: f64) -> f64 { ... }
}

// Bad: Complex, unclear API
pub struct Controller {
    pub fn do_stuff(&mut self, x: f64, y: Option<f64>, z: &str) -> Result<Vec<f64>, Box<dyn Error>> { ... }
}

Documentation

Include comprehensive README:

# PID Controller

Production-ready PID controller for HORUS robotics framework.

## Features
- Anti-windup protection
- Derivative filtering
- Output clamping

## Installation
```bash
horus install pid-controller
```

## Usage
```rust
use pid_controller::PIDController;

let mut pid = PIDController::new(1.0, 0.1, 0.01);
let output = pid.update(error);
```

## Examples
See `examples/` directory for complete examples.

## License
MIT

Testing

Always test before publishing:

# Run tests
horus test

# Run examples
horus run examples/demo.rs --release

# Build in release mode
horus build --release

Versioning Strategy

Semantic Versioning:

  • 0.x.x - Development (expect breaking changes)
  • 1.0.0 - First stable release
  • 1.x.x - Stable with backward compatibility
  • 2.0.0 - Major rewrite or breaking changes

Changelog:

# Changelog

## [1.2.0] - 2025-10-09
### Added
- Anti-windup protection
- Configurable output limits

### Fixed
- Derivative kick on setpoint change

## [1.1.0] - 2025-09-15
### Added
- Derivative filtering

## [1.0.0] - 2025-08-01
- Initial stable release

Common Workflows

Creating a Package Library

# 1. Create new project as library
horus new my-sensor-lib --rust --lib

# 2. Update horus.toml
# [package]
# name = "my-sensor-lib"
# version = "0.1.0"
# type = "lib"

# 3. Implement in src/lib.rs
# pub mod drivers;
# pub mod calibration;

# 4. Add examples
mkdir examples
# Create examples/demo.rs

# 5. Test
horus run examples/demo.rs

# 6. Publish
horus auth login
horus publish

Using Multiple Packages

# Install packages
horus install pid-controller
horus install motion-planner
horus install sensor-fusion

# Use in your project
// Illustrative: pid-controller, motion-planner and sensor-fusion are example
// registry packages, so this block is not compiled by the docs test suite.
use pid_controller::PIDController;
use motion_planner::AStarPlanner;
use sensor_fusion::KalmanFilter;
use horus::prelude::*;

fn main() {
    let mut scheduler = Scheduler::new();

    // Combine multiple packages
    let pid = PIDController::new(1.0, 0.1, 0.01);
    let planner = AStarPlanner::new();
    let filter = KalmanFilter::new();

    // Add nodes...
}

Updating Dependencies

# Update all packages to latest versions
horus update

# Update a specific package
horus update pid-controller

# Or install a specific version
horus install pid-controller@1.3.0

# Check available versions on registry
horus info pid-controller

Troubleshooting

Package Not Found

Error:

Error: Package 'nonexistent-package' not found in registry

Solutions:

# Search the registry for the name you meant
horus search nonexistent

# Confirm the exact name from the results before installing
horus info correct-package

Version Conflict

Error:

Error: Version conflict
  robot-controller requires motion-planner ^2.0
  sensor-fusion requires motion-planner ^1.5

Solutions:

# Option 1: Update conflicting package
horus install sensor-fusion@2.0.0  # Install compatible version

# Option 2: Pin version manually
# Edit horus.toml:
# [dependencies]
# motion-planner = "2.0"  # Force version 2.0

Build Failures

Error:

Error: Failed to build package 'my-package'

Solutions:

# Clean and rebuild
horus uninstall my-package
horus install my-package

# Check dependencies via registry web interface
# Visit https://api.horusrobotics.dev

# Install dependencies manually if needed
horus install dependency-name

Authentication Required

Error:

Error: Authentication required to publish packages
Run: horus auth login

Solution:

horus auth login
# Opens browser for GitHub OAuth

Registry Unavailable

Error:

Error: Failed to connect to registry

Solutions:

# Check internet connection
ping api.horusrobotics.dev

# Try again later (registry might be down)

# Use cached packages if available
ls ~/.cache/horus/

Registry API

Direct API Access

You can interact with the registry programmatically:

Search packages:

curl 'https://api.horusrobotics.dev/api/packages/search?q=sensor'

Get package info:

curl https://api.horusrobotics.dev/api/packages/pid-controller

Download package:

curl -o pkg.tar.gz https://api.horusrobotics.dev/api/packages/pid-controller/1.2.0/download

Next Steps