Skip to content
nappletPublic

Repository files navigation

napplet is alpha. The specification is experimental and a moving target. There will most certainly be drift between packages and the specification. Things will most certainly change. For adventurers only.

napplet

CI AI Slop Score Publish Publish to JSR

Monorepo for the napplet SDK -- libraries for building NIP-5D Nostr Web Applets - "napplets"

A napplet is a sandboxed web app that runs inside a shell. The shell and napplet communicate over a MessagePort that the injected runtime hands to the shell in a single napplet.hello, using a JSON envelope format ({ type, ...payload }) defined by NIP-5D. The napplet never touches localStorage, relay connections, or signing keys directly -- the shell proxies everything through NAP interfaces. Read about napplets in NIP-5D possiblity

NAP proposal status and shipped implementation maturity are separate. All capabilities can change. Inspect NAP_IMPLEMENTATIONS from @napplet/core and use selectNapDomains for local host opt-in policy. See maturity and opt-in guidance.

Build a napplet

Install the standalone CLI; Deno is not required:

# macOS or Linux
curl -fsSL https://napplet.run/install.sh | sh

# Windows PowerShell
irm https://napplet.run/install.ps1 | iex

Run napplet --version (also -v or version) to identify the installed CLI. Run napplet guide for the current workflow and links to the relevant docs, or follow the same path directly:

napplet create my-napplet
cd my-napplet
napplet init
npx skills add napplet/web     # agent skills via skills.sh (Claude Code, Codex, Cursor, …)
pnpm install
# Ask your agent to build the napplet, then verify and preview it.
pnpm verify
napplet deploy --dry-run
napplet deploy

napplet create downloads the maintained Vite + TypeScript starter. napplet init owns deployment name, title, description, archetype roles and conventions, relays, and Blossom servers in .napplet/config.yaml. The standalone CLI provisions its own cached tooling for screenshots, conformance, and Paja, so no host Node.js, npm, npx, Deno, Git, or Kehto installation is required. Node.js 20+ is needed by the generated Vite project and the optional skills.sh CLI.

The CLI supports napplet screenshot --output preview.png. napplet deploy captures a screenshot and publishes a separate Zapstore application event by default; use --no-screenshot and/or --no-zapstore to opt out. See the CLI guide for configuration, browser setup, and dry-run examples.

Use the napplet-icon skill to design, generate, and refine an app icon, check it at small sizes, and prepare it for NIP-5D integration. Install it with npx skills add napplet/web --skill napplet-icon; see the agent skills guide for the complete workflow.

Use napplet-port-napp to port an eskema/nostrapps NAPP through bridge replacement, implementation and behavior verification. Install it with npx skills add napplet/web --skill napplet-port-napp.

Event schema compatibility

Current event defaults begin at CLI 0.7.0, Vite plugin 0.15.0, conformance 0.18.0, and conformance-cli 0.3.0. Earlier release series use legacy path/aggregate manifests. The event kinds remain 5129/15129/35129; current events use the artifact SHA-256 in x, a description in content, and R/O plus z/i metadata. CLI deployments offer temporary --format legacy support; unattended deployments default to current. See the migration guide for event conversion, legacy pointers and shell storage/ACL implications.

Packages

Package npm JSR Description
@napplet/core npm JSR JSON envelope types (NappletMessage, NapDomain), NAP dispatch infrastructure (registerNap, dispatch), protocol constants and Nostr types. Imported by all other packages.
@napplet/shim npm JSR Runtime-side helper for injecting selected window.napplet.<domain> objects before napplet code runs. Sends JSON envelope messages via postMessage.
@napplet/sdk npm JSR Named TypeScript exports wrapping window.napplet for bundler consumers. Provides domain wrapper objects and NAP message type re-exports, including relay, inc, storage, cvm, outbox, upload, intent, ble, webrtc, link, count, lists, common, serial, fs, dm, and llm.
@napplet/nap npm JSR Compatibility package for active NAP domain subpaths (shell, relay, storage, inc, ifc, keys, theme, media, notify, identity, config, resource, cvm, outbox, upload, intent, ble, webrtc, link, count, lists, common, serial, fs, dm, llm) with barrel + granular (types/shim/sdk) exports. Tree-shakable (sideEffects: false). Includes ownership-aware media and resource, the ContextVM cvm bridge with registry helpers, outbox-aware outbox relay routing, shell-mediated upload, archetype intent dispatch, runtime-mediated BLE/WebRTC, link opening, event counts, list mutations, common social actions, serial device access, shell-mediated virtual filesystem access, direct messages, user-selected language model inference, and read-only identity helpers. See packages/nap/README.md for the full subpath reference.
@napplet/vite-plugin npm JSR Vite plugin for current NIP-5D manifests: hashes the final index.html artifact, emits description content and capability/intent metadata, and defaults to single-file output.
@napplet/cli npm JSR Standalone CLI for creating projects, owning deploy metadata, discovering builds, and deploying signed manifests. JSR/Deno remains an alternative install route.
@napplet/boilerplate npm — Project-only generator behind napplet create; clones the maintained Vite + TypeScript starter and derives its package name without setting deployment metadata.
@napplet/conformance npm JSR Framework-agnostic conformance engine: hand-written envelope validators for the active NAP wire domains, a signed manifest-event validator, a scriptable reference mock shell, the zero-config check catalog, and pretty/JSON/JUnit reporters. Browser-safe; reused by both the CLI and the web runtime.
@napplet/conformance-cli npm — Headless napplet-conformance runner. Drives the engine against a napplet in real Chromium (Playwright) and sets a CI exit code — wire it up as test:conformance. npm-only (Playwright dependency).
@napplet/conformance-web npm — Browser conformance runtime deployed at /conformance and bundled into napplet-conformance --ui. Runs the conformance engine live in the page with a check tree, envelope log, and manifest inspector.
@napplet/lab — — Private tooling for probing napplets and the shells that host them. Ships lab napplets under packages/lab/napplets/*, starting with the Security Lab (@napplet/lab-security): a napplet that probes the sandbox, CSP, namespace, runtime and identity-revocation boundaries of whatever shell loads it, plus a headless Kehto Paja harness. Published as NIP-5D manifests, not to npm.

Conformance testing

Napplets can verify they conform to the NAP protocol before publishing, in two scopes that share one engine:

# Headless / CI — exits non-zero on any error-severity failure:
npx napplet-conformance ./dist
# App variant — opens the live web runtime and re-runs on every change (like vitest --ui):
npx napplet-conformance --ui . --exec "vite build --watch"
// package.json — works with pnpm / npm / yarn / bun:
{
  "scripts": {
    "test:conformance": "napplet-conformance ./dist",
    "test:conformance:ui": "napplet-conformance --ui . --exec \"vite build --watch\""
  }
}

The same web runtime ships standalone (apps/conformance, deployed at /conformance) and runs the checks live in the browser with a visual report. v1 is zero-config protocol conformance: signed manifest-event validity, boots under sandbox="allow-scripts", receives a runtime-injected window.napplet, every emitted envelope is well-formed, graceful degradation when a domain is absent, and no forbidden globals.

Changelog

  • Conformance tooling — new @napplet/conformance engine + @napplet/conformance-cli runner + standalone apps/conformance web runtime let a napplet self-verify NAP protocol conformance headlessly (CI) and live in the browser. Hand-written per-NAP envelope validators (drift-guarded against @napplet/nap), manifest checks, a reference mock shell, and a test:conformance-ready CLI.
  • v0.32.0 — Read-Only NAP-IDENTITY — identity.getPublicKey() is a snapshot that resolves to a hex pubkey or "" when no user is connected, and identity.onChanged(handler) receives shell-pushed identity.changed updates. Identity no longer exposes decrypt, encrypt, or signing operations.

Architecture

Package Dependency Graph

@napplet/shim ──► @napplet/nap ──► @napplet/core
@napplet/sdk  ──► @napplet/core

@napplet/vite-plugin  (build-time only, depends on nostr-tools)
@napplet/cli          (Deno deploy and diagnostics tool)

@napplet/boilerplate  (CLI generator, clones github.com/napplet/boilerplate)
@napplet/conformance-cli ──► @napplet/conformance + @napplet/conformance-web
@napplet/conformance-web ──► @napplet/conformance

Runtime Injection And Napplet-Side Communication

Shell runtime                              @napplet/shim
  ShellBridge                                window.napplet.outbox (query/subscribe/publish)
  ├── JSON envelope message routing          window.napplet.inc   (topics/channels)
  ├── Identity bound to the hello port       window.napplet.storage (get/set/remove)
  ├── ACL enforcement                        window.napplet.resource (bytes/bytesMany/bytesAsObjectURL)
  ├── NAP dispatch (outbox/relay/storage)    window.napplet.domain presence
  └── INC routing

◄────────── window: { type: 'napplet.hello' } + one MessagePort (once) ─────►
◄────────── port:   { type: 'outbox.subscribe', id, filters }       ─────────►
◄────────── port:   { type: 'outbox.event', subId, result }         ─────────►

@napplet/vite-plugin (build time)
  └── NIP-5D manifest generation + R/O capability advertisements

The iframe uses sandbox="allow-scripts", without allow-same-origin, as specified by NIP-5D Transport. Napplets cannot access the host shell's DOM, cookies, localStorage, or service workers. All persistent state goes through the shell's proxies.

Intent dispatch

NAP-INTENT accepts convention URIs through invoke(uri, options?) and open(uri, options?). The binding derives the role, action, and stable convention identity before dispatch.

const result = await window.napplet.intent?.open('napplet:profile/open', {
  payload: { pubkey: 'abc123' },
  behavior: { focus: true, reuse: false },
});
if (!result?.ok) throw new Error(result?.error ?? 'intent not accepted');
window.napplet.intent?.onDelivery(({ sender, convention, payload }) => {
  console.log(sender, convention, payload);
});

Acceptance transfers delivery responsibility to the runtime; target delivery is retained until onDelivery registration and does not depend on the source remaining alive. Candidates expose opaque catalog identifiers and manifest-derived parameter contracts. NAP-SHELL provides optional environment information without gating other domains. This non-normative orientation defers to the living NAP-INTENT and NAP-SHELL.

Origin

The napplet protocol is defined by the living NIP-5D specification; the NAP capability domains are defined on the NAPs track. Any shell can host napplets by injecting window.napplet before napplet scripts run. Napplet application code consumes injected domains directly or through @napplet/sdk.

Development

pnpm install
pnpm build        # Build all packages via turborepo
pnpm type-check   # TypeScript validation
napplet create my-napplet # Scaffold a new napplet from the template repo

Publishing

Publishing runs from GitHub Actions. Prepare release metadata locally, then merge the release changes into main. Every push to main runs the JSR publisher, which skips versions already present in the registry and publishes missing versions regardless of the commit title. Manual dispatch remains available to retry a failed release. npm publishing runs through Changesets; the Deno CLI is distributed through JSR and standalone GitHub release binaries.

pnpm version-packages   # Apply changesets, bump versions

Website

Run pnpm dev:site to start the website and documentation together. Each launch chooses open ports through the OS and prints both URLs. The website uses its paired documentation server's actual address; concurrent launches can run side by side. Ctrl+C stops the servers started by that command. Standalone website and documentation commands also select open ports.

The informational site and package documentation live in apps/:

  • apps/web -- Astro ecosystem showcase with runnable napplets, shells, and developer tools; /protocol/ lists merged NAPs and open proposals with detail pages and contribution guidance. The original Svelte explainer lives at /explainer/. Run pnpm generate:protocol to refresh the NAP directory and build the website; the deployment workflow also refreshes it daily. See website development and verification.
  • apps/docs -- VitePress documentation, served under /docs.
  • apps/conformance -- the standalone conformance web runtime, served under /conformance.
pnpm --filter @napplet/web dev             # showcase and explainer
pnpm --filter @napplet/docs dev            # documentation
pnpm --filter @napplet/conformance-web dev # conformance runtime

Documentation development prebundling uses the same esnext target as production builds and source transforms. Keep optimizeDeps.esbuildOptions.target aligned with the other docs Vite targets when updating the toolchain.

scripts/assemble-site.mjs creates the same static artifact for link checks and .github/workflows/deploy-site.yml: showcase at /, original explainer at /explainer/, docs at /docs/, and conformance at /conformance/, plus Markdown copies of every page, /llms.txt and the /.well-known/agent-skills/ index for agents (Agent access). Bunny is the canonical deployment target, with Accept: text/markdown negotiation in the edge/ script; nsite is an optional mirror. Configure deploy secrets with scripts/setup-site-secrets.sh.

Related

  • NIP-5D -- the living napplet-shell protocol specification (source of truth)
  • NAPs track -- where every NAP capability domain is proposed and defined

License

MIT

Controller snapshots

This non-normative guidance defers to NAP-GAMEPAD. When the runtime exposes window.napplet.gamepad, the SDK provides cached getGamepads(), read-only available and focused, and onChange(handler) with an independently closable listener. Keep keyboard or pointer controls available when the domain or native controllers are unavailable. Ignore gameplay edges while unfocused, and reset the button baseline on regained focus. Preserve null slots and route controllers by their index.

import { gamepad } from '@napplet/sdk/gamepad';

if (window.napplet?.gamepad) {
  const subscription = gamepad.onChange(state => {
    if (!state.available || !state.focused) return;
    for (const pad of state.pads) {
      if (pad) console.log(pad.index, pad.axes);
    }
  });
  // Call subscription.close() when the owning view is disposed, not right away.
}

Declare gamepad as optional (O) when other controls fulfill the core task. Use a hard requirement (R) only when the core task needs controller input. The runtime owns controller access and focus isolation; app code does not call native navigator.getGamepads() or open raw HID devices. Haptics and remapping are outside this proposal.

Language model support

The packages expose the proposed NAP-LLM through window.napplet.llm, @napplet/sdk, @napplet/sdk/llm, and the layered @napplet/nap/llm subpaths. Napplets ask the user to select a model connection, submit stateless generations (optionally with tool definitions the napplet itself executes) with optional streamed deltas, and cancel them; the runtime supplies the connections, credentials, disclosure consent, and provider access. See the non-normative LLM guide.