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/binThe 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 1Conditional 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.comRobust 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 /tmpPath 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")
}2026 © All Rights Reserved.
- All the content is available under CC BY-SA 4.0, expect where otherwise stated.
- Source code is available on GitLab, licensed under Apache 2.0.
Last updated on
