Appearance
Paja Getting Started
Paja runs one local napplet app inside the real Kehto browser runtime while your app dev server keeps its own HMR loop.
1. Install the CLI
bash
pnpm add -D @kehto/cli2. Add a Dev Script
Use the app server URL you expect Vite, Next, or another tool to serve:
json
{
"scripts": {
"dev": "kehto paja --target-url http://127.0.0.1:5173 -- pnpm vite --host 127.0.0.1"
}
}Run it:
bash
pnpm devOpen the printed Paja runtime URL, not the app server URL.
Paja sandboxes the target iframe without allow-same-origin, so the app fetches its own module scripts with Origin: null. Allow that origin in the dev server or the app's entry module is blocked and the frame stays blank — Vite needs server: { cors: { origin: '*' } } because its default allowlist covers only localhost origins. When the target would block the frame, Paja logs a paja.target.cors.error entry in Messages and warns on the browser console.
3. Check Identity
Paja does not generate or select a development signer by default. It auto-connects NIP-07 when a browser provider is available; otherwise the Signer panel starts disconnected and identity.getPublicKey returns the empty string until you select Dev, connect NIP-07 or Bunker, or configure the fixed read-only identity below. NIP-5D keeps signer APIs outside the napplet iframe, so every signing path remains shell-mediated; see the immutable NIP-5D authority at eb45dfd7335b7f88cb53781984c553581d2b4c34.
For a fixed pubkey, use:
bash
kehto paja \
--target-url http://127.0.0.1:5173 \
--identity-mode fixed \
--identity-pubkey 44444444444444444444444444444444444444444444444444444444444444444. Toggle Interfaces
Use Interfaces to turn individual window.napplet.<domain> injection on or off. Paja reloads the target after each toggle, and the next shell.init advertises the changed support surface.
shell is not an interface toggle. @napplet/shim@0.30.0 does not supply a generic shell API, so Paja's Kehto-owned prelude always installs mandatory window.napplet.shell before one bare shell.ready. It caches the first shell.init for local ready(), supports(), read-only services, and one-shot onReady() behavior.
5. Tune ACL
Use ACL to grant or revoke runtime capabilities such as state:write, notify:send, outbox:write, and upload:write. These controls write through Kehto runtime ACL state for the active napplet identity. Denials use each NAP's defined failure shape; for example, a denied outbox.query returns a correlated outbox.query.result with events: [] and error.
6. Watch Messages
Use Messages to filter inbound and outbound envelopes by type, domain, or payload text. The log includes target traffic plus Paja system events for interface toggles, ACL changes, signer changes, and signer/publish confirmations. Error envelopes show their error text directly in the row.
Intent Delivery in a Local Host
Paja's installed intent catalog contains resolver-verified manifest contracts; it is not the same thing as the live target iframe. A closed target can remain installed and be cold-started. Selection uses only exact compatible installed contracts: a compatible default can win, the host can ask a chooser, ambiguity is rejected, and an explicit d-tag needs sender-aware authorization.
Paja starts or reuses the verified target, waits for its current registered source and shell.ready, then sends exactly one target-only inc.event for the selected convention. The final result identifies the handled target. Stale/replaced targets and terminal failures stay in the host controller's retry/replacement policy and produce a canonical failed result.
7. Choose Upload Storage
The default upload backend is a memory simulator; it does not store bytes. For real Blossom uploads, select a writable signer in the Signer panel and run:
bash
kehto paja \
--target-url http://127.0.0.1:5173 \
--upload-mode blossom \
--upload-server https://blossom.examplePaja discloses the file, napplet, selected server, and public/durable effect before signing or sending it. Use HTTPS in production. Loopback HTTP is allowed for a local Blossom fixture, which must permit browser CORS preflight, PUT, Authorization, and Content-Type.
8. Publish Safely
Paja begins without a writable signer. Select Dev, use NIP-07 to connect a browser extension signer, or paste a bunker:// or nostrconnect:// URI and choose Bunker for a NIP-46 signer. Every sign, publish, Blossom upload, or external-link request opens Paja's serialized in-page confirmation dialog. Deny has initial focus and Escape denies. Signing defaults to one-time approval; for a napplet-attributed request, you can instead remember the exact event kind or trust every kind from that napplet identity and Paja target. The trust choice warns that future events will be signed without another prompt. Choices are isolated by signer pubkey, host-owned d-tag and aggregate hash, plus the verified runtime-pointer artifact or exact direct-target URL. Direct-target trust survives code reloads at that URL. Forget remembered approvals revokes every saved choice; if browser storage refuses deletion, Paja keeps it listed and logs the failure. A full Paja host reload creates another ephemeral Dev signer and asks again, while the same NIP-07/NIP-46 account can reuse saved choices. Publish and other operation prompts remain independent. A configured fixed pubkey remains read-only unless the connected signer proves the same pubkey.