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

@conjureos/pack

v0.2.0

Published

Anchor-app toolkit for ConjureOS: a local dev server (esbuild — `conj-pack dev`, no Vite) plus a packer that validates a web app and writes a .conj for the importer. A .conj is just a DEFLATE zip with a metadata sidecar — rename it to .zip to inspect.

Readme

@conjureos/pack

Validate and package a web app into a .conj for the ConjureOS importer. It's for anyone who wants to hand someone an app that imports cleanly — not just first-party anchor apps.

A .conj file is just a DEFLATE-compressed ZIP with one extra file: a conjureos.pack.json sidecar at the root. Rename a .conj to .zip and any unzip tool opens it. The sidecar carries the format version, packer version, packed-at timestamp, and a normalized copy of the app's manifest so the importer can trust the artifact on sight instead of re-walking every entry.

Why bother with the wrapper?

Dragging a raw project ZIP into ConjureOS works today, but the importer has to (a) strip whatever node_modules / dist / .git the contributor accidentally bundled, (b) hope the bundler doesn't choke on stray debris, and (c) guess at a display name. .conj shifts all of that left to a one-shot package step:

  • Validation runs once at package-time, not on every import.
  • Dev-only directories (node_modules, dist, .git, .vite, .DS_Store, .env* except .env.example, *.tsbuildinfo, *.log) are stripped automatically — the 70 MB fitness ZIP becomes a few hundred KB.
  • Size ceilings are enforced (default 10 MB, configurable).
  • The format is versioned so future importer changes can refuse stale artifacts cleanly instead of trying to limp along.

CLI

No flags required. From the project root:

cd my-app
npx -p @conjureos/pack conj-pack
# wrote /path/to/my-app.conj (212483 bytes, 47 source files)

That writes <package-name>.conj next to your package.json. Drag it onto the ConjureOS Import dialog and it loads. The optional flags:

npx -p @conjureos/pack conj-pack [SOURCE_DIR] [options]

  -o, --out <path>        Output .conj path. Default: <pkg-name>.conj in cwd.
      --max-bytes <size>  Source-size limit. Accepts B/KB/MB suffix. Default: 10MB.
      --strict            Require params AND returns schemas on every action
                          (always on for apps declaring a `needs` block).
      --skip-validation   Bypass validation (use sparingly).
  -h, --help              Show help.

# exit 0  success
# exit 1  validation failed (bad/missing package.json, no entry point, too big,
#         unresolvable manifest type ref, malformed needs)
# exit 2  argument / runtime error

conj-pack init — scaffold a new app

npx -p @conjureos/pack conj-pack init --name my-app   # writes ./my-app
cd my-app && npm install && npm run dev

Generates a working self-describing app: a conjureos block with one typed example action (provides) and one example need (consumes), both referencing interfaces in src/manifest-types.ts; typings for window.__conjureos; an entry that registers the action handler and calls actions.discover(); tsconfig + dev/pack scripts. The target directory must be new or empty — init never overwrites existing work. npm run pack on a fresh scaffold produces a valid .conj with zero edits.

Library

import { pack, unpack, validate } from "@conjureos/pack";
// walkSource needs node:fs, so it lives behind the Node-only subpath:
import { walkSource } from "@conjureos/pack/node";

// 1. From a Node directory tree:
const files = await walkSource("./my-app");
const result = pack(files); // throws PackValidationError on bad input
await fs.writeFile("my-app.conj", result.bytes);

// 2. From an in-memory FileMap (e.g. browser):
const v = validate(files);
if (!v.ok) { /* show v.errors to the user */ }

// 3. Inverse:
const { files, meta, formatSupported } = unpack(conjBytes);

What validate() enforces

Validation mirrors what the ConjureOS importer actually rejects. Hard errors (pack fails) — these are the things that break an import:

  • package.json exists and parses.
  • An entry point exists at one of: src/main.tsx, src/main.ts, src/main.jsx, src/main.js, src/index.tsx, src/index.ts, index.html.
  • Total uncompressed size is under maxBytes (default 10 MB). 80% of limit emits a warning.

Everything about the conjureos block is optional — it's authoring sugar the importer derives or re-reads itself, not an import gate:

  • No conjureos block? displayName is derived from package.json name (falling back to "Untitled app") and icon defaults to "fa:cube", with a warning.
  • displayName / icon present but blank/malformed → derived default + warning.
  • permissions / actions / promptSuggestions present but malformed → the bad field (or the single bad action) is dropped with a warning. Unknown permissions warn (the kernel ignores them).

pack() calls validate() first and throws PackValidationError on failure (caller can inspect .validation for issues).

Action contracts (conjureos.actions)

Each action is an endpoint other apps and the AI orchestrator can invoke. It declares a permission (actions.read / actions.write) plus:

  • description — required. An action with no non-empty description is dropped (with a warning): the orchestrator matches user intent against this text, so an action without one is unroutable. The description must be substantive, not just the action name restated — validate() warns when a description is very short or only echoes the action name (e.g. action logMeal described as "Log meal"). Say what it does and when to use it, so the router can tell it apart from sibling actions.
  • params — optional JSON-Schema (recommended). The input shape the orchestrator fills. Object root; typed scalars / enums / arrays / nested objects with required + bounds. When absent, validate() nudges (the model must infer the shape from prose). Recommend a description on any property whose meaning or format isn't obvious from its name (dates, slugs, units, semantic enums) — e.g. "date": { "type": "string", "description": "YYYY-MM-DD; defaults to today" }.
  • returns — optional JSON-Schema (advisory). The result shape; documents the endpoint and supports future multi-step chaining. No nudge when missing.

A malformed params/returns schema is dropped with a warning (the action still publishes off its description) — schemas never hard-fail a pack unless the app is self-describing (it declares needs, or you pass --strict); see the next section.

"logRecipeMeal": {
  "permission": "actions.write",
  "description": "Log a saved recipe as a meal by slug, copying its per-serving nutrition into the diary and marking the recipe cooked.",
  "params": {
    "type": "object",
    "properties": {
      "slug": { "type": "string", "description": "The Recipes app slug of the saved recipe." },
      "servings": { "type": "number", "minimum": 0.1, "maximum": 20, "description": "Defaults to 1." }
    },
    "required": ["slug"]
  },
  "returns": { "type": "object", "properties": { "id": { "type": "string" }, "logged": { "type": "boolean" } } }
}

Self-describing apps (needs & provides)

Phase 45: an app's conjureos block describes both sides of its data contracts, and the platform connects them at runtime.

  • Provides = the app's actions, each with typed params + returns.
  • Needs = shapes the app wants to CONSUME from other apps:
"conjureos": {
  "needs": [
    { "id": "recipeSource",
      "description": "Saved recipes this app can pull into the meal planner.",
      "type": "RecipeSource" }
  ],
  "actions": {
    "logFood": {
      "permission": "actions.write",
      "description": "Record a food the user ate in today's diary.",
      "params": "LogFoodParams",
      "returns": "LogFoodResult"
    }
  }
}

At runtime the kernel matches a need's shape against installed actions' returns — an exact structural match connects free; a near-miss can connect through a user-confirmed AI field map. Apps call window.__conjureos.actions.discover(needId), which resolves to ProviderMatch[] ([] when nothing satisfies the need — always handle it).

Lean authoring: TS types instead of inline schemas

needs[].type and each action's params/returns accept either an inline JSON schema (the pre-0.2 form, still valid) or a string naming an exported TypeScript type. Resolution rules:

  1. The name must be a single exported interface, type alias, enum, or class — bare name, no paths or generics.
  2. src/manifest-types.ts is searched first (the convention — put your contract types there), then every .ts/.tsx the project tsconfig includes (alphabetically; no tsconfig means all of them). .d.ts files are skipped.
  3. Types must be self-contained within the app's source — a type that leans on an npm import won't resolve.
  4. JSDoc comments on properties become the schema descriptions — write them for the orchestrator, they ship.

Supported shapes map onto the schema subset: primitives (string, number, boolean), literal unions → enum, | null → type arrays, optional (?) props → omitted from required, arrays, nested objects/intersections, and Record<string, …> → open objects. Not supported (build error naming the file and type): Date/Map/Promise/functions, tuples, unions of objects, recursion, any/unknown. TS has no integer and no bounds — use an inline schema when you need "type": "integer", minimum, minLength, etc.

conj-pack (and conj-pack dev, at startup) compiles the refs and emits a fully-expanded manifest — inline schemas only — into the .conj's rewritten package.json and sidecar. A ref that doesn't resolve is a build failure. Building a FileMap yourself? Run the same expansion first:

import { compileManifestTypes } from "@conjureos/pack/node";
const { files, issues, changed } = await compileManifestTypes(rawFiles);

Strictness (and back-compat)

Declaring a needs block opts the app into the full contract: every action must declare both params and returns, and malformed schemas become errors — those schemas are what the platform matches on. Legacy apps (no needs) keep warnings-only behavior so published apps still pack; pass --strict to get the errors without declaring needs. An uncompiled type-ref string is always an error.

Types for app authors

@conjureos/pack re-exports the shared runtime types from @conjureos/bridge: AppNeed, ProviderMatch, ActionParamSchemadiscover() returns Promise<ProviderMatch[]>. The conj-pack init scaffold ships matching window.__conjureos typings.

File format

my-app.conj            (deflate ZIP, .conj extension)
├── conjureos.pack.json
│   {
│     "formatVersion": 1,
│     "packerVersion": "0.0.1",
│     "packedAt": "2026-05-31T00:00:00.000Z",
│     "name": "conjureos-fitness",
│     "version": "0.2.0",
│     "description": "...",
│     "manifest": { "displayName": "Conjure Fitness", "icon": "...", ... },
│     "files": ["src/main.tsx", "src/App.tsx", ...]
│   }
├── package.json
├── src/
│   ├── main.tsx
│   └── ...
└── ... (everything else `walkSource()` kept)

Format versioning

formatVersion bumps when the sidecar's required shape changes in a non-additive way. The importer reads it and refuses unknown versions rather than silently misinterpreting fields. Additive sidecar fields don't bump the version — old importers ignore unknown keys.

Roadmap / out of scope (TODO)

These were deliberately left out of the initial extraction. They're tracked here so the next contributor doesn't have to rediscover them:

  • [ ] Forbidden-API static scan. Block eval, raw fetch outside the SDK, and similar escape hatches at pack-time instead of leaving them for the runtime sandbox to catch.
  • [ ] Trial-build gate via @conjureos/bundle. Packing succeeds today even if the bundler would fail to build the source — packing only checks shape, not buildability. A pre-pack trial build would shift that failure left. The importer's bundle step is still the source of truth for build correctness until then.
  • [ ] Signature / publisher attestation. .conj from anyone is treated identically right now; there's no provenance beyond the embedded packerVersion. Signing belongs at the App Store layer.
  • [ ] Browser-side pack(). Today only unpack() runs in the browser (it's pure fflate + JSON). The producer side (pack/walkSource) is Node-only because walkSource needs node:fs. A future browser-callable pack() (fed an in-memory FileMap instead of a directory) would let ConjureOS itself produce .conj artifacts without shelling out to the CLI.

License

MIT