Zillowe FoundationZillowe Documentation

Syntax

Learn how to write ZSM scripts using declarative headers and safe-shell logic.

A ZSM script consist of two parts: the Declarative Header and the Script Body.

The Declarative Header

The header uses the # :: prefix to define script metadata and required permissions.

#!/usr/bin/env zsm

# :: name: my-app
# :: version: 1.0.0
# :: url: zillowe.pages.dev/zoi/install.zsm
# :: description: A cool app installer

# Request host tools
# :: require bin: gpg, tar, zstd

# Request internal permissions
# :: perm net: example.com
# :: perm fs: ~/.local/bin

The Script Body

The body uses bash-like logic with ZSM-specific built-ins.

Variables & Capture

ZSM supports variable assignments from literal strings or command output.

BIN_NAME="zoi"
# Capture stdout from a pipeline
LATEST_TAG = run curl -s "api_url" | run grep "tag" | run head -n 1

Conditional Logic (If Statements)

ZSM supports basic if / else branching, allowing scripts to adapt to the host environment.

OS = system.os()
if OS == "macos" {
    web.download("https://.../mac.tar.gz", "/tmp/bin")
} else {
    web.download("https://.../linux.tar.gz", "/tmp/bin")
}

Deep Supervision (Argument Locking)

For maximum security, ZSM allows you to restrict external tools to specific argument patterns. If a script tries to run the tool with different arguments, it will be blocked.

# Only allow curl in silent mode
# :: require bin: curl --silent

# This will PASS
run curl --silent https://example.com

# This will FAIL (Security Violation)
run curl --fail https://example.com

Robust Piping

ZSM supports multi-stage pipes. Each stage is supervised and runs with a mandatory 60s timeout.

run zstd -dc archive.tar.zst | run tar -xf - -C /tmp

Path Portability

The tilde (~) character is automatically expanded to the user's home directory in both permission headers and script logic.

# Permission header
# :: perm fs: ~/.local/bin

# Script logic
fs.move("/tmp/bin", "~/.local/bin/app")

Uninstall Hooks

ZSM automatically tracks files created by fs.move and fs.mkdir and deletes them when a user runs zsm uninstall <pkg>.

However, if your script modified shell profiles (using system.add_path, etc.), you should provide cleanup logic. ZSM automatically injects a ZSM_PHASE variable that equals "uninstall" during the removal process.

if ZSM_PHASE == "uninstall" {
    # This block only runs during `zsm uninstall`
    system.remove_path("~/.local/bin")
    system.remove_env("MY_APP_HOME")
} else {
    # Normal installation logic goes here
    system.add_path("~/.local/bin")
    system.add_env("MY_APP_HOME", "~/.config/myapp")
}

A software organization

2026 © All Rights Reserved.

  • All the content is available under CC BY-SA 4.0, expect where otherwise stated.

Last updated on