Use & publish a library

This guide installs a reusable Jaiph library into your workspace, imports it from a program, and (in the second half) publishes a library of your own.

A Jaiph library is a git repository with at least one .jh module anywhere in the tree. Imports written as lib-name/path resolve to <workspace>/.jaiph/libs/<lib-name>/<path>.jh after jaiph install clones the library into that directory.

Prerequisites

Part A. Use a library

1. Install by name or URL

jaiph install jaiphlang                                    # registry name (uses JAIPH_REGISTRY)
jaiph install mylib@v1.2                                    # pin a registry name to a version
jaiph install https://github.com/you/queue-lib.git@v1.0    # git URL, optional @<ref> for branch/tag
jaiph install jaiphlang https://github.com/you/queue-lib.git  # several at once, names and URLs mixed

The argument shape decides the path: a token with no / and no : is a registry name resolved through the index, and everything else is a git URL (optional @<ref> suffix for a branch or tag). Registry names install into .jaiph/libs/<name>/; git URLs install into .jaiph/libs/<last-path-segment-without-.git>/, so for one repository a git-URL prefix may differ from its registry prefix. Existing directories are skipped unless you pass --force. Commit the resulting .jaiph/libs.lock.

See CLI — jaiph install for the argument-resolution rules, the allowed URL schemes, the shallow-clone behavior, and the commit/signature verification with its error messages.

2. Restore from the lockfile

jaiph install

With no arguments, jaiph install restores every entry in .jaiph/libs.lock. It clones any missing library directory, and existing directories are skipped unless you pass --force. Each entry must be commit-pinned: an entry without a commit (an older, unpinned lockfile) is refused and the run fails before cloning anything — re-run jaiph install <name> to re-pin it. The cloned commit (and any recorded signature) is then verified as described in Restore-from-lockfile mode. The registry is never read on this path.

3. Import from a program

The clone directory name is the import prefix. For jaiph install jaiphlang, the lib lives at .jaiph/libs/jaiphlang/ and imports use the jaiphlang/ prefix:

import "jaiphlang/queue" as q

export def main() {
  q.has_tasks()
  const t = q.get_first_task()
  log "${t}"
}

An import whose path has no / is looked up only relative to the importing file. The library fallback is skipped for it.

4. Verify

ls .jaiph/libs/jaiphlang/         # cloned files, no nested .git
cat .jaiph/libs.lock              # one entry per installed library
jaiph run ./flow.jh               # imports must resolve at compile time

A clone with no .jh files anywhere in the tree is rejected with lib "<name>" contains no .jh modules — not a jaiph library? and the directory is removed before any lock entry is written.

5. Update an installed library

jaiph install skips any library directory that already exists, so re-running it does not fetch new commits on its own. To move a library to a different tag, branch, or commit, re-run the install with --force:

jaiph install https://github.com/you/queue-lib.git@v1.1 --force

--force deletes the existing clone, clones again at the new ref, and rewrites the .jaiph/libs.lock entry with the new commit. Commit the updated lockfile.

Trust boundary for the execution binary

An imported library cannot silently change how your prompt steps run. Jaiph honors the execution-binary keys (agent.command, agent.backend) and the trust, argv, and run-directory keys (agent.trusted_workspace, agent.cursor_flags, agent.claude_flags, run.logs_dir) only from your entry module. See the import trust boundary section of the configuration reference for the full cross-module scoping contract and the advanced import-unlock opt-ins.

Part B. Publish a library

Publishing uses plain git. There is no package registry upload and no build step.

1. Lay out the repo

A library is a git repository of .jh modules plus any companion script files those modules reference. Libraries commonly take one of these shapes:

Companion scripts (e.g. queue.py next to queue.jh) must be referenced with relative paths, such as import script "./queue.py". The import script statement resolves the path only relative to the importing file, so it has no fallback to the workspace libraries directory.

2. Decide the public surface

Add export to the defs and scripts you want importers to see:

export def get_first_task() { … }
export def has_tasks() { … }

A module with zero export lines exposes nothing through the import alias. Names are private by default; importers can only call exported names.

3. Tag a release

git tag v0.1.0
git push --tags

Consumers pin to that tag with jaiph install <name>@v0.1.0 or jaiph install <url>.git@v0.1.0. The ref is passed straight to git clone --branch. The resolved 40-char commit is recorded in .jaiph/libs.lock, so restore is reproducible even when the tag later moves.

4. (Optional) List on jaiph.org/registry

To let consumers install by bare name, open a PR against jaiphlang/registry adding an entry to registry.json under a unique key matching /^[A-Za-z0-9_-]+$/:

{
  "libs": {
    "<your-name>": {
      "url": "https://github.com/<you>/<repo>.git",
      "description": "<one line>",
      "commit": "<40-char hex SHA>"
    }
  }
}

An entry must pin a commit (a 40-character hex SHA) and may include a detached minisign signature over that commit SHA. Jaiph verifies that signature only against its own embedded project key (jaiph.pub), never against a key supplied by the entry, so an entry cannot vouch for its own signature. npm run registry:build refuses to write an index whose entries are not all pinned, so shipped entries always carry a commit; consumers installing an unpinned entry must pass --allow-unpinned. When these fields are present, jaiph install checks the cloned commit against the pinned commit and verifies the signature, and it fails the install if either check does not pass.

The key is the import prefix consumers will write (import "<your-name>/…"). After the PR merges upstream, maintainers of the Jaiph repo run npm run registry:build, sign the built index with minisign so that docs/registry.minisig is committed beside docs/registry, and push. GitHub Pages then serves both files, the index at https://jaiph.org/registry and its signature at https://jaiph.org/registry.minisig. A remote jaiph install verifies the index against that signature and fails closed when the signature is missing or does not match, so publishing docs/registry.minisig is required for remote installs to work. See Contributing — Library registry signing.

Verification

For a consumer:

For a publisher: