Skip to content

Package manager

prepoly ships a minimal package manager called ppm (prepoly package manager). It handles project scaffolding, dependency fetching, and compilation/execution with a handful of commands.

ppm new creates a new directory and scaffolds a project inside it:

Terminal window
ppm new myapp

This creates a new directory with the following layout:

Path Purpose
myapp/myapp/ Source directory for sub-modules
myapp/myapp.pp Package root file (your program’s entry point)
myapp/package.toml Package manifest
myapp/AGENTS.md Instructions teaching LLM agents prepoly (see LLM agents)
myapp/CLAUDE.md Symlink to AGENTS.md, so Claude Code reads the same instructions

To initialize a project in the current directory instead, use ppm init:

Terminal window
mkdir myapp && cd myapp
ppm init myapp

The generated package.toml looks like this:

[package]
name = "myapp"
author = ""
license = "MIT"
[dependencies]
# mylib = { git = "https://github.com/user/mylib", hash = "<commit hash>" }
# mylib = { path = "../mylib" }

The commented lines show the two dependency forms, ready to fill in.

Inside a project directory (where package.toml lives), use:

Terminal window
ppm run # compile and run
ppm check # type-check only

Both commands read package.toml, fetch any missing dependencies, set the PREPOLY_PACKAGES environment variable, and invoke prepoly on the root file (<package-name>.pp).

ppm lsp starts prepoly-lsp with the same dependency resolution, so editor diagnostics, hover, and completion see the project’s dependencies. Point your editor’s LSP command at ppm lsp instead of prepoly-lsp (see Installing the LSP server). In a directory without a package.toml it simply starts the plain server, so the one editor configuration works for projects and loose .pp files alike.

A dependency is either a Git repository pinned to a commit hash, or a local directory given by path. Add them to the [dependencies] section of package.toml:

[dependencies]
"geometry" = { git = "https://github.com/user/geometry-pp", hash = "a1b2c3d4e5f6" }
"utils" = { git = "https://github.com/user/utils-pp", hash = "deadbeef1234" }
"mylib" = { path = "../mylib" }

When you run ppm run or ppm check, each Git dependency is cloned to ~/.prepoly/packages/<name>-git-<hash> if it is not already present, and then checked out at the pinned commit.

A path dependency is used in place — nothing is copied or fetched. The path is resolved relative to the project root (the directory holding package.toml) and must point at the dependency project’s root directory: the one containing its <package-name>.pp root file. Edits to the dependency are picked up on the next ppm run/ppm check with no extra step, which makes path the form to use while developing a library alongside its consumer; a dependency cannot combine path with git/hash.

Once a dependency is declared, its modules are available via import:

// Import specific names from the package root
import geometry.{ Vec2, dot }
// Import a sub-module
import geometry.utils.{ normalize }
// Qualified module import
import geometry
// then use: geometry.Vec2, geometry.dot(...)

The package root file is <package-name>.pp inside the dependency directory, and sub-modules live under the <package-name>/ directory — the same layout that ppm new creates.

A library package has the same layout as an application. Declare the public API in the root file and organize implementation details into sub-modules. Names starting with _ are private and cannot be imported by dependents (see Modules).

mylib/
mylib.pp # public API: types, functions
mylib/
_internal.pp # private helper (not importable)
extra.pp # public sub-module
package.toml

ppm sets the environment variable PREPOLY_PACKAGES before invoking prepoly. The format is a colon-separated list of name=path entries:

PREPOLY_PACKAGES=geometry=/home/user/.prepoly/packages/geometry-git-a1b2c3d4:utils=/home/user/.prepoly/packages/utils-git-deadbeef

Both the compiler and the language server read this variable at startup, so editor diagnostics and completions work for dependencies too.