GitHub

recipe CLI Reference

Complete reference for the recipe package manager CLI - a local-first package manager using Rhai scripts.

Overview

recipe is the local-first package manager for LevitateOS. It executes Rhai scripts that define how to acquire, build, and install packages.

recipe <command> [options] [arguments]

Commands

install

Install a package by executing its recipe lifecycle:

recipe install <package>
recipe install <package> --deps    # Also install dependencies
recipe install ./path/to/pkg.rhai  # From local file

Lifecycle phases:

  1. is_installed() - Skip if already installed (optional)
  2. acquire() - Download/copy source materials (required)
  3. build() - Compile or transform (optional)
  4. pre_install() - Pre-install hook (optional)
  5. install() - Copy files to PREFIX (required)
  6. post_install() - Post-install hook (optional)

After successful install, the recipe is updated with installed = true, installed_version, installed_at (timestamp), and installed_files (list of installed paths).

remove

Remove an installed package:

recipe remove <package>

Removal phases:

  1. pre_remove() - Pre-removal hook (optional)
  2. Delete all files tracked in installed_files
  3. remove() - Custom cleanup during deletion (optional)
  4. Clean up empty directories
  5. post_remove() - Post-removal hook (optional)

If any files fail to delete, the package state is preserved and you'll need to fix permissions before retrying.

update

Check for available updates:

recipe update              # Check all installed packages
recipe update <package>    # Check specific package

Calls the recipe's check_update() function if defined. This function should return a new version string if an update is available, or () if up to date. When an update is found, the recipe's version variable is updated.

Example check_update() implementation:

fn check_update() {
    let latest = github_latest_release("owner/repo");
    if latest != version {
        latest  // Return new version
    } else {
        ()      // No update
    }
}

upgrade

Upgrade packages to newer versions:

recipe upgrade              # Upgrade all with pending updates
recipe upgrade <package>    # Upgrade specific package

Compares the recipe's version against installed_version. Uses semantic versioning comparison when possible, falling back to string comparison. If an upgrade is needed, removes the old version and installs the new one.

list

List all available packages:

recipe list

# Output shows installation status and versions:
# ripgrep    [installed: 14.1.0]
# htop       [available: 3.3.0]
# curl       [installed: 8.0, 8.5 available]

info

Show detailed package information:

recipe info <package>

# Output includes:
# Name:        ripgrep
# Version:     14.1.0
# Description: Fast line-oriented search tool
# Depends:     pcre2
# Recipe:      /path/to/ripgrep.rhai
#
# Status:      Installed
# Installed:   14.1.0
# Installed at: 2024-01-15T10:30:00
# Files:       3 files
#              /usr/local/bin/rg
#              /usr/local/share/man/man1/rg.1
#              ...

deps

Show package dependencies:

recipe deps <package>           # Direct dependencies
recipe deps <package> --resolve # Full install order

Without --resolve, shows direct dependencies only. With --resolve, performs topological sort to show the full install order with cycle detection.

# Direct dependencies:
recipe deps myapp
#   - libfoo
#   - libbar

# Resolved install order:
recipe deps myapp --resolve
#   1. core [installed]
#   2. libfoo
#   3. libbar
#   4. myapp

Global Options

Option Default Description
-r, --recipes-path $XDG_DATA_HOME/recipe/recipes Path to recipes directory
-p, --prefix /usr/local Installation prefix
-b, --build-dir (temp dir) Build directory

The RECIPE_PATH environment variable can also set the recipes directory.

Writing Recipes

Recipes are Rhai scripts (.rhai files) that define package metadata and lifecycle functions. See Recipe Format for the full specification.

Required Variables

Variable Type Description
name String Package name
version String Package version
installed Boolean Installation state (start with false)

When installed = true, these are also required:

Variable Type Description
installed_version String Version that was installed
installed_files Array List of installed file paths

Optional Variables

Variable Type Description
description String Short package description
deps Array List of dependency package names
installed_at Integer Unix timestamp of installation

Required Functions

Function Purpose
acquire() Download or copy source materials
install() Install files to PREFIX

Optional Functions

Function Purpose
build() Compile or transform sources
is_installed() Custom installation check
check_update() Check for new versions
pre_install() Hook before install phase
post_install() Hook after install phase
pre_remove() Hook before removal
remove() Custom cleanup during removal
post_remove() Hook after removal

Built-in Variables

These constants are available in recipe scripts:

Variable Description
PREFIX Installation prefix (e.g., /usr/local)
BUILD_DIR Temporary build directory
ARCH Target architecture (x86_64, aarch64)
NPROC Number of CPU cores
RPM_PATH RPM repository path (from environment)

Minimal Recipe Example

hello.rhai
let name = "hello";
let version = "1.0.0";
let installed = false;

fn acquire() {
    // Download source
    download("https://example.com/hello-1.0.0.tar.gz");
}

fn build() {
    extract("tar.gz");
    cd("hello-1.0.0");
    run("make");
}

fn install() {
    install_bin("hello");
}

Recipe with Dependencies

myapp.rhai
let name = "myapp";
let version = "2.0.0";
let description = "My application";
let deps = ["libfoo", "libbar"];
let installed = false;

fn acquire() {
    download(`https://example.com/myapp-${version}.tar.xz`);
}

fn build() {
    extract("tar.xz");
    cd(`myapp-${version}`);
    run(`./configure --prefix=${PREFIX}`);
    run(`make -j${NPROC}`);
}

fn install() {
    run("make install");
}

fn check_update() {
    let latest = github_latest_tag("owner/myapp");
    if latest != version { latest } else { () }
}

Package Resolution

When you run recipe install , the CLI looks for recipes in this order:

  1. Explicit path if it contains / or ends with .rhai
  2. /.rhai
  3. //.rhai (subdirectory style)

Package names must be alphanumeric with hyphens/underscores only. Path traversal (e.g., ../ ) is rejected.

Concurrency & Safety

The recipe CLI uses file locking to prevent concurrent operations on the same package. If a lock file exists from a crashed operation, delete it manually:

rm /path/to/recipes/package.rhai.lock

Recipe state updates are atomic - the file is written to a temp file first, then renamed. This prevents corruption if the process is interrupted.

See Also