Skip to content

Packages & Registry

Shape projects can be compiled into distributable .shapec package bundles containing pre-compiled bytecode, metadata, and dependency information. Packages can be shared via the Shape Package Registry at pkg.shape-lang.dev.

From a project directory containing shape.toml:

Terminal window
shape build

This compiles all .shape files in the project into a single .shapec bundle.

shape build [--output <path>] [--opt-level <0-3>]
FlagDefaultDescription
--output<name>-<version>.shapec (portable) or <name>-<version>-<arch>-<os>.shapec (host-bound native deps)Output path for the bundle
--opt-level0Optimization level (reserved)
Terminal window
$ shape build
Building package 'mylib' v0.1.0...
Built 3 modules into mylib-0.1.0.shapec (2048 bytes)

.shapec files use a binary format:

OffsetSizeContent
08 bytesMagic: SHAPEPKG
84 bytesFormat version (little-endian u32)
12variableMessagePack-encoded payload

The payload contains:

  • Metadata: package name, version, compiler version, source hash, entry module, build timestamp, bundle kind, build host, native portability flag
  • Modules: each with its module path, compiled bytecode, export names, and per-file source hash
  • Dependencies: declared dependency versions from shape.toml
  • Native dependency scopes: transitive [native-dependencies] grouped by package identity

File paths are converted to module paths using :: separators:

File PathModule Path
main.shapemain
utils/helpers.shapeutils::helpers
utils/index.shapeutils

index.shape files map to their parent directory name, following the convention for directory-level module entry points.

Point a dependency directly at a .shapec file:

[dependencies]
mylib = { path = "./libs/mylib-1.0.0.shapec" }

When a path dependency is resolved, the resolver checks for a .shapec file alongside the directory. If dep.shapec exists next to dep/, the bundle is preferred:

project/
shape.toml # mylib = { path = "./mylib" }
mylib/
shape.toml
lib.shape
mylib.shapec # <-- preferred over mylib/

Bundle dependencies resolve with version = <bundle metadata version> in the dependency resolver. The module loader automatically detects .shapec paths during dependency setup and loads their modules into the bundle resolver.

Current resolver support:

  • path dependencies
  • git dependencies (cached under ~/.shape/cache/git/)
  • semver registry dependencies (index entries loaded from SHAPE_REGISTRY_INDEX or ~/.shape/registry/index, sources from SHAPE_REGISTRY_SRC or ~/.shape/registry/src)
  • transitive semver constraints are solved across the full dependency graph

When a bundle is loaded as a dependency, its modules are prefixed with the dependency name:

shape.toml
[dependencies]
mathlib = { path = "./mathlib.shapec" }

A module helpers inside the bundle becomes mathlib::helpers:

from mathlib::helpers use { normalize }

The full resolution order for imports:

  1. Extension-provided modules (highest priority)
  2. Bundle-provided modules
  3. Embedded standard library
  4. Filesystem (project root, module paths, stdlib path)

Each bundle records SHA-256 hashes:

  • Per-module hash: hash of the individual source file
  • Combined hash: hash of all source files concatenated

Compiling the same source twice produces identical hashes. Modifying any file changes the combined hash, enabling staleness detection.

The Shape Package Registry (pkg.shape-lang.dev) hosts published packages for the community.

Packages are signed with Ed25519 keys. Generate a signing key if you haven’t already:

Terminal window
shape keys generate

Then publish from a project directory:

Terminal window
shape publish

This builds the project, signs the bundle with your key, and uploads it to the registry. Every published version is immutable — once published, a version cannot be overwritten.

The [project] section in shape.toml should include a description for the registry listing:

[project]
name = "mylib"
version = "1.0.0"
description = "A useful library for Shape programs"
entry = "src/lib.shape"

Add a registry dependency:

Terminal window
shape add mylib

This fetches the package index, resolves the best matching version, downloads the .shapec bundle, verifies its checksum and cryptographic signature, and adds it to shape.toml:

[dependencies]
mylib = "^1.0"

On first use of a package from an unknown author, you’ll see a Trust-on-First-Use (TOFU) prompt showing the author’s signing key. You can choose to trust the key for this package or for all packages by that author.

Terminal window
shape remove mylib

Removes the dependency from shape.toml.

Terminal window
shape search "json parser"
shape info mylib

shape info shows package details including version history, required permissions, author signing key, and documentation.

Every package in the registry is:

  • Signed — Ed25519 signature on each module manifest, verified on download
  • Content-addressed — SHA-256 checksums on bundles, verified against the index
  • Permission-declared — required capabilities (filesystem, network, etc.) are visible before install
  • Author-bound — signing keys are tied to authenticated registry accounts

The verification chain on shape add:

  1. Download sparse index entry for the package
  2. Resolve version via semver solver
  3. Download .shapec bundle
  4. Verify bundle SHA-256 matches index checksum
  5. Deserialize and verify each module manifest integrity hash
  6. Verify Ed25519 signature on each manifest
  7. Check author key against local keychain (TOFU prompt if unknown)
  8. Display required permissions

Registry credentials are stored in ~/.shape/credentials.json (mode 0600). API tokens use the shp_tok_ prefix and are scoped to specific operations (publish, yank).

Terminal window
# In the library project
cd mylib/
shape build
# produces mylib-1.0.0.shapec
# In the consumer project
cd myapp/
# shape.toml: mylib = { path = "../mylib/mylib-1.0.0.shapec" }
shape myapp.shape
Terminal window
# Publish a library
cd mylib/
shape publish
# Use it in another project
cd myapp/
shape add mylib
shape run

Native Bundles And Cross-Platform Compatibility

Section titled “Native Bundles And Cross-Platform Compatibility”

When native libraries are involved, .shapec portability depends on how [native-dependencies] are declared.

  • Portable bundles (metadata.native_portable = true) include only system-style native specs whose resolved values are not path-like.
  • Host-bound bundles (metadata.native_portable = false) include any path or vendored provider, or any target entry that resolves to a path-like native value.

By default:

  • portable output name: <name>-<version>.shapec
  • host-bound output name: <name>-<version>-<arch>-<os>.shapec

Target-qualified vendored packages are supported in package metadata through targets = { "os-arch[-env]" = "..." }, but bundles using vendored or path native assets are still host-bound today. Publish one bundle per target platform for those packages. For portable packages, a single bundle can be reused across platforms.

Important: current bundles preserve native dependency metadata only. The native files referenced by provider = "path" or provider = "vendored" are not embedded into .shapec, and shape publish uploads only the bundle bytes. Registry distribution therefore does not yet carry those native assets; they must be shipped separately on disk if a consumer is expected to load them.

Transitive Native Requirements From Bundles

Section titled “Transitive Native Requirements From Bundles”

Bundle compilation embeds transitive native requirements into native_dependency_scopes. When another package depends on that .shapec, the shared native resolver ingests these scopes so frozen locks still cover transitive native requirements.

Older bundles without embedded scopes still load, but the CLI warns that transitive native locking cannot be guaranteed for those bundles.

Use shape tree to inspect dependency resolution:

Terminal window
shape tree
shape tree --native
  • shape tree shows source/bundle dependency edges and resolved versions.
  • shape tree --native additionally prints bundled native dependency scopes and host-bound markers.