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 fileLifecycle phases:
is_installed()- Skip if already installed (optional)acquire()- Download/copy source materials (required)build()- Compile or transform (optional)pre_install()- Pre-install hook (optional)install()- Copy files to PREFIX (required)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:
pre_remove()- Pre-removal hook (optional)- Delete all files tracked in
installed_files remove()- Custom cleanup during deletion (optional)- Clean up empty directories
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]search
Search for packages by name:
recipe search <pattern>
# Example:
recipe search rip
# ripgrep 14.1.0 Fast line-oriented search toolinfo
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. myappGlobal 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
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
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:
- Explicit path if it contains
/or ends with.rhai / .rhai (subdirectory style)/ / .rhai
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.lockRecipe 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
- Recipe Format - Full specification for writing recipes
- Helper Functions - All available functions for recipes