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:
| Source | Description | Example |
|---|---|---|
| HORUS Registry | Curated robotics packages | horus install pid-controller |
| crates.io | Rust ecosystem packages | horus install serde |
| PyPI | Python ecosystem packages | horus install numpy |
| Git | Git repositories (via horus.toml) | See Configuration |
| Local Path | Local 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:
- First checks HORUS registry
- Then checks both PyPI and crates.io
- 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:
- Detects package source (HORUS registry, crates.io, or PyPI)
- Downloads package from the appropriate source
- Resolves dependencies automatically
- Caches locally in
~/.cache/horus/or.horus/packages/ - 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:
| Source | Directory Format | Example |
|---|---|---|
| HORUS Registry | <name>@<version>/ | pid-controller@1.0.0/ |
| crates.io | cratesio_<name>@<version>/ | cratesio_serde@1.0.200/ |
| PyPI | pypi_<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) cargomust 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
pipmust 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
horus search
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.jsonwritten 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:
- Opens browser to GitHub OAuth page
- You authorize HORUS Registry
- Token saved to
~/.config/horus/auth.json - 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:
- See "Authorize HORUS Registry" page
- Review permissions requested:
- Read user profile
- Read email address
- Click "Authorize horus-registry"
- 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.jsonwhen 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 profileuser: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:
- Go to https://github.com/settings/applications
- Find "HORUS Registry" under "Authorized OAuth Apps"
- 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:
- Generate API key:
horus auth api-key --name "GitHub Actions" - Copy the resulting credentials file:
cat ~/.config/horus/auth.json - Add to GitHub secrets:
- Go to repository Settings → Secrets → Actions
- New repository secret:
HORUS_AUTH_JSON - Paste the file contents
- 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:
- Generate key:
horus auth api-key --name "GitLab CI" - 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:
- Generate key:
horus auth api-key --name "Jenkins" - Jenkins monitor → Manage Jenkins → Credentials
- 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_KEYenvironment variable — nothing in HORUS reads one. Credentials always come fromauth.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.jsonfrom 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:
-
Authenticate with the registry (see Authentication section above):
horus auth login -
Complete
horus.tomlmetadata:[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.
-
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.
Adding Documentation and Source Links
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:
- 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.Patch1.0.0→1.0.1- Patch: Bug fixes only1.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 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 release1.x.x- Stable with backward compatibility2.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
- Environment Management - Pin dependencies with
horus.lockand share the global cache - CLI Reference - Complete command documentation