@napplet/cli
Standalone CLI for creating, configuring, inspecting, and deploying napplets.
@napplet/cli is the command-line deploy and diagnostics tool for built napplets. It creates .napplet/config.yaml, discovers built index.html artifacts, inspects deploy plans, signs manifest events, uploads files to Blossom servers, publishes to relays, and runs local napplet tooling such as conformance and Paja.
The standalone binary runs every built-in command without separately installed Node.js, npm, npx, Deno, Git, Kehto, or browser tooling. Screenshot, conformance, and Paja commands download a private, checksum-verified Node runtime and maintained packages on first use; browser commands also provision Chromium. Linux captures include the browser libraries and fonts. Downloads live in the CLI cache and never install into your project or global PATH.
First use of these tools requires network access and a writable cache. Later invocations reuse the downloads. The default cache is ~/Library/Caches/napplet on macOS, $XDG_CACHE_HOME/napplet (or ~/.cache/napplet) on Linux, and %LOCALAPPDATA%\napplet on Windows. Set NAPPLET_CACHE_DIR to choose another location; copying a populated cache between machines with the same OS and architecture supports offline tooling. User-configured conformance.command, paja.command, and app-server commands passed to Paja use the software you explicitly select.
The generated Vite project and optional skills.sh CLI still need their own development dependencies. Agent skills are installed separately — see Agent skills.
- npm:
@napplet/cli - JSR:
@napplet/cli - Source: packages/cli
Install
Install a checksum-verified standalone binary without Deno:
# macOS or Linux
curl -fsSL https://napplet.run/install.sh | sh# Windows PowerShell
irm https://napplet.run/install.ps1 | iexThe installers download from the stable CLI release and verify the asset against its SHA256SUMS. Linux and macOS support x64 and ARM64; Windows supports x64.
Check the installed version
napplet --version
# Also available: napplet -v or napplet versionThis prints the version of the CLI you actually invoked. If you have both standalone and Deno installations, use type -a napplet on macOS/Linux or Get-Command napplet -All in PowerShell to check which executable takes precedence. A version command reported as unknown means that installation predates version reporting.
JSR/Deno alternative
deno install --global \
--allow-read --allow-write --allow-run --allow-env --allow-net \
--name napplet \
jsr:@napplet/cli/cliTo refresh an existing JSR installation, rerun the command with --force --reload after --global. Then check the Deno-installed executable directly (~/.deno/bin/napplet --version with Deno's default install location); another napplet earlier on PATH is unaffected. See Deno's install reference for custom install locations and upgrade flags.
Then open the developer guide or check the command reference:
napplet guide
napplet --helpThe permission set is explicit because deploys need to read build output and config, write temporary deploy state, run local helper commands, read signing environment variables, upload to Blossom servers, publish to relays, and connect to remote signers.
Quick start
Run the same path for every new project:
napplet create my-napplet
cd my-napplet
napplet init
npx skills add napplet/web
pnpm install
# Ask your agent to build the napplet.
pnpm verify
napplet deploy --dry-run
napplet deploynapplet createdelegates to the maintained starter generator without setting deploy metadata.napplet initowns name, title, optional description, canonical archetype conventions, and network targets in.napplet/config.yaml. In an interactive terminal it guides setup and shows live suggestions from relays such aswss://relaypag.es, and suggests Blossom servers from kind10063server-list events.npx skills add napplet/webinstalls thenapplet-*agent skills for whichever coding agents you use; it is the skills.sh CLI, not anappletsubcommand.napplet debugprints resolved config, discovered napplets, deploy targets, manifest templates, and signing readiness without network writes. Manifest template failures (for example a missing description, or built files a current deploy would drop) are reported inmanifests.errorinstead of aborting, and--format current|legacyselects the event format inspected.napplet deploy --dry-runbuilds the same deploy plan and signed manifest events without uploading or publishing.napplet deployuploads files to configured Blossom servers and publishes signed root, named, and optional snapshot manifest events to configured relays.- When no signer flag or stored signer exists, interactive
napplet deploystarts the NIP-46 connection flow and stores the paired remote signer when native key storage is available. --prompt-secreads hidden input until Enter; when.nappletnames a bunker pubkey/npub, a mismatched prompted signer requires interactive confirmation and fails closed in non-interactive runs.
Commands
napplet guide
napplet create <directory> [--template <path-or-url>] [--force]
napplet init [--force] [--root] [--source-dir <dir>] [--name <dtag>] [--title <title>] [--description <text>] [--archetype <napplet:archetype/intent>] [--relay <url>] [--server <url>] [--proxy <url>]
napplet discover [--config <file>] [--all]
napplet debug [--format current|legacy] [--config <file>] [--all] [--root] [--name <dtag>] [--snapshot] [--sec <secret>]
napplet deploy [--format current|legacy] [--config <file>] [--all] [--root] [--name <dtag>] [--snapshot] [--sec <secret>] [--prompt-sec] [--zapstore | --no-zapstore] [--no-screenshot] [--screenshot [<preview-url>]] [--proxy <url>] [--dry-run]
napplet keys store --name <ref> [--sec <secret> | --prompt-sec]
napplet keys connect --name <ref> [--relay <url> ...] [--proxy <url>] [--config <file>]
napplet keys use --name <ref> [--config <file>]
napplet keys list
napplet keys delete --name <ref>
napplet keys doctor
napplet conformance [--config <file>] [--all] [-- <args>]
napplet paja [--config <file>] [-- <args>]Layouts
For a single napplet repository, discovery checks sourceDir and prefers dist/index.html, falling back to a top-level index.html.
For workspaces, set discover.roots and use --all. Each discovered napplet deploys under its own folder name as the named d tag.
YAML configuration and JSON migration
Project settings live in .napplet/config.yaml. YAML supports comments and uses the same configuration fields as before. Use --config path/settings.yaml or --config path/settings.yml for a custom file. Configuration updates preserve comments on unchanged values.
When a command needs configuration and finds the old JSON file, it asks before migrating. Answer y to continue with YAML; n or Enter exits with Cannot continue without migrating. Migration retains the exact original bytes in config.json.bak, preserves file permissions, and removes the active JSON file only after writing YAML. Conflicting YAML or backup files stop migration without overwriting them. To roll back for an older CLI, move the YAML file aside and restore the JSON backup.
CI, redirected input, --json, and -i / --non-interactive do not prompt for migration. Approve conversion explicitly with --migrate-config:
napplet init --migrate-config -i
napplet deploy --config settings.json --migrate-config --dry-runAn explicit legacy --config settings.json path resolves to settings.yaml after migration, including on later invocations. The original command continues using YAML. Help and version remain available before migration. The napplet migrate command handles signed manifest events separately; event files and machine-readable output remain JSON.
Archetype conventions
Pass each --archetype option as a queryless convention URI, for example napplet:profile/open. The CLI derives the role from the URI. YAML configuration accepts URI strings directly:
metadata:
archetypes:
- napplet:profile/openThe CLI normalizes each URI to an object such as { "slug": "profile", "convention": "napplet:profile/open" }. Existing object configs remain supported; add "params": ["pubkey"] to an object to advertise accepted parameter names. Older role-prefixed CLI inputs remain supported for compatibility.
This example emits the independent advertisements:
["z", "profile"]
["i", "napplet:profile/open"]This non-normative guide follows the living NAP-INTENT manifest catalog contract. A convention is eligible for dispatch when its archetype segment matches a z role on the same manifest. Metadata convention identities are queryless and fragment-free. Trailing i values advertise parameter names, not event-kind restrictions or parameter values.
See also
@napplet/vite-plugin— emits build-side manifest metadata the CLI preserves during deploy.@napplet/conformance-cli— the default command behindnapplet conformance; the standalone CLI provisions it in its private cache without a host package manager or global conformance executable.- Getting started — scaffold, build, and verify a napplet before deploying.
Current and legacy event formats
From CLI 0.7.0, deployments default to the current NIP-5D artifact-hash schema. Interactive deploys ask for current or legacy; scripts can choose with --format current|legacy. A non-empty description is required for current event content. Legacy output remains isolated in a temporary serializer; 0.6.x and earlier defaulted to legacy events. Current deploys upload only /index.html plus the blobs referenced by icon, screenshot and preview tags; any other built file in the deploy directory makes deploy fail with the list of those files (build a single-file artifact or use --format legacy). When config metadata or the Vite plugin sidecar lists a domain as both required and optional, it is emitted only as O, matching the Vite plugin.
napplet deploy --dry-run --format current
napplet deploy --format legacy
napplet migrate signed-event.json --optional theme --output migration-preview.jsonmigrate verifies the source signature and writes an unsigned preview with provenance. It does not publish or modify the artifact, and refuses to overwrite an existing output file. Review description, required/optional choices and pointers before signing. See the migration guide for format differences and shell data/consent implications.
Repository source metadata
For current-format deploys, the CLI adds one source tag from the Git origin of the repository containing each selected build when neither config, the Vite sidecar, nor HTML already supplies it. Nested builds and Git worktrees are supported. HTTPS, HTTP, ssh://, and scp-style clone URLs are converted to HTTP(S) repository URLs; inferred URLs omit credentials, query strings, fragments, and the .git suffix. SSH remotes use HTTPS without the SSH port. Git is queried locally; no remote is contacted. If Git, a repository, or a usable origin is unavailable, the tag is omitted. Other remotes are not selected, and SSH host aliases are not expanded; configure their public source URL explicitly.
Set metadata.source in .napplet/config.yaml to override every inferred or embedded source:
metadata:
source: https://github.com/example/my-nappletUse source: false in that object to omit the tag, including any sidecar or HTML source. A source override must be one absolute HTTP(S) URL without credentials or a NIP-34 nostr:// clone URL; arrays are not accepted. The precedence is config → sidecar → HTML → Git origin. Root, named, and companion snapshot events retain the resolved source. This follows the current NIP-5D manifest table, which permits zero or one source tag. Automatic Git inference does not apply to --format legacy; an explicit config override still does. These are CLI defaults, not additional protocol requirements.
NIP-5D §Source also allows one source-commit pin and one source-archive. Set them explicitly; the CLI never infers a commit, because a dirty or unpushed tree would pin a revision nobody can fetch:
metadata:
source: https://github.com/example/my-napplet
sourceCommit: 0123456789abcdef0123456789abcdef01234567
sourceArchive:
url: https://dl.example.com/my-napplet-src.tar.gz
sha256: c0ffee…
mimeType: application/gzipConfig values replace matching sidecar tags. sourceCommit must be a full lowercase-hex Git commit ID (40 or 64 characters); sourceArchive.url must be an absolute HTTPS URL without credentials or a fragment, with the archive's sha256 and MIME type. Deploy refuses malformed or repeated pins rather than signing tags clients ignore. A source-commit is only meaningful next to a repository source.
Standalone HTML recovery
The CLI reads the head mappings from NIP-5D, HTML Metadata for Publishing, including title, description, named identifier, roles, intents with advertised parameters, required/optional domains, source, server hints, and supported PNG/JPEG/WebP data-URL icons. This is non-normative implementation guidance; the living specification remains authoritative. Embedded metadata is optional, remains untrusted publishing input, and does not replace signed manifest verification at runtime.
For current-format deployment, explicit config metadata overrides matching sidecar categories, which override matching head categories. Missing categories fall back to HTML. Explicit target/name selection and configured names override napplet-id; a single-project named deploy without a configured name uses the sidecar identifier, then the head identifier, before the existing default fallback. Monorepo traversal retains folder-based naming and filtering. Configured Blossom servers override embedded hints; when none are configured, recovered server hints supply upload destinations. The CLI validates selected destinations before manifest signing or upload: each must be a non-empty HTTP(S) origin without credentials, a path, a query, or a fragment; a trailing slash is accepted. Invalid destinations produce an error identifying their config or manifest-hint position. Relay and signing configuration are still needed for publication.
Signed or unsigned sidecars can contain additional Nostr tags. Deploy preserves their full string payloads, order, and repeated values through signing and companion snapshots; migration preserves them too. Config overrides and CLI-managed publication fields still apply, but tag names are not restricted to a napplet allowlist. A current event's extra path, description, requires, or archetype tag is not a legacy publishing instruction. Extra description tags remain separate from event content. These are implementation notes; NIP-01 and the living NIP-5D proposal define the event format.
Root and named events omit lineage tags. Companion snapshots retain the CLI's selected source address as their parent; head provenance does not replace that explicit selection. Root/snapshot events omit the named identifier. Deployment does not rewrite the HTML when config or selection overrides its hints: it hashes and uploads the original bytes and signs the resolved metadata separately.
Decoded icon bytes are hashed and uploaded unchanged alongside the HTML, even when index.html is the only file in the deployment directory. No extracted icon file is written. If a sidecar overrides the icon, embedded bytes are used only when both hash and MIME type match that declaration. Remote, unsupported, empty, malformed, and MIME-mismatched icon URLs are ignored. Hash-only sidecars still require the matching blob to be supplied separately. Consumers verify decoding before display according to NIP-5D. Legacy-format deployment retains its previous title/description HTML fallback behavior.
Deployment manifests (current and legacy, including snapshots) and optional Zapstore application events include ["client", "napplet.run"] before signing. This identifies the publisher using the NIP-89 client tag and does not change artifact hashes. Dry-run templates include the same attribution.
Presentation media
NIP-5D §Presentation media describes screenshots and one preview video on the manifest itself, each by the sha256 of its bytes with optional m, dim, size, alt and (for the preview) duration fields. Current-format deploys handle them in three ways:
- Sidecar or configured
screenshot/previewtags whosexhash matches a built file are uploaded with/index.html(the Vite plugin writes supplied media todist/<sha256>.<ext>). Entries clients would ignore (no usablex, an undecodablem, more than onepreview) fail the deploy instead of being signed. metadata.screenshotsandmetadata.previewpublish local files kept outside the build directory. Paths resolve relative to the working directory, are hashed, sized, format-sniffed (PNG, JPEG, WebP; WebM or MP4 for the preview) and measured, and replace any sidecar entries of the same kind; an empty list clears them.- Automatic deploy capture (below) adds its PNG as a
screenshottag unlessmetadata.screenshotsis configured.
metadata:
screenshots:
- shots/board.png
- path: shots/editor.webp
alt: Editing a note
preview:
path: shots/demo.webm
duration: 8.5migrate drops unusable screenshot and preview metadata from a converted event and says so in its notes. These are CLI behaviors, not protocol requirements; clients verify hashes and decoding before display.
Screenshots and Zapstore metadata
Capture a built napplet without starting a host. The command starts a temporary local Kehto Paja preview, captures its iframe, and closes the preview afterwards. The CLI provisions its private runtime and Chromium automatically on first use, without host Node.js or npx. Screenshot and default conformance commands select the tested @napplet/conformance-cli@0.3.3 runner explicitly, so an older project-local conformance dependency cannot intercept them. Explicit custom conformance commands remain supported. The preview uses Paja’s development identity, fresh browser storage, and live relays, so content may differ from your signed-in shell.
napplet screenshot --output preview.png
# Optional: choose another project or an existing shell.
napplet screenshot ./my-project --output other.png
# Optional: select one iframe and wait for content inside it.
napplet screenshot http://localhost:5173 --output ready.png --selector '#napplet-frame' --ready-selector '.app-ready'The default capture is 1200 × 750 pixels with a 1500 ms delay. Override these with --width, --height, and --delay (milliseconds). The command captures only the iframe, waits for fonts, closes Chromium on success or failure, and refuses to overwrite an existing file. Keep screenshots outside the build directory.
To customize the application listing, add a zapstore object to .napplet/config.yaml:
zapstore:
id: org.example.notes
name: Notes
description: Private notes in a napplet shell.
summary: A simple notebook
images:
- ./preview.png
- https://example.org/notes-mobile.png
icon: ./assets/icon.png
website: https://example.org/notes
repository: https://github.com/example/notes
license: MIT
tags:
- notes
- productivityThe zapstore object is optional. When omitted, deploy derives an application ID, name, and description from the selected manifest; a multi-build deploy uses the project directory name for its shared listing. Set id and name here to control that stable listing identity. Reusing an ID updates the listing under the signing pubkey. Configured fields take precedence over inferred values, and this configuration describes one application listing per deploy invocation.
napplet deploy --dry-run --json
napplet deploy
# Publish metadata without capturing a new screenshot:
napplet deploy --no-screenshot
# Skip both application metadata and automatic capture:
napplet deploy --no-zapstoreApplication publication and screenshot capture default to on. --no-screenshot skips capture but still publishes the application event, including configured images. --no-zapstore skips the application event and automatic capture. enabled: false in the zapstore object also disables both; --zapstore overrides that setting for one deployment. Supplying both Zapstore flags or combining --no-screenshot with explicit screenshot options is an error. Dry runs capture a preview and show the application template and resolved media URLs without uploading or publishing.
Local PNG, JPEG and WebP images are resolved relative to the working directory, hashed, and uploaded to the configured Blossom servers. Existing HTTP(S) image URLs are used as supplied. Local media URLs point to the first selected Blossom server; if those uploads fail, application publication is skipped and deploy exits nonzero even if another mirror succeeds. The application uses the same signer and relays as the deployment. A requested application event must be accepted by at least one relay for deploy to succeed.
The listing uses kind 32267 from the Software Applications proposal, with screenshot URLs in image tags. It is published as a separate event before the napplet manifests. The current NIP-5D manifest does not define an application reference tag, so deploy leaves manifest tags and artifact hashes unchanged. This publishes application metadata only, without software release or asset events; acceptance into a particular store's catalog depends on that store.
Deploy starts a local preview for each distinct selected build, captures a temporary PNG, appends it to the application images for that deployment, and adds the same capture to that build's current-format manifests as a NIP-5D screenshot tag (x, m image/png, dim, size) so the blob is uploaded once and referenced by hash. Configured metadata.screenshots keep their own list instead of the capture. Capture failure stops deployment before any upload or relay publication. The temporary image is removed after success or failure. Capture diagnostics go to stderr so --json stdout remains valid JSON. No host URL is required. Root and snapshot selections of the same artifact share one capture. To capture an existing shell instead, use --screenshot <preview-url>. Use --screenshot-selector, --screenshot-ready-selector, --screenshot-width, --screenshot-height, and --screenshot-delay for the corresponding capture options. Use the standalone napplet screenshot command when you want to keep a PNG on disk.
Proxies
deploy, init and keys connect accept --proxy <url> to send their Blossom server and relay traffic through an HTTP or SOCKS5 proxy. Supported schemes are http://, https://, socks5:// and socks5h://; embedded credentials (socks5://user:pass@host:port) are never printed. Use socks5h:// to resolve host names on the proxy, as Tor requires.
napplet deploy --proxy socks5h://127.0.0.1:9050
napplet keys connect --name remote --proxy http://proxy.example:8080The proxy carries artifact and media uploads, manifest and application-metadata publishing, init suggestions, and remote-signer (NIP-46) relay sessions with their relay information documents. Managed tools started as separate processes, such as screenshot capture, conformance and paja, are not proxied by it. Other commands reject the flag. This is CLI transport configuration; it does not change what is published.
Key storage without system helpers
Native keychains are used when available. Otherwise, key commands use password-encrypted local files in ~/.napplet/keys (AES-256-GCM, PBKDF2-SHA256 with 600,000 iterations). Enter a password of at least 12 characters at the hidden prompt, or set NAPPLET_KEYSTORE_PASSWORD for non-interactive use. The password is not stored; reuse it to unlock your keys. Set NAPPLET_KEYSTORE=portable to select this store explicitly and NAPPLET_KEYSTORE_DIR to change its location. napplet keys doctor reports the selected provider.
Interactive terminals and CI
Human terminal sessions show the napplet banner, grouped help, colored deployment summaries, and live upload/publish progress with filled █ blocks and a ░ remaining track. napplet deploy prompts for missing configuration values before signing, preserves existing values, and offers to save completed settings. Use napplet deploy --dry-run to preview without uploading or publishing.
Use -i or --non-interactive on any command to disable prompts. CI and redirected streams do this automatically. Missing required values produce actionable errors; supply them through configuration or --source-dir, --description, --relay, and --server. --json keeps deploy stdout machine-readable, and NO_COLOR=1 or --no-color disables colors.
napplet deploy -i --format current --json
napplet init -i --name notes --description "Private notes"Signing secrets remain hidden and are never saved in project configuration. --prompt-sec can consume explicitly piped input in automation. Set NAPPLET_KEYSTORE_PASSWORD for encrypted local keys; keys connect requires interactive remote approval, so CI should use an existing key or session.