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.3.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.

Downloads

2,169

Readme

@conjureos/pack

The anchor-app toolkit for ConjureOS. Two tools in one package:

  1. conj-pack dev — a zero-config esbuild dev server (no Vite) for developing an anchor app locally. See Local dev server below.
  2. conj-pack (the packer) — validate and package a web app into a .conj for the ConjureOS importer. For anyone who wants to hand someone an app that imports cleanly — not just first-party anchor apps.

Anchor apps use this package as their only build-time dependency: they develop with conj-pack dev, and CI builds the published artifact with ConjureOS @bundle (identical to a user-published app). They do not carry Vite.

Local dev server

npx -p @conjureos/pack conj-pack dev
# or, in an anchor app whose package.json has "dev": "conj-pack dev":
npm run dev

Serves the app from source with esbuild + live reload. It resolves the entry the same way the packer/importer does (src/main.tsx → .ts/.jsx/.js → src/index.*), so what runs locally matches what CI bundles. Intended as the first-party answer to "how do I run this without Vite" — keeping anchor apps off a second toolchain that would diverge from the @bundle build. Rationale: ConjureOS DECISIONS 2026-06-04.

The packer

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, .devserve — conj-pack dev's own output, sourcemap included — .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 my-app   # writes ./my-app (--name my-app also works)
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(); @conjureos/ui wired in (a sync:ui script, run on install, vendors its sheet into src/conjureos-ui.css, imported by relative path because the store build cannot serve a bare CSS import); tsconfig, .gitignore, a LICENSE, and dev/pack/deploy scripts.

| Flag | Default | Effect | |---|---|---| | --license MIT\|UNLICENSED | MIT | MIT writes a LICENSE file; UNLICENSED writes none and marks the package proprietary | | --author <holder> | The authors | Copyright holder in the MIT LICENSE |

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.

conj-pack deploy: pack and publish in one step

export CONJUREOS_URL=https://<ref>.supabase.co CONJUREOS_ANON_KEY=<anon key>
export CONJUREOS_ACCESS_TOKEN=<your token>   # or CONJUREOS_EMAIL + CONJUREOS_PASSWORD
npx conj-pack deploy --first     # first publish: creates the listing + version 1
npx conj-pack deploy             # every later version (bump package.json version first)

Runs the packer's checks (type-ref compile, validation), bundles the app exactly as the store does, uploads the HTML as your account and registers the version through the store's store-version endpoint, the same server-side choke point the in-app publish uses. The tier gate, semver guard and security scan run there, so deploy cannot skip them. Failures are named:

| Headline | Cause | |---|---| | tier gate | The account's plan cannot publish | | semver guard | package.json version is not above the published one | | security scan failed | The server scan rejected the HTML (verdict + issues printed) | | size limit | The upload was refused for size | | no store listing found / a listing with that slug already exists | Missing or extra --first |

Other flags: --slug, --store-app-id, --changelog, --display-name, --description, --visibility public|private, --tags a,b, --strict, --dry-run. SUPABASE_URL, SUPABASE_ANON_KEY and PUBLISH_BOT_* are read too, so anchor-app CI secrets work unchanged. Exit codes: 0 published, 1 rejected, 2 usage/config.

Hostname binding seam. --host <name> is reserved: when apps can be served at a URL, deploy is where a hostname is claimed and bound to the version it just published (PublishOptions.host in the library). Today it exits 2 before building.

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, src/index.jsx, src/index.js, index.html. conj-pack dev looks for the same set minus index.html — it bundles a JS/TS entry with esbuild and can't serve an HTML-only entry the way pack bundles one.
  • 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:icons", 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, ActionParamSchema — discover() 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