{"_id":"@azad-ai/miniapp-sdk","name":"@azad-ai/miniapp-sdk","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@azad-ai/miniapp-sdk","version":"0.1.0","type":"module","sideEffects":false,"description":"Typed, framework-agnostic SDK for Azad V3 Mini App frontends: the app side of the azad.app/3 bridge.","exports":{".":"./src/index.ts","./protocol":"./src/protocol.ts","./cards":"./src/cards.ts"},"dependencies":{"@azad-ai/card-kit":"0.1.0","zod":"4.5.4"},"private":false,"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/kodu-ai/azad-ide-web.git","directory":"packages/miniapp-sdk"},"_id":"@azad-ai/miniapp-sdk@0.1.0","gitHead":"fe5d7a0bc794c4573be524943a64cf099e21b16c","bugs":{"url":"https://github.com/kodu-ai/azad-ide-web/issues"},"homepage":"https://github.com/kodu-ai/azad-ide-web#readme","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-56wagw44AbD7Y2HC1QlK8t26TtF+oxC7EETUfsTHksAWrzPdpVnOx8Zos3ta6Kj4JkYOW8xyeE64hPlEhOHOFg==","shasum":"751fd14383cf0fbc36bc7961cbc97e10361abb35","tarball":"https://registry.npmjs.org/@azad-ai/miniapp-sdk/-/miniapp-sdk-0.1.0.tgz","fileCount":5,"unpackedSize":85017,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAW0j9f5l6emdGe+yjh0obuG0h5iPGp+KthjofUQbQVSAiBM8VVOdZHjeNRok48eU4H4VyIf0wyixBCITY+SW9r5MA=="}]},"_npmUser":{"name":"matannah","email":"matanleague@gmail.com"},"directories":{},"maintainers":[{"name":"matannah","email":"matanleague@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/miniapp-sdk_0.1.0_1788756764121_0.9905218467000441"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-07T04:52:44.005Z","0.1.0":"2026-09-07T04:52:44.259Z","modified":"2026-09-07T04:52:44.520Z"},"maintainers":[{"name":"matannah","email":"matanleague@gmail.com"}],"description":"Typed, framework-agnostic SDK for Azad V3 Mini App frontends: the app side of the azad.app/3 bridge.","homepage":"https://github.com/kodu-ai/azad-ide-web#readme","repository":{"type":"git","url":"git+https://github.com/kodu-ai/azad-ide-web.git","directory":"packages/miniapp-sdk"},"bugs":{"url":"https://github.com/kodu-ai/azad-ide-web/issues"},"readme":"# @azad-ai/miniapp-sdk\n\nThe typed, framework-agnostic SDK for **Azad V3 Mini App frontends**. It\nis the app side of the versioned `azad.app/3` bridge: your app runs in a\nsandboxed iframe with an **opaque origin**, talks to the Azad host over a\nprivate `MessageChannel` port, and can do exactly what the bridge\nvocabulary admits. Default requests are host-proxied; no ambient cookie is used.\nThe explicit `backend.direct` capability additionally gives the frame a scoped\nviewer token for real HTTP to its own backend only.\n\n- Protocol schemas: `@azad-ai/miniapp-sdk/protocol` (shared\n  with the host, both sides validate every inbound message).\n- Authoring skill and downloadable SDK: `GET /api/authoring` on your Board API,\n  or `azad.authoring.read()` through MCP code mode. Install the returned\n  `miniapp-sdk.tgz` and `card-kit.tgz` together in an independent repository.\n- Source types in this package describe the current bridge. The public skill\n  covers manifests, mounts, agent definitions, resources, and recovery.\n\n## What a V3 Mini App is\n\nA package under `apps/<key>/` — `azad.app.yaml` (the only fixed\nlocation) plus whatever it references: a built frontend, a built Worker\nbackend, harness plugin roots (`spec.plugins`), migrations. `frontend`,\n`backend`, and `plugins` are each optional; a package declares at least\none. It is ingested as an immutable content-addressed artifact. The\nplatform:\n\n- serves your **frontend** from the app-assets lane at an immutable URL\n  (`/api/v3/app-assets/<instanceId>/<sha256-digest>/…`) — a new deploy\n  changes the digest, so every asset URL is cache-forever;\n- runs your **backend** as a dedicated Cloudflare Worker with its own D1\n  database on its own origin;\n- hands your frontend this SDK's bridge instead of any direct platform\n  access.\n\n## Quick start (complete authoring example)\n\n```ts\n// app/main.ts — built into your package's frontend tree.\nimport { init, MiniAppBridgeError } from \"@azad-ai/miniapp-sdk\"\n\nconst app = await init()\n\n// 1. Context: who/where you are. Display-convenience only — your\n//    BACKEND authorizes from the verified token the host attaches.\nrender(`Hello ${app.context.user.displayName} in #${app.context.channel.slug}`)\ndocument.documentElement.dataset.theme = app.context.theme.mode\napp.onContextChange((ctx) => {\n  document.documentElement.dataset.theme = ctx.theme.mode\n})\n\n// 2. Your own backend (dedicated Worker origin), host-proxied with the\n//    scoped app token. JSON in/out.\nconst queue = await app.backend.fetch(\"/api/items\")\nawait app.backend.fetch(\"/api/items\", {\n  method: \"POST\",\n  body: { title: \"Ship it\" },\n})\n\n// 3. Channel files (requires `files` in\n//    spec.capabilities.frontend.requests in your manifest). Scoped by\n//    the host to this channel — you never name a channel.\nconst { items } = await app.files.list()\nawait app.files.add({\n  name: \"report.md\",\n  contentType: \"text/markdown\",\n  content: \"# Weekly report\",\n})\n\n// 4. Emit a durable typed event toward a Role (declare the type in\n//    spec.events.emits, request `events.emit` backend capability).\nawait app.events.emit(\"item.approved\", {\n  roleKey: \"release-manager\",\n  payload: { itemId: \"42\" },\n  dedupeKey: `approve-42`,\n  offline: \"queue\", // hold + flush when connectivity returns\n})\n\n// 5. Role presence, derived from live connector leases: staffed |\n//    draining | offline (+ wakePendingSince while an unstaffed role has\n//    pending wake-worthy activity). { state: \"unavailable\" } is the\n//    honest fallback for unknown roles / failed reads — render it as\n//    \"presence unavailable\", never a fabricated dot.\napp.presence.watch(\"release-manager\", (presence) => {\n  renderPresence(presence.state)\n})\n\n// 6. Surfaces: the manifest declares where the app renders beyond its\n//    center tab (`spec.frontend.surfaces`: side panes, header actions,\n//    background frames). `app.surface` says which one THIS frame is;\n//    `surfaces.open` is the one deep-link vocabulary — own side pane,\n//    another app, another channel. The host honours at most the `ui`\n//    level you grant and toasts when the target is not mounted.\nif (app.surface.zone === \"side\") renderCompact()\napp.surfaces.open({ surfaceId: \"tasks\", path: \"/tasks/42\" })\n\n// 7. Navigation is host-mediated: board paths only, external links only\n//    to allowlisted https hosts (github.com today). Invalid targets are\n//    dropped by the host.\napp.navigate(`/projects/${app.context.project.id}`)\napp.openExternal(\"https://github.com/kodu-ai/azad-ide-web/pull/1\")\n\n// 8. Prefilled send-message: the host seeds the channel composer and\n//    brings the USER to chat — apps never post as the user silently.\nawait app.dialogs.sendMessage(\"The release queue needs a second approver.\")\n\n// 9. Visibility: kept-alive frames stay mounted while hidden and get NO\n//    visibilitychange — this signal is your only pause/refresh cue.\napp.onVisibilityChange((shown) => (shown ? startPolling() : stopPolling()))\n\n// 10. Degraded signalling — tell the host chrome honestly.\ntry {\n  await app.backend.fetch(\"/api/health\")\n  app.reportDegraded(null)\n} catch (error) {\n  if (error instanceof MiniAppBridgeError) {\n    app.reportDegraded(\"Backend unreachable; showing cached data.\")\n  }\n}\n```\n\n## Chat cards (`@azad-ai/miniapp-sdk/cards`)\n\nAn app can render cards inside Azad chat. A card is a real React component\nwritten against `@azad-ai/card-kit` — not layout JSON — that executes in THIS\nframe's sandbox and streams its element tree to the chat surface, which\npaints it with the platform's own components (DOM on web, native views on\niOS/Android). No iframe is ever mounted in the message list.\n\n```ts\nimport { init } from \"@azad-ai/miniapp-sdk\"\nimport { registerCards } from \"@azad-ai/miniapp-sdk/cards\"\nimport { taskCard } from \"./cards/task\"\n\nregisterCards([taskCard]) // before or after init(); both work\nconst app = await init()\n```\n\nWhat the host guarantees:\n\n- **The card never holds a credential.** `useAct`, `useSecretSink`,\n  `useHost().navigate` and `files.url` are RPCs answered by the chat surface\n  on the viewer's session.\n- **Secrets never enter the sandbox.** A `SecretInput` is real host UI; the\n  card only ever learns `hasValue`.\n- **Until the card is live, the user still sees it.** The chat paints the\n  card's server-derived fallback tree in the same reserved box and swaps the\n  live render in place — no spinner, no layout shift.\n\nPackaging (v1): build the definitions into a separate `cards/` entry, with\n`react` and `@azad-ai/card-kit` external, and call `registerCards` from that\nentry. Run `extractCardMetadata` and write the returned array to the sibling\n`manifest.json`; point `spec.cards.entry` at the built entry. Registering cards\nin the ordinary frontend bundle alone does not publish the card metadata the\nplatform uses to validate posts.\n\n## Manifest coordinates this SDK relies on\n\n```yaml\n# azad.app.yaml\napiVersion: azad.dev/v3\nkind: App\nmetadata:\n  name: release-queue\n  displayName: Release queue\n  version: 1.0.0\nspec:\n  compatibility: { azad: \"3\" }\n  frontend: { entry: frontend/index.html }\n  # backend and plugins are each optional — a package declares at least\n  # one of frontend/backend/plugins.\n  backend: { entry: backend/index.js, healthPath: /health }\n  plugins:\n    - key: release-operator # complete harness plugin root at plugins/<key>\n  capabilities:\n    frontend: { requests: [files] }\n    backend: { requests: [events.emit] }\n  events:\n    emits:\n      - type: item.approved\n```\n\nThe platform rejects any manifest field this release does not consume —\nan accepted declaration is always a working one.\n\n## Backend CORS requirement\n\n`app.backend.fetch` is performed by the HOST page (the Azad web origin)\nagainst your Worker's dedicated origin, with the scoped app token as an\n`Authorization: Bearer` header. Your Worker must therefore answer CORS:\n\n```ts\nconst CORS = {\n  \"access-control-allow-origin\": \"*\", // auth is the bearer token, never cookies\n  \"access-control-allow-methods\": \"GET,POST,PUT,PATCH,DELETE\",\n  \"access-control-allow-headers\": \"authorization,content-type\",\n}\nif (request.method === \"OPTIONS\") return new Response(null, { headers: CORS })\n```\n\n`*` is safe here because your backend authenticates every request by\nverifying the ES256 token (audience `azad-app-v3:<instanceId>`) against\nthe canonical board JWKS at `GET /api/v3/apps/jwks`.\n\n## Offline behavior\n\n- The host relays connectivity as online/offline bridge events —\n  `app.online` / `app.onOnlineChange`.\n- Reads: your app owns its caching. The platform **guarantees immutable\n  asset URLs** (content-addressed digests), so redeployments receive fresh asset identities.\n- Mutations: `events.emit` takes `offline: \"queue\" | \"reject\"` (default\n  reject). Queued emissions flush on reconnect with an auto\n  `dedupeKey`, and the platform emit lane dedupes on it, so idempotent\n  redelivery is safe. `backend.fetch` while offline rejects — queue\n  domain mutations in your own storage if your app needs more.\n\nThe frame has an opaque origin. Do not depend on service workers,\nlocalStorage, or cookies. Keep transient read caches in memory and persist\napplication data in the backend or through explicitly granted host APIs.\n\n## Hard rules the host enforces (so you don't have to guess)\n\n- Every bridge message is schema-validated with bounded sizes (backend\n  bodies ≤ 1 MB, event payloads ≤ 32 KB, file uploads ≤ 8 MB, bounded surface declarations). Oversized or malformed messages are dropped/refused, never\n  truncated.\n- Unknown message types are forward-compatible no-ops in both\n  directions.\n- The iframe sandbox is `allow-scripts allow-forms` — no popups, no top\n  navigation, no same-origin. All navigation goes through the host.\n- Files calls are refused unless your manifest declares the `files`\n  frontend capability.\n","readmeFilename":"README.md","_rev":"1-e1fde98857c8f162a6b3dd19a8a5ac6c"}