The Synoema package system lets you share reusable Synoema code with others — and install code others have shared. In this tutorial we'll build a small but real package: sno-units, a unit-conversion library for physical measurements (temperature, pressure, distance).
By the end you'll know how to:
- Structure a package directory
- Write and type-check library code
- Declare exports, capabilities, and LLM use-case hints
- Test locally before publishing
- Publish to the registry with one command
Prerequisites
Install Synoema if you haven't already:
curl -fsSL https://synoema.tech/install.sh | sh
sno --version # should print 0.1.0-beta.1 or newer
Step 1 — Create the directory
mkdir sno-units
cd sno-units
A Synoema package is just a directory. No build system, no generated files.
Step 2 — Write the manifest (sno.toml)
Create sno.toml:
[package]
name = "sno-units"
version = "0.1.0"
description = "Physical unit conversions: temperature, pressure, distance"
keywords = ["units", "conversion", "temperature", "physics", "iot"]
license = "MIT"
author = "Your Name <you@example.com>"
min_lang_version = "0.1.0-beta.1"
[package.exports]
functions = [
"celsius_to_fahrenheit",
"fahrenheit_to_celsius",
"celsius_to_kelvin",
"hpa_to_psi",
"psi_to_hpa",
"meters_to_feet",
"feet_to_meters",
"km_to_miles",
"miles_to_km",
]
[package.use_cases]
examples = [
"Convert sensor temperature readings from Celsius to Fahrenheit",
"Display pressure readings in PSI or hPa depending on locale",
"Convert GPS altitude from meters to feet for aviation use",
"Unit conversion in IoT sensor data pipelines"
]
[package.capabilities]
network = false
fs_read = false
fs_write = false
exec = false
A few things to note:
- keywords — used by
sno pkg searchfor full-text search - exports.functions — the registry displays only these; others are internal helpers
- use_cases.examples — the MCP server uses these to surface your package when an LLM is looking for relevant libraries
- capabilities — all false because this package is pure math, no I/O
Step 3 — Write the library (lib.sno)
Create lib.sno:
-- sno-units: physical unit conversion library
-- ── Temperature ──────────────────────────────────────────────────────────────
celsius_to_fahrenheit c = c * 1.8 + 32.0
fahrenheit_to_celsius f = (f - 32.0) / 1.8
celsius_to_kelvin c = c + 273.15
kelvin_to_celsius k = k - 273.15
-- ── Pressure ─────────────────────────────────────────────────────────────────
-- hectopascals → pounds per square inch
hpa_to_psi hpa = hpa * 0.0145038
-- pounds per square inch → hectopascals
psi_to_hpa psi = psi * 68.9476
-- ── Distance ─────────────────────────────────────────────────────────────────
meters_to_feet m = m * 3.28084
feet_to_meters ft = ft / 3.28084
km_to_miles km = km * 0.621371
miles_to_km mi = mi / 0.621371
This is pure Synoema — no imports, no side effects. Every function takes a number and returns a number. The Hindley-Milner type checker will infer all types automatically.
Step 4 — Type-check
sno check lib.sno
Expected output:
✓ lib.sno — OK (11 bindings, 0 warnings)
If you see a type error, fix it before continuing. sno check is fast (under 50ms for this file) and can be run on every save.
Step 5 — Test locally
Create a quick test script:
-- test.sno
import "./lib.sno" -- relative path, not registry
main =
p1 = print ("0°C = " ++ show (celsius_to_fahrenheit 0.0) ++ "°F")
p2 = print ("100°C = " ++ show (celsius_to_fahrenheit 100.0) ++ "°F")
p3 = print ("1013 hPa = " ++ show (hpa_to_psi 1013.25) ++ " PSI")
p4 = print ("5 km = " ++ show (km_to_miles 5.0) ++ " mi")
sno run test.sno
0°C = 32.0°F
100°C = 212.0°F
1013 hPa = 14.695... PSI
5 km = 3.10685... mi
The import path "./lib.sno" is a file-system import. When users install your package via sno pkg add and write import "sno-units", the runtime resolves it to the installed package directory instead.
Step 6 — Add an example
Good packages include examples. Create examples/demo.sno:
-- Example: sno-units in a sensor reading pipeline
import "sno-units"
-- Simulate a sensor callback
process_sensor_reading celsius_raw hpa_raw =
f = celsius_to_fahrenheit celsius_raw
psi = hpa_to_psi hpa_raw
("Temp: " ++ show f ++ "°F | Pressure: " ++ show psi ++ " PSI")
main =
-- Typical values from a BME280 sensor
print (process_sensor_reading 23.4 1008.7)
print (process_sensor_reading (-5.0) 1020.0)
sno run examples/demo.sno
Step 7 — Publish
7.1 Sign in
sno pkg login
This opens your browser for GitHub or Google OAuth. On success:
✓ Logged in as your-username
For CI/CD, create an API key from your profile page and use:
sno pkg login --key sno_your_api_key
7.2 Run sno publish
sno publish
The CLI will:
- Validate
sno.toml(all required fields present) - Run
sno check lib.sno(type-check) - Scan for unsafe content (no binaries in a source-only registry)
- Check for breaking changes vs. previous version (none for 0.1.0)
- Upload the tarball
Validating sno.toml ✓
Type-checking lib.sno ✓ (11 bindings)
Content scan ✓ (source only)
Breaking-change diff ✓ (first release)
Uploading sno-units@0.1.0 ✓
Published: https://synoema.tech/packages/sno-units
Step 8 — Verify the published package
sno pkg search units
sno pkg add sno-units # installs from registry
Create a new file outside your package directory:
-- anywhere/test_installed.sno
import "sno-units"
main =
print (celsius_to_fahrenheit 37.0) -- body temperature
sno run test_installed.sno
# → 98.6
Your package is live and installable by anyone.
Updating your package
Edit lib.sno (add a function, fix a bug), bump the version in sno.toml, and publish again:
# sno.toml: version = "0.1.1"
sno publish
The registry keeps all versions. Users on sno pkg add sno-units@^0.1 will get 0.1.1 on their next sno pkg add. Users who pinned to @0.1.0 keep 0.1.0.
What makes a good package
- Pure functions when possible. Pure functions are easy to test, compose, and reason about. LLM agents trust pure packages more.
- Honest capabilities. Declare
network = trueif you make HTTP calls. Users decide whether to allow it. - Descriptive use_cases. The MCP server uses these for semantic search. Write them as questions an LLM might ask, not as technical feature lists.
- Versioned intentionally. Don't publish 0.1.0, 0.1.1, 0.1.2, 0.1.3 in one day to fix typos. Batch small fixes and use patch releases deliberately.
- Narrow scope. A package that does one thing well is more reusable than a toolkit that does everything.
Full package layout (reference)
sno-units/
sno.toml # manifest — required
lib.sno # library entry point — required
README.md # shown on the package page in the registry
examples/
demo.sno # runnable example
Next steps
- Read the full Packages Guide for advanced topics: version ranges, transitive dependencies, capabilities enforcement in
--untrustedmode - Browse published packages for inspiration
- See the Packages ecosystem overview for the big picture