Package Management

Note: Publishing packages requires the marketplace 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 the registry
  • Publish your work for others to use
  • Manage dependencies automatically
  • Version control with semantic versioning
  • Search and discover community packages

Quick Start

Installing a Package

# Install latest version
horus pkg install pid-controller

# Install specific version
horus pkg install pid-controller -v 1.2.0

# Install globally (share across all projects)
horus pkg install sensor-drivers -g

What happens during installation:

  1. Downloads package from registry
  2. Resolves dependencies automatically
  3. Caches locally in ~/.horus/cache/ or .horus/packages/
  4. Makes package available via use statements

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.register(Box::new(pid), 5, Some(true));

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

Publishing Your Package

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

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

# 3. Publish
horus pkg publish

Package Locations

Local Packages

Project-local (default):

my_project/
── .horus/
   ── packages/
       ── pid-controller@1.0.0/
       ── sensor-drivers@2.1.3/
── src/
    ── main.rs

Why use local:

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

Global Packages

System-wide (installed with -g flag):

~/.horus/
── cache/
    ── pid-controller@1.0.0/
    ── pid-controller@1.2.0/
    ── sensor-drivers@2.1.3/

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 ~/.horus/cache/

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

3. System install /usr/local/lib/horus/ (if available)

  • Last resort fallback

Smart Installation Behavior:

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

# Default behavior (no flags)
horus pkg install serde

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

# If package NOT in global cache:
#    Install directly to .horus/packages/serde@1.0.228/
#    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
~/.horus/cache/serde@1.0.228/  # Corrupted

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

# Result:
.horus/packages/serde@1.0.150/  # 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 pkg install

Install packages from the registry.

Usage:

horus pkg install <package> [OPTIONS]

Options:

  • -v, --ver <VERSION> - Install specific version (default: latest)
  • -g, --global - Install to global cache
  • -t, --target <NAME> - Target workspace/project name

Examples:

# Latest version
horus pkg install motion-planner

# Specific version
horus pkg install motion-planner -v 2.0.1

# Global installation
horus pkg install common-utils -g

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

Output:

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

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

Usage:
  use pid_controller::PIDNode;

horus pkg remove

Uninstall a package.

Usage:

horus pkg remove <package>

Options:

  • -g, --global - Remove from global cache
  • -t, --target <NAME> - Target workspace/project name

Examples:

# Remove local package
horus pkg remove motion-planner

# Remove from global cache
horus pkg remove common-utils -g

# Remove from specific workspace
horus pkg remove pid-controller -t my-project

Output:

Removing pid-controller@1.2.0...
 Removed from .horus/packages/
 Freed 892 KB

Package removed: pid-controller@1.2.0

horus pkg list

List installed packages or search the registry.

Usage:

horus pkg list [QUERY] [OPTIONS]

Options:

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

List Local Packages:

horus pkg list

Output:

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

List Global Cache:

horus pkg list -g

Search Registry:

# Search by keyword
horus pkg list sensor

Output:

Found 3 package(s):
  sensor-fusion 2.1.0 - Kalman filter fusion
  sensor-drivers 1.5.0 - LIDAR/IMU/camera drivers
  sensor-calibration 1.0.0 - Calibration tools

horus pkg unpublish

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

Usage:

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

Options:

  • -y, --yes - Skip confirmation prompt

Examples:

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

# Skip confirmation prompt
horus pkg 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://marketplace.horus-registry.dev

Publishing Packages

Prerequisites

Before publishing:

  1. Authenticate with the registry:

    horus auth login --github
    
  2. Complete Cargo.toml metadata:

    [package]
    name = "my-awesome-package"
    version = "1.0.0"
    authors = ["Your Name <you@example.com>"]
    description = "Brief description of your package"
    license = "MIT"
    repository = "https://github.com/username/my-awesome-package"
    
    [package.metadata.horus]
    category = "control"  # Navigation, Vision, Perception, Control, App, Manipulation, Simulation, Utilities
    keywords = ["pid", "controller", "motion"]
    
  3. Test your package locally:

    horus run --release
    

Publishing Workflow

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

# 2. Verify everything builds
horus run --build-only --release

# 3. Publish
horus pkg publish

Interactive prompts:

Publishing my-awesome-package v1.0.0...

 Package metadata validated
 Build successful
 Tests passed
 Documentation generated

Package size: 245 KB (compressed)

Publish to registry? [y/N]: y

Uploading...
 Uploaded to registry

Published: my-awesome-package@1.0.0
Registry URL: https://marketplace.horus-registry.dev/packages/my-awesome-package

Others can now install with:
  horus pkg install my-awesome-package

After publishing, 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
── Cargo.toml

Benefits of Local Docs:

  • Users can view docs directly from the marketplace
  • 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 pkg publish

Publishing my-sensor-package v1.0.0...
 Uploaded to registry

Published: my-sensor-package@1.0.0
   View at: https://marketplace.horus-registry.dev/packages/my-sensor-package

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

 Updating package metadata...
Package metadata updated!

How Users See Your Links

On the marketplace, your package will display:

─────────────────────────────────────────
  my-sensor-package      v1.0.0          
                                         
  High-performance sensor fusion         
                                         
  [View Details] [Docs] [Source]         
                                         
         Markdown Viewer  GitHub Repo    
─────────────────────────────────────────
  • 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.0 1.0.1 - Patch: Bug fixes only
  • 1.0.0 1.1.0 - Minor: New features (backward compatible)
  • 1.0.0 2.0.0 - Major: Breaking changes

Publishing new version:

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

# 2. Publish
horus pkg publish

Version constraints in dependencies:

[dependencies]
pid-controller = "1.2.0"       # Exact version
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 pkg 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 Cargo.toml:

[dependencies]
horus = "0.1"
pid-controller = { version = "1.2", registry = "horus" }
motion-planner = "2.0"

# Optional dependencies
[dependencies]
advanced-planning = { version = "3.0", optional = true }

[features]
planning = ["advanced-planning"]

Package Structure

Minimal Package

my-package/
── Cargo.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: Hub<f64>,
    pub output: Hub<f64>,
    gain: f64,
}

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

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

    fn tick(&mut self, ctx: Option<&mut NodeInfo>) {
        if let Some(value) = self.input.recv(ctx) {
            let result = value * self.gain;
            self.output.send(result, ctx).ok();
        }
    }
}

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.register(Box::new(controller), 5, Some(true));

    scheduler.tick_all().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 pkg install pid-controller

Usage

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:**
```bash
# Run tests
cargo test

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

# Build in release mode
horus run --build-only --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

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

[lib]
name = "my_sensor_lib"
path = "src/lib.rs"

# 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 --github
horus pkg publish

Using Multiple Packages

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

# Use in your project
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();

    // Register nodes...
}

Updating Dependencies

# Update specific package to newer version
horus pkg install pid-controller -v 1.3.0

# Check available versions on registry
horus pkg list pid-controller

Troubleshooting

Package Not Found

Error:

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

Solutions:

# Check spelling
horus pkg list nonexistent

# Search registry for correct package name
horus pkg list 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 pkg install sensor-fusion -v 2.0.0  # Install compatible version

# Option 2: Pin version manually
# Edit Cargo.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 pkg remove my-package
horus pkg install my-package

# Check dependencies via registry web interface
# Visit https://marketplace.horus-registry.dev

# Install dependencies manually if needed
horus pkg install dependency-name

Authentication Required

Error:

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

Solution:

horus auth login --github
# Opens browser for GitHub OAuth

Registry Unavailable

Error:

Error: Failed to connect to registry

Solutions:

# Check internet connection
ping marketplace.horus-registry.dev

# Try again later (registry might be down)

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

Registry API

Direct API Access

You can interact with the registry programmatically:

Search packages:

curl https://marketplace.horus-registry.dev/api/packages?q=sensor

Get package info:

curl https://marketplace.horus-registry.dev/api/packages/pid-controller

Download package:

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

Next Steps