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/bridge

v0.3.0

Published

Single-source ConjureOS app bridge: the canonical wire-protocol types and the injected ES5 shim (window.__vfs / window.__conjureos), parameterized by transport (desktop iframe postMessage or react-native-webview). Desktop and mobile import this instead of

Downloads

511

Readme

@conjureos/bridge

Single source for the ConjureOS app bridge: the canonical wire-protocol types and the injected ES5 shim (window.__vfs.*, window.__conjureos.*), parameterized by transport. Desktop and mobile import this package instead of vendoring copies of each other's bridge code.

Kernel-side dispatchers (the trusted routers that validate messages and enforce permissions) stay platform-specific and are out of scope here — this package is only what goes over the wire and what gets injected into the app document.

What's in the box

  • protocol.ts — every message an app puts on the wire (vfs.request, ai.request, report.request, auth.request, notify.request, actions.*, native.request) and every result shape the kernel returns, plus runtime type guards (isVFSRequest, isAIRequest, ...). The native.request / native.response pair is part of the canonical protocol: mobile serves it, desktop stubs it with an "unsupported" response.
  • schemaCompat.ts / fieldMap.ts — pure, dependency-free helpers for Phase-45 self-describing apps: the structural needs↔provides checker (schemaSatisfies) and the rename-only field-map applier/validator (applyFieldMap, validateFieldMap). See below.
  • shim.tsbuildShim(opts) emits the <script> block injected into every app's HTML <head>. The output is deliberately ES5 (no arrow functions, template literals, or let/const): it runs before any transpiled app code, in whatever the browser or device WebView ships.
  • fixtures/bridge-parity.html — the cross-platform behavioral fixture. Install it as an app on desktop or open it via the mobile runner; every bridge op must pass or cleanly skip. A hang or unexpected shape is a bug.

The two transports

The app-facing API is identical on both platforms — same globals, method names, timeouts, and result shapes. Exactly one thing differs: how bytes move between the app document and the kernel.

| | transport: "iframe" (desktop) | transport: "react-native-webview" (mobile) | |---|---|---| | app → kernel | parent.postMessage(msg, '*') | window.ReactNativeWebView.postMessage(JSON.stringify(msg)) | | kernel → app | window message events | injected window.__conjureosDeliver(msg), called via webViewRef.injectJavaScript |

import { buildShim } from "@conjureos/bridge";

// Desktop (ConjureOS src/kernel/sandbox.ts): splice into the app HTML head.
const shim = buildShim({
  transport: "iframe",
  viewportMode: "desktop",
  signedIn: true,
  isAdmin: false,
  env: { usdaProxyUrl, recipesApiUrl },
});
const injected = html.replace("</head>", `${shim}</head>`);

// Mobile (conjureos-mobile src/bridge/shim.ts): prepend to the served document.
const shim = buildShim({
  transport: "react-native-webview",
  viewportMode: "mobile",
  signedIn: true,
  isAdmin: false,
  env: {},
});

Divergence knobs

Where the two platforms historically injected genuinely different behavior beyond transport, the difference sits behind an explicit option whose default preserves what each transport's platform ships today:

  • nativeBridge?: boolean — emit window.__conjureos.native (camera / photos / share, 120s timeout). Default true on react-native-webview, false on iframe (desktop's kernel stub for native.request is still pending; don't emit an API that would hang).
  • actionsTimeoutStyle?: "local" | "kernel" — actions-bridge timeout semantics. "local" (iframe default) is the desktop behavior: the shim's own timer is authoritative and invoke() rejects at timeoutMs with code: "TIMEOUT" ('invoke timed out after <N>ms'; register/list reject 'register timed out' / 'list timed out'). "kernel" (webview default) is the mobile behavior: the kernel's timer is authoritative and the shim keeps a backstop at timeoutMs + 1000 (generic 'actions.invoke timeout' messages, no code).

Everything else — VFS (5s), AI (60s idle, opt-in ai.chunk streaming), report (45s), auth (10s), notify (10s, resolves the deny shape rather than rejecting), error/console forwarding — is byte-for-byte shared.

AI: tool use + sampling (0.2)

ai.complete accepts optional sampling parameters and Anthropic-style tool use. All new fields are optional; the 0.1 payload shape is unchanged.

New request fields (AIRequestPayload):

  • temperature?: number — sampling temperature, 0–1.
  • topP?: number — nucleus sampling.
  • stopSequences?: string[] — max 4 custom stop sequences.
  • tools?: AIToolDefinition[] — JSON-Schema-typed tools the app offers the model: { name, description?, inputSchema } (name matches [a-zA-Z0-9_-]{1,64}; inputSchema is JSON Schema for the input object).
  • toolChoice?: AIToolChoice"auto" | "any" | "none" | { name }.

New result fields (AIDispatchResult):

  • stopReason?: "end_turn" | "max_tokens" | "stop_sequence" | "tool_use"
  • toolUses?: AIToolUse[]{ id, name, input } calls the model requested.

Tool execution is app-side. The platform never runs a tool for you. The loop is: the model returns toolUses (with stopReason: "tool_use") → the app runs its own code for each call → the app calls ai.complete again, appending an assistant message that echoes the toolUses and a user message carrying the answers as toolResults ({ toolUseId, content, isError? }):

const first = await __conjureos.ai.complete({
  system: "…", messages,
  tools: [{ name: "get_weather", inputSchema: { type: "object", properties: { city: { type: "string" } } } }],
});
if (first.stopReason === "tool_use") {
  const results = first.toolUses.map((u) => ({ toolUseId: u.id, content: runMyTool(u) }));
  const second = await __conjureos.ai.complete({
    system: "…", tools,
    messages: [
      ...messages,
      { role: "assistant", content: first.content || "", toolUses: first.toolUses },
      { role: "user", content: "", toolResults: results },
    ],
  });
}

AIChatMessage carries these turns without a content-blocks rewrite: content stays a plain string; toolUses? is meaningful on role: "assistant" and toolResults? on role: "user".

Note: platforms may reject stream: true (an onChunk callback) combined with tools in v1 — do the tool-use turns non-streaming.

Self-describing apps: needs & provides (0.3)

Apps self-describe in their manifest. Provides = the app's existing actions, each now carrying a typed returns?: ActionParamSchema next to params?. Needs = a new needs?: AppNeed[] array of data shapes the app wants to consume from other apps ({ id, description, shape } — authoring uses TS type refs; pack compiles them to the shape schema). There are no predefined named interfaces: the kernel matches needs↔provides purely structurally.

  • schemaSatisfies(provided, required) (schemaCompat.ts, pure) is the deterministic gate: a provider's returns that structurally satisfies a need's shape connects free (binding: "exact"). It's conservative on purpose — same primitive types (integer also satisfies number), object/array recursion, required arrays honored, extra provided fields fine, unknown constructs fail closed with dotted-path reasons.
  • Otherwise the platform AI proposes a rename-only field map (fieldMap.ts, pure): FieldMapEntry { from, to } dotted paths with at most one [] array segment (e.g. meals[].macros.kcalrecipes[].nutrition.calories). validateFieldMap rejects transforms, unknown paths, and type-incompatible renames; the user confirms; the kernel applies it via applyFieldMap (binding: "ai-mapped", with confidence).

App-facing API:

// Which provider actions satisfy my need? → ProviderMatch[]
const matches = await __conjureos.actions.discover("recipes-with-nutrition");
// [{ appPath, displayName, action, binding: "exact" | "ai-mapped", confidence? }]

// Invoke a provider; `normalize: <needId>` has the kernel apply the
// confirmed field map to the result (no-op for exact bindings).
const m = matches[0];
const data = await __conjureos.actions.invoke(m.appPath, m.action, {}, {
  normalize: "recipes-with-nutrition",
});

On the wire: actions.discover ({ type, id, needId }, guarded by isActionsDiscover) answered by actions.discover.response ({ ok, matches?, error? }, mirroring actions.list), and an optional normalize?: string on actions.invoke. All additive — 0.2 apps and kernels are unaffected.

Wire compatibility rule

Wire shapes are frozen per semver minor. An app bundle is the same artifact on desktop and mobile, and old kernels meet new apps (and vice versa) all the time. Within a minor line, no shape may change at all; a new message type or a new optional field is at least a minor bump; anything that would break an existing app or kernel — removing/renaming a field, changing a type, making an optional field required — is a major bump. When in doubt, add a new message type instead of mutating one.

Consuming repos

  • ConjureOS (desktop/web): src/kernel/sandbox.ts re-exports the protocol types from here and calls buildShim({ transport: "iframe", ... }) instead of concatenating its per-bridge IIFEs.
  • conjureos-mobile: src/bridge/protocol.ts and src/bridge/shim.ts become re-exports of this package (retiring the vendored copies and the check-protocol-drift pin).

Develop

npm install
npm run build   # tsc → dist/ (ESM + .d.ts)
npm test        # vitest: transport containment, ES5 checks, in-vm round trips