npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@telorun/cli

v0.96.0

Published

Telo CLI - Command-line interface for the Telo runtime.

Readme

Telo CLI

The Telo CLI is the command-line interface for the Telo kernel. It loads and runs YAML manifests on your local machine, watches them for changes during development, statically validates them with telo check, pre-installs controllers with telo install, refreshes imports: pins with telo upgrade, rewrites legacy spellings with telo migrate, ships an application as a single executable with telo package, and publishes module artifacts to an OCI registry with telo publish.

Installation

npm install -g @telorun/cli
# or
pnpm add -g @telorun/cli

Quick Start

# Run a local manifest
telo ./examples/hello-api

# Run from a remote URL
telo https://raw.githubusercontent.com/telorun/telo/main/examples/hello-api/telo.yaml

# Watch mode - auto-restart on file changes
telo --watch ./manifest.yaml

Global options

These apply to every command.

  • -o, --output text|json — Encoding of the CLI's own output. Default text. Under json, stdout carries one machine-readable document and nothing else, while prose, progress and warnings keep going to stderr in both formats — so a script parses stdout and a human still sees why something failed. telo run is exempt: the application writes to the same two descriptors and neither is the CLI's to claim, so its machine surface is --debug instead. Under json, stdout is never coloured; stderr keeps its colour. An unrecognised format is an error, not a fallback to text.
  • --verbose — Verbose CLI logging on stderr.
  • --help, -h / --version — The usual.

Machine output shapes. Commands that report a result (check, install, upgrade, migrate, publish, release) emit an envelope { ok, … } under -o json and nothing on stdout under text. Commands that produce a document (cel, search, module versions|manifest|digest|resources|kinds) write that document under either format — their older per-command --json flags predate -o and keep working — and never mix an error envelope into it: a failure there is prose on stderr plus a non-zero exit.

The cache root. Every command that reads or writes .telo/ resolves its location the same way, in this order:

  1. TELO_CACHE_DIR, if set.
  2. .telo/ beside the nearest telo-workspace.yaml above the manifest — so one repository has one cache, whatever the manifest's depth.
  3. .telo/ beside the manifest, when no marker exists above it.

A monorepo therefore keeps its cache at the workspace root, which matters when you COPY a single app into an image — see Docker image.

Environment variables the CLI reads are listed in Running in production.

Commands

telo publish <paths..>

Publish one or more module artifacts to an OCI registry. For each manifest, the command:

  1. Finds all controllers entries with a local_path qualifier (i.e. locally-developed packages).
  2. Optionally bumps each controller package version with --bump.
  3. Builds each controller package.
  4. Publishes each controller package to its registry (currently npm). If the version already exists, the publish step is skipped — the command is idempotent.
  5. Rewrites the PURL version specs in the manifest to exact static versions.
  6. Bumps metadata.version in the manifest when --bump is given.
  7. Pushes the artifact to an OCI registry — telo.yaml in its own layer, plus one layer per group of files the manifest declares (see below).
telo publish ./modules/my-module/telo.yaml
telo publish ./modules/my-module/telo.yaml --bump=patch
telo publish ./modules/a/telo.yaml ./modules/b/telo.yaml --bump=minor
telo publish ./modules/my-module/telo.yaml --dry-run
telo publish ./modules/my-module/telo.yaml --skip-controllers

Shipping files with files:

A Telo.Application or Telo.Library may declare a files: list to ship files alongside telo.yaml — bundled controllers, a built SPA served by Http.Static, templates, seed data. Without it, only the manifest reaches the registry and a relative Http.Static root: resolves to an empty directory on the consumer.

kind: Telo.Application
metadata: { name: todo-app, version: 1.0.0 }
files:
  - public/**          # ship the built frontend
  - "!**/*.map"        # but not source maps

files: entries are ordered, .gitignore-style patterns (the same ignore engine git uses): positive patterns opt files in, ! patterns carve them out, last match wins. They are resolved against the manifest directory. A small always-on set is never shipped regardless of patterns: node_modules/, .git/, .telo/, .telobundle.*.

When files: selects anything, telo publish partitions the selection into layers and pushes each as its own blob: one layer per bundled-controller platform, one for whatever the optional assets: list claims, and one for everything else. It prints the partition so you can see where each file landed. The consumer then fetches only what it needs — a Node host skips a Rust controller's binary, a linux/amd64 host skips the darwin/arm64 one, and the asset layer is fetched only if something actually reads a file from it.

Declaring assets: (a subset of files:) is what makes those files lazy:

files:
  - nodejs/*.mjs
  - public/**
assets:
  - public/**

Omitting it is safe — the files still ship and still resolve, they are just fetched alongside the module's controllers instead of on demand.

telo run materializes layers on demand, so nothing has to be pre-fetched. telo install pre-fetches everything for one platform so a later run needs no network; pass --platform os/arch[/libc] (e.g. linux/arm64/musl) when baking an image for an architecture other than the build machine's.

Options:

  • --bump patch|minor|major — Bump all controller package versions before publishing. Also bumps metadata.version in the manifest.
  • --dry-run — Show what would happen without writing files or publishing anything.
  • --skip-controllers — Skip the controller build/publish/PURL-rewrite loop and only run static analysis and push the artifact to the OCI registry. Use this when controller packages have already been published by another tool (e.g. Changesets in CI). Mutually exclusive with --bump.

telo check <paths..>

Statically validates one or more manifests without running them. Uses the Telo analyzer to check schema correctness, x-telo-ref references, CEL expression types, and resource scope visibility. Exits with code 1 if any errors are found.

telo check ./manifest.yaml
telo check ./modules/my-module/telo.yaml
telo check https://example.com/manifest.yaml

Accepts local paths, directories containing a telo.yaml, or HTTP(S) URLs.

Imports resolve through the same .telo/manifests/ cache telo run reads, and check writes through to it after a successful load (disable with --no-cache-write). Freshness depends on how the import addresses its target:

  • Pinned (…@1.2.3#sha256-…, or an @sha256: reference) — verified against the hash on read, so a repeat check of a fully pinned manifest needs no network at all.
  • Mutable OCI tag — revalidated with one HEAD per reference against the digest recorded in .telo/manifests/.origins.json. If the tag has moved, that entry is dropped and refetched.
  • HTTP(S) URL — never read from the cache by check, since its key carries no version segment and a hit would be served indefinitely. Always re-fetched (one request, the same cost revalidating would have).

One loader serves every path in a single invocation, so telo check a b c resolves a module shared between them once.

Example output:

manifest.yaml:14:5  error    No Telo.Definition found for kind "Http.Srver".  UNDEFINED_KIND
manifest.yaml:22:7  warning  variables.port is declared but never referenced in any CEL expression.  UNUSED_DECLARATION

1 error, 1 warning

Every code is listed in the diagnostics reference, along with what triggers it and how to fix it.

On success:

✓  No issues found

telo install <paths..>

Pre-downloads every controller declared by a manifest and its transitive imports into the on-disk cache, and persists every imported manifest's YAML alongside it. At runtime the kernel finds each controller already installed AND resolves every import from disk — boot does zero network I/O.

Installs run in parallel; failures are reported per controller and the command exits non-zero if any failed. For a platform you NAME, a module layer that did not materialize is also a failure — a fetch that could not complete, or a layer constraining an axis the target leaves undetermined (pass --abi <family>-<version> beside --platform os/arch[/libc]). The tree such an install produces is the only cache its consumer has — a baked image, a packaged application — so a skipped layer would otherwise surface as a boot failure on another machine. Without --platform the target is this machine, whose ABI is used automatically, and a gap is a warning: telo run here still fetches what it needs. Subsequent runs are idempotent — already-cached packages are skipped, and manifest cache files are overwritten with freshly fetched bytes.

telo install ./apps/my-app/telo.yaml
telo install ./apps/a/telo.yaml ./apps/b/telo.yaml

Environment:

  • TELO_PKG_MANAGER — Override the package manager invoked for controller installs. Defaults to npm. Set to pnpm (or any compatible CLI) when the runtime environment ships a different manager.

The cache lives under the cache root.telo/ beside the manifest, or beside telo-workspace.yaml when one sits above it (see Global options):

  • .telo/npm/ — controller node_modules tree (one realm per manifest), for the modules that still deliver their controller from npm.
  • .telo/controller-src/ — bundles built from a local module's controller sources. Only ever written for a module that is a working copy on disk: a published module ships a prebuilt bundle in its artifact and never reaches this path. Each entry is named by a digest of every input the build read, so an edit anywhere in the source graph yields a new entry rather than invalidating one.
  • .telo/manifests/oci/<host>/<repo…>/<tag>/telo.yaml — manifests imported from an OCI registry.
  • .telo/manifests/url/<host>/<pathname> — manifests imported via raw HTTP URLs.
  • .telo/manifests/.origins.json — for each cached import named by a mutable tag, the OCI manifest digest that produced the copy. telo check revalidates against it with one HEAD per reference; a pinned import needs no entry, since its bytes are verified against the ref's own hash.

Every manifest entry is keyed <transport>/<host>/<path…>/<version>/<file>, the same grammar the discovery hub uses for its cached manifests.

The whole .telo/ tree is portable: COPY the directory that holds it into your image and both caches travel with it. Without a workspace marker that is the manifest's own directory; with one it is the marker's — copy that, or pin the location with TELO_CACHE_DIR in both build and production stages.

Example output:

Installing 20 controllers for apps/my-app/telo.yaml
  ✓  pkg:telo/local/js?path=./nodejs/server.mjs
  ✓  pkg:telo/local/js?path=./nodejs/request.mjs
  ...

✓  20 installed in 3.2s

A bundled controller (pkg:telo/local/js) ships inside its module's own artifact, so "installing" it is materializing that artifact's controller layer — nothing is fetched from npm. A pkg:npm candidate still resolves through the npm cache above.

Typical Dockerfile usage:

FROM telorun/node:latest-slim as build
WORKDIR /srv
COPY apps/my-app/ apps/my-app/
COPY modules/ modules/
RUN telo install apps/my-app/telo.yaml

FROM telorun/node:latest-slim as production
WORKDIR /srv
COPY --from=build /srv /srv
CMD ["telo", "apps/my-app/telo.yaml"]

Available image variants:

  • telorun/node:<version> — debian base, no rust toolchain.
  • telorun/node:<version>-slim — debian-slim base, no rust toolchain (smallest footprint; recommended for production).
  • telorun/node:<version>-rust-<rust-version> — slim + rust toolchain (controllers that compile native deps at install time).
  • telorun/node:<version>-rust-<rust-version>-slim — the same image, under its historical name.

Pin to an exact CLI version for reproducible builds; latest, <major>, and <major>.<minor> are rolling tags.

The build stage materializes .telo/npm/ and .telo/manifests/ under the cache root; the production stage is a single COPY and does no network I/O at boot.


telo upgrade <paths..>

Scans one or more manifests for remote imports: entries — an OCI ref (oci://host/repo@tag) — asks each ref's transport for the latest published version, and rewrites the source in place when a newer version is available. Both the scalar shorthand (Alias: <src>) and the object form (Alias: { source: <src>, … }) are handled. Version enumeration, ref reconstruction, and integrity hashing are all delegated to the transport that owns the ref's scheme, so every backend Telo can resolve is also upgradeable — the command never special-cases a scheme. The rewrite operates at the byte level: only the version characters of changed source values are spliced into the original file. Comments, indentation, folded block scalars (>- / |), quote style on the source value, and every other byte outside the rewritten ranges are preserved exactly. The on-disk YAML is mutated only when at least one import in the file changes.

Accepts the same path shapes as check / install: a manifest file, a directory containing a telo.yaml, or several of those mixed. By default only the imports declared in the files you pass are inspected; pass --recursive / -r to also follow relative (local) imports into their sibling manifests and upgrade those too.

telo upgrade ./apps/my-app/telo.yaml
telo upgrade ./apps/my-app                       # directory → ./apps/my-app/telo.yaml
telo upgrade ./apps/a ./apps/b --dry-run
telo upgrade ./apps/my-app --recursive           # follow ./relative imports too
telo upgrade ./manifest.yaml --include-prerelease

Options:

  • --include-prerelease — Consider versions with a SemVer prerelease segment (e.g. 1.0.0-beta.1) when picking the latest. Off by default — prereleases are ignored unless the flag is set.
  • --dry-run — Show the proposed rewrites without touching any files.
  • --recursive, -r — Follow relative (local) imports and upgrade their manifests too. Cycle-safe, and each file is upgraded at most once even when reached from several manifests. Remote refs (OCI / HTTP) are always upgraded in place; recursion only descends into on-disk siblings.

Behavior per import:

| Pinned version state | Action | Log marker | | --- | --- | --- | | Equal to the latest published | leave unchanged | = already at <ver> | | Lower than the latest, and itself published | rewrite to latest | ↑ <old> → <new> | | Not present in the origin's version list | rewrite to latest (repair) — flagged with (pinned version not published). Direction can be downward if the broken pin is higher than anything published. | or | | Module not found (404) / no eligible versions after filtering | leave unchanged, report | ! no published versions | | Remote ref with no comparable version — a bare https:// URL, an OCI digest pin (@sha256:…), or a moving tag like latest | leave unchanged | · skipped (not version-pinned) / ! unparseable current version | | source is a relative / absolute local path | leave unchanged (or follow under --recursive) | · skipped (local import — use --recursive to follow) | | source is not a remote ref at all | leave unchanged | · skipped (not a remote ref) |

A non-existent pin is always treated as broken and repaired against the origin — leaving an unbootable pin in place would defeat the point of the command — but the rewrite is annotated so the action is visible. Network or non-404 errors are surfaced per import and produce a non-zero exit code; other imports in the same file still get processed.

Example output:

Upgrading apps/my-app/telo.yaml
  ↑  oci://ghcr.io/telorun/run  0.2.4 → 0.2.7
  ↑  oci://ghcr.io/telorun/http-server  0.19.1 → 0.20.0
  =  acme/lib  already at 2.0.0
  ↓  oci://ghcr.io/telorun/foo  9.9.9 → 0.4.1  (pinned version not published)
  !  oci://ghcr.io/telorun/does-not-exist  no published versions
  ·  ../sibling  skipped (local import — use --recursive to follow)

3 upgraded, 1 already current, 2 skipped

telo migrate <paths..>

Rewrites legacy spellings in a manifest to their current form.

Telo occasionally renames something a manifest can say — a schema annotation, a keyword, a value grammar. Because published artifacts carry the old spelling and cannot be edited, the loader rewrites every legacy spelling in memory on the way in: the old form and the new one behave identically, on every kernel. telo migrate applies the same rewrites to your file, so the source says what the runtime already reads.

Running it is never a prerequisite for your manifest to work. It is a repair of the text, offered because telo check reports each legacy spelling as a deprecation warning and a warning you cannot act on is not actionable.

telo migrate ./manifest.yaml
telo migrate ./apps/my-app                  # directory → ./apps/my-app/telo.yaml
telo migrate ./apps/a ./apps/b

The rewrite operates at the byte level, exactly as telo upgrade's does: comments, indentation, folded block scalars (>- / |), quote style and every byte outside the rewritten ranges are preserved. A file with nothing to migrate is not touched.

Scope. Only the manifest you name and its include: partials are rewritten. Imported modules are left alone — a published dependency is not yours to fix, and its author is the only person who can. That is the same rule that decides which deprecation warnings telo check shows you.

Example output:

ref-slot-scalar-type  modules/mine/telo.yaml  3 rewrites

3 rewrites in 1 file. Imported modules were not touched.

A rewrite the loader applies in memory can still be unwritable in place — a flow-style sequence has no item line to extend, a block scalar's span covers the newline that ended its mapping entry, and two rewrites whose byte spans overlap cannot both be spliced. Those locations are reported on stderr rather than skipped silently, since the diagnostic that sent you here says to run this command; each needs a hand edit:

!  ref-slot-scalar-type  modules/mine/telo.yaml  targets  could not be rewritten in place (the YAML there cannot carry the edit) — fix it by hand

Under -o json the payload is { ok, rewrites, unwritable, files: [{ file, rewrites: [{ migration, count }], unwritable: [{ migration, path }] }] }.


telo package <manifest>

Build a single executable that runs one application on a machine with no Node.js, no telo and no network. The file is the released telo binary for the chosen platform with the application's payload inside it: the manifest, every local file its graph reaches, and the whole resolved import closure, already warmed.

telo package ./telo.yaml --out dist/orders
telo package ./telo.yaml --out dist/orders --platform linux/arm64
telo package inspect dist/orders

Options:

  • --out <file> - Where to write the executable. Required; .exe is added for a windows platform.
  • --platform <os/arch[/libc]> - The platform to package for (linux/amd64, linux/arm64/musl, darwin/arm64, …), in the same vocabulary telo install --platform takes. Defaults to the host; on linux an omitted libc means gnu. A darwin platform can only be packaged on a macOS host, because the payload rides in a Mach-O segment and the binary is re-signed afterwards.

The binary carries the telo that packaged it, enforced rather than assumed: there is no flag to choose another version, and only a released telo may download a carrier. A working copy and the release of the same version number are different code, so from a source checkout you build that checkout's own binary (pnpm --filter @telorun/cli build:standalone) and package with it; that binary is the carrier for its own platform, and packaging for another platform needs an installed release. A payload carries only controllers that are files on the target machine: a closure whose kind is delivered from npm (pkg:npm) or built from a crate (pkg:cargo) is refused, naming the kind and the candidate.

Running the result: every argument belongs to the application, .env files are read from the working directory, and signals and exit codes behave exactly as under telo run. TELO_APP_INFO=1 ./orders prints what the binary carries; TELO_APP_DIR moves the directory it unpacks into (~/.cache/telo/apps/<name>-<digest> by default). TELO_CACHE_DIR is ignored inside a packaged app — the payload is its cache.

Full guide: Packaging an application.


telo run <path> (default)

Load and run a Telo manifest. run is the default command, so telo ./manifest.yaml and telo run ./manifest.yaml are the same thing.

Arguments:

  • path - Path to a YAML manifest file, a directory containing telo.yaml, or an HTTP(S) URL.

Options:

  • --watch, -w - Watch manifest file(s) for changes and restart automatically
  • --debug - Write every kernel event, in order, to .telo.debug.jsonl next to the manifest. No network, no UI — the file is the machine-readable record of a run. telo run is exempt from -o json (the app owns stdout/stderr), so this is the machine surface for a run.
  • --inspect[=[host:]port] - Start the live inspection endpoint (default 127.0.0.1:9230), serve the inspection UI and open it in a browser. Holds the app open even when it would otherwise exit. The endpoint streams event payloads — inputs and outputs of every call — so binding a non-loopback host is warned about; mark credentials with x-telo-sensitive so they ride the wire as [redacted].
  • --no-open - With --inspect, do not open the browser.
  • --no-cache-write - Validate in-memory and read the existing .telo cache, but never persist new derived entries (compiled validators, analysis stamp). For ephemeral, read-only runs (e.g. a prebuilt container whose deps are baked at TELO_CACHE_DIR); the cache is still used, only writes are suppressed.
  • -- - Everything after it is handed to the kernel as the application's own arguments, for controllers that read them.

See Global options for --verbose, -o and the cache root.


telo search <query>

Search resource kinds across every module the hub tracks, matched on name and description. This is how you find a kind before you know which module owns it.

Arguments:

  • query - What the resource should do, in plain words.

Options:

  • --kinds - Flat kind hits, one line per resource kind, instead of grouping by module
  • --json - Emit the hub response as JSON
  • --hub-url <url> - Base URL of the hub. Overrides TELO_HUB_URL.
telo search "key value store"
oci://ghcr.io/telorun/kv-store@<version>  —  Durable key/value storage with atomic conditional writes…
  Store  (Provider)  A configured durable key/value store: read a value with its version token…
oci://ghcr.io/telorun/cache@<version>  —  Caching of computed or fetched values under a key…
  Store   (Provider)   A configured cache backend that holds values under a key…
  Lookup  (Invocable)  Reads a cached key and reports the outcome as a miss, a fresh hit or a stale hit…

Each hit prints the exact ref to paste into your imports: map, and the kinds that ref makes available.


telo module <subcommand> <ref>

Inspect a module without importing it. <ref> is any form the runtime resolves: a local path, an oci:// ref, or a direct URL.

| Subcommand | What it prints | | --- | --- | | versions | The module's published versions, newest first (one entry for a local path or direct URL) | | manifest | The module's telo.yaml, verified against the inline hash when the ref is pinned | | digest | The version's content-identity digest — a cheap read that downloads no payload | | resources | The resource instances the module declares | | kinds | The resource kinds the module defines, with capability and whether each is exported |

telo module versions oci://ghcr.io/telorun/console
telo module kinds oci://ghcr.io/telorun/console@<version>

telo cel <subcommand>

Inspect and evaluate Telo's CEL environment — useful for checking an expression's syntax and result without wiring it into a manifest first.

| Subcommand | What it does | | --- | --- | | functions | Lists the CEL standard-library functions available in manifests | | eval <expression> | Evaluates an expression — the body of a !cel scalar |

telo cel eval "'a-' + string(1+2)"
# a-3

eval runs with no manifest loaded, so variables, resources, steps and the rest of the manifest scope are not available; it is for the pure-expression half of CEL.

Examples

Simple HTTP Server

Create a file server.yaml:

kind: Telo.Application
metadata:
  name: Example
  version: 1.0.0
imports:
  Http: oci://ghcr.io/telorun/http-server@<version>
  Run: oci://ghcr.io/telorun/run@<version>
targets:
  - !ref Server
ports:
  http:
    env: PORT
    default: 8080
variables:
  audience:
    env: AUDIENCE
    type: string
    default: World
---
kind: Http.Server
metadata:
  name: Server
port: !cel "ports.http"
mounts:
  - path: /api
    mount: !ref HelloApi
---
kind: Http.Api
metadata:
  name: HelloApi
routes:
  - request:
      path: /hello
      method: GET
    handler:
      kind: Run.Value
      # `bindings:` names a temporary value: computed once, read by bare name
      # wherever this resource's expressions need it.
      bindings:
        who: !cel "variables.audience"
      value:
        message: !cel "'Hello, ' + who + '!'"
        greeted: !cel "who"
    returns:
      - status: 200
        content:
          application/json:
            body:
              message: !cel "result.message"

Run it:

telo server.yaml

Access it at http://localhost:8080/api/hello

Watch Mode for Development

telo --watch ./manifest.yaml

In watch mode, the manifest is reloaded and the kernel restarted whenever any manifest files change. This is useful while developing.

Remote Manifests

You can run manifests directly from URLs without downloading them:

telo https://example.com/my-manifest.yaml