@everfur/sdk

中文文档
1.0.1发布于 2026/11/14SEE LICENSE IN LICENSE.mdTypeScriptESM + CJS近 30 天下载 2874

Embed Everfur veterinary chat, health records, and pet-state surfaces in your React Native app.

原始使用文档
文档目录8 节
  1. Install
  2. Bundler resolution
  3. On the web
  4. Quickstart
  5. The security model
  6. How feature access works
  7. Public entry points
  8. Reference app

@everfur/sdk (React Native and the web)

Drop Everfur's AI vet assistant and pet-health features (records, gait/video, photo checkup, pet-state media) into a React Native app, and the assistant into a website. What each of your customers and users can see is decided server-side by Everfur's entitlement control plane and rendered by the SDK, so you gate features without shipping an app update.

  • Hexagonal, framework-free core with a thin RN layer; every surface settles (no thrown domain errors).
  • Server-authoritative entitlements: a feature you are not entitled to renders a deliberate off-state, never a blank screen, and cannot be force-enabled client-side.
  • The app never holds a secret. See The security model.

This branch prepares a future release under the Everfur SDK License. Requires React Native >=0.74 (or React >=18 with react-dom for the web entry), and Node >=18 on the backend that mints sessions.

As of October 23, 2026, published SDK 0.14.0 and CLI/MCP 0.8.0 still contain MIT notices. Publishing these docs does not replace those artifacts. See the license rollout status. The version 2.1 acceptance flow is not yet available. Versions released under it will require acceptance in the partner console, Sandbox credentials for evaluation, and a written Partner Agreement for production. Those packages will include LICENSE.md, the full terms in SDK-LICENSE.txt, and THIRD_PARTY_NOTICES for bundled open-source components. Those components retain their own licenses.

Getting keys: open the partner console and sign in. Where Everfur has enabled sandbox sign-up, create a sandbox (test-mode) tenant. If sign-up is unavailable, ask your Everfur contact to create one. Live tenants and their keys are provisioned by the Everfur team. Contact [email protected] to get set up. The publishable key is designed to ship inside your app; the secret key is not.

Install

npm i @everfur/sdk
# A React Native app installs these itself (react-native and react-native-safe-area-context are declared
# optional peers, so a web project is not handed them):
npm i react react-native react-native-safe-area-context

Peer dependencies by capability (install only what you use):

CapabilityExtra peer depsNotes
chat, entitlementsnoneworks with react + react-native alone
recordsHost-supplied document picker and upload transportThe SDK does not import Expo file-system or image-manipulator modules.
video (gait), photo (checkup)react-native-vision-camerause 4.x on RN 0.74; 5.x requires the new architecture
televet in-app call@daily-co/react-native-daily-js and @daily-co/react-native-webrtc, plus Daily's own peerstested pair: daily-js ^0.88.0 with webrtc 124.0.6-daily.2 (exact); the peer ranges also admit daily-js 0.90 with webrtc 155; iOS needs UIBackgroundModes audio; Expo SDK 55 to 57 use @daily-co/[email protected]

The camera peer is optional: if you never mount ./video or ./photo, you do not need it. EverfurRecords accepts the host's picker and upload transport, so use the native modules that fit your app (including Expo SDK 57) without an SDK peer-dependency constraint. react-native-vision-camera needs a camera usage description; 4.x also ships an Expo config plugin (add it to app.json plugins), while 5.x ships none and instead needs its own peers react-native-nitro-modules and react-native-nitro-image.

Bundler resolution

No Metro configuration is required. Every subpath (@everfur/sdk/chat, /records, /televet, /video, /photo, /animations, /client, /core, /testing, /testing/rn, /testing/web) resolves on a stock install. Modern bundlers read the package exports map; Metro 0.80.x, which ships with Expo 51, defaults to unstable_enablePackageExports: false and never reads it, so the package also ships classic root-level resolution shims for the same subpaths. You do not need to turn package exports on, and turning them on does not change what you get.

@everfur/sdk/server is the one exception, deliberately: it is your backend module and carries the sk_partner_ session minter. If it is ever pulled into an app bundle it throws on import rather than shipping the minter to a device. Import it from your server only.

On the web

Two ways, one wire contract, documented in docs/partner-integration/13-WEB-INTEGRATION.md:

  • In-page, for a page whose React tree you own: import { EverfurProvider, EverfurChat } from '@everfur/sdk/web' (peers: react, react-dom). The copy-paste shape is examples/minimal-web.tsx. Take the /web entry, not the package root: the root is the React Native entry and imports react-native and react-native-safe-area-context, so in a browser project it fails to resolve them rather than rendering. The /web entries need react, react/jsx-runtime and zod and nothing native, which npm run verify:pack proves on every CI run by importing every one of them in a React web project.
  • Frame mode, for a page you do not fully control (a storefront theme, a CMS page): one <script src="https://sdk.everfur.com/v1/everfur.js"> puts the surface in an iframe on sdk.everfur.com, and the frame refuses to initialise on any page origin your tenant has not registered. The copy-paste shape is examples/frame-embed.html.

Chat and records are the web surfaces (records on its own subpath and its own frame document, available when Everfur enables records for your account); photo and video remain React Native for now. Vet visits (available when Everfur enables them for your account) have their own entry on both hosts, @everfur/sdk/web/televet and @everfur/sdk/televet, and a visit opens INSIDE your app by default: booking, the confirmation and the call run in an Everfur modal (React Native) or dialog (web) that VetVisitButton and EverfurChat mount themselves; the hosted page is an explicit opt-in, prefer="redirect". The booking screens (@everfur/sdk/televet/booking, @everfur/sdk/web/televet/booking) and the call (@everfur/sdk/televet/call, @everfur/sdk/web/televet/call) are their own entries. On the web they are loaded on demand, so a page where nobody opens a visit downloads neither and a page where nobody joins takes no WebRTC dependency; on React Native both are static imports in the same bundle (the WebRTC stack is the native module your app registers with registerEverfurDaily). Because EverfurChat from @everfur/sdk/chat mounts the visit host, a React Native app that imports it also bundles the booking and call screens; @everfur/sdk/chat/lite is the same chat without that host, for an app that mounts <EverfurTelevetHost /> itself or opens no visits. docs/partner-integration/16-TELEVET-VISITS.md.

Quickstart

Wrap your tree once, high, then drop features in behind a gate. This is the exact shape the reference app uses, for a React Native host; a browser host imports the same names from @everfur/sdk/web (see On the web):

import { EverfurProvider, EverfurChat, petRef, userRef } from '@everfur/sdk';

export default function App() {
  return (
    <EverfurProvider
      config={{
        publishableKey: process.env.EXPO_PUBLIC_EVERFUR_PUBLISHABLE_KEY!, // pk_test_... or pk_live_...  safe to ship
        apiBaseUrl: 'https://api.everfur.com/api/v1',
      }}
      // Omit `user` entirely for anonymous mode (publishable key alone, general answers).
      user={{
          userRef: userRef('your-stable-user-id'),
          // An absolute URL: a relative path does not resolve in a React Native bundle.
          // Your route answers JSON `{ token, expiresAt }`; see the contract below.
          getToken: async () => {
            const res = await fetch('https://your-backend.example.com/everfur/session', { method: 'POST' });
            if (!res.ok) throw new Error('session mint failed: ' + res.status);
            const { token, expiresAt } = (await res.json()) as { token: string; expiresAt: number };
            return { token, expiresAt };
          },
        }}
      activePet={petRef('pet-ref-123')}
    >
      {/* Every prebuilt widget self-gates. CapabilityGate is for YOUR surrounding UI - a tab, a nav
          entry - not for wrapping a widget that already gates itself. */}
      <EverfurChat petRef={petRef('pet-ref-123')} />
    </EverfurProvider>
  );
}

getToken calls your backend for a short-lived session token (never a secret in the app). Your route answers JSON, { "token": sessionToken, "expiresAt": expiresAt } straight from mintPartnerSession, with a non-2xx status on any failure, which is the shape every example in the docs uses:

import type { MintedToken } from '@everfur/sdk';

async function getToken(): Promise<MintedToken> {
  const res = await fetch('https://your-backend.example.com/everfur/session', {
    method: 'POST',
    headers: { authorization: `Bearer ${yourAppsUserToken}` },
  });
  if (!res.ok) throw new Error('session mint failed: ' + res.status);
  const { token, expiresAt } = (await res.json()) as { token: string; expiresAt: number };
  return { token, expiresAt };
}

The SDK calls getToken lazily, re-mints about a minute before expiresAt (halfway through, for a token that lives under two minutes), and re-calls it once after a 401, so a fresh token is always used without you managing expiry. Everfur never sees how you signed the person in (OAuth, a JWT, Firebase, Cognito, a session, a magic link all work); it needs one stable, opaque, non-guessable id per person as userRef. Records lives on its own subpath:

import { EverfurRecords } from '@everfur/sdk/records';
import { petRef } from '@everfur/sdk';

<EverfurRecords petRef={petRef('pet-ref-123')} />

The security model

The one rule: the app never holds a secret. Two provisioning credentials, kept apart, plus a read-only docs key for tooling. Test mode is a sandbox tenant (livemode: false in its entitlements), on staging or on production; the API accepts a pk_test_ key only for a sandbox tenant, so a test key never reaches live data. A sandbox key can start with pk_live_ or pk_test_, so read livemode, not the prefix. Each environment's API base URL, keys and hosted MCP address are in Environments and keys.

CredentialWhere it livesWhat it does
Publishable key pk_live_... or pk_test_... (a sandbox key can carry either)In the app (EXPO_PUBLIC_...). Safe to ship.Names your tenant. Cannot mint a session or grant a feature by itself.
Partner secret key sk_partner_...Only on your server. Never in the app, its bundle, env, git, or logs.Mints session tokens. Revocable and rotatable without an app release.
Session token (bearer)App memory, short-lived.Authenticates one end user; re-minted via your backend on a 401. Signed, holds no secret, 15 minutes by default (1 minute to 1 hour).
Docs key dk_partner_...Only in the MCP server or CLI configuration on a developer machine.Read-only: fetches the Everfur contract from GET /partners/contract and opens no other route.

Your backend exchanges the two provisioning credentials for a short-lived session by calling POST /widget/v1/sessions with x-everfur-partner-key: pk_... + x-everfur-partner-secret-key: sk_partner_..., both from the same tenant. The SDK ships a server-only helper (mintPartnerSession, imported from @everfur/sdk/server) so you do not hand-roll that request. That helper is deliberately absent from every React-Native-resolvable bundle: a device can never import it, so the secret has no path onto the phone.

Do: ship the publishable key (pk_test_ or pk_live_) in the app; keep sk_partner_ in a server secret manager; mint sessions on your backend; serve everything over HTTPS. Never: put sk_partner_ in the app; mint sessions on the device; try to force-enable a feature client-side (the server re-checks and returns 403).

How feature access works

Access is decided server-side and delivered to the SDK (GET /widget/v1/entitlements, fail-closed). Each surface sits behind CapabilityGate; a capability you are not entitled to renders a deliberate off-state, and it cannot be force-enabled client-side because the API re-checks on every request. A coarse feature (chat, records, video, ...) is enabled only when every dotted sub-feature resource it requires is granted (for example records needs both records.upload.create and records.record.read). You set these per customer and per user in Everfur's admin control plane; the app renders the verdict.

Every SDK surface renders all four states (loading, empty, error, populated); a denied surface renders its off-state, never a blank region.

Public entry points

ImportContents
@everfur/sdkEverfurProvider, CapabilityGate, EverfurChat, useEverfurChat, CapabilityName, error/result types, branded id helpers (petRef, userRef, conversationId)
@everfur/sdk/recordsEverfurRecords (the Everfur app's records flow), the named screens (EverfurRecordsUpload, EverfurRecordsRequests, EverfurRecordsRequest, EverfurRecordDetail, ...), EverfurClinicRequest, EverfurClinicRequestBatch, SignaturePad, records hooks + types
@everfur/sdk/records/depth, @everfur/sdk/web/records/depthrecord depth (the timeline, the withheld record sections, per-document contributions) on their own entries, dark until Everfur enables it per deployment; see chapter 9
@everfur/sdk/videogait / live-scan surface
@everfur/sdk/photophoto checkup surface
@everfur/sdk/animationsserver-driven pet-state media surface
@everfur/sdk/webthe browser entry: EverfurProvider, CapabilityGate, EverfurChat, useEverfurChat, EverfurTabs (Ask, Health Plan and Records in one shell, with the first-open intro), the theme hook, rendered with react-dom; withheld from React Native bundles the way ./server is
@everfur/sdk/web/recordsrecords for a browser host: EverfurRecordsPage (the Everfur web app's records page, complete on its own: request from a clinic, upload, request status, the full record with vaccinations, all inside your page), the named screens, EverfurRecords (the React Native-shaped flow), useEverfurRecords, createWebRecordsUploadTransport, fileToHandle; its own entry, withheld from React Native like ./web; available when Everfur enables records for the account. Frame mode serves it with surface: 'records' (see chapter 13)
@everfur/sdk/web/records-sheetrecordsSheet(): the v3 records sheet and its lab result screen, EverfurRecordsPage's default look (loaded on first mount; sheet={false} keeps the classic page, which downloads none of it), or built with options and passed to its sheet prop; withheld from React Native like ./web; see chapter 13
@everfur/sdk/web/introthe first-open intro EverfurTabs plays on a phone-narrow page (EverfurIntro, its clinic request frames, EverfurRecordsLandedBanner, useEverfurIntroSeen), downloaded only when it plays; withheld from React Native like ./web; see chapter 13
@everfur/sdk/web/records/uploadcreateWebRecordsUploadTransport alone, for the uploadTransport on the provider config at your app root; the same function @everfur/sdk/web/records exports, without the records entry
@everfur/sdk/serverserver-only mintPartnerSession, revokePartnerMemberSessions (end one member's sessions now) and the partner pet helpers (no React Native resolution; never bundled on-device)
@everfur/sdk/server/eventsserver-only, Node only partner events (preview): constructEvent to verify webhooks, sendPartnerEvent; see chapter 15
@everfur/sdk/testing, @everfur/sdk/testing/rn, @everfur/sdk/testing/webtest seams (MockTransport, useEverfurTestEntitlements, ...) for your own tests

@everfur/sdk/client and @everfur/sdk/core expose the framework-free layers for advanced integrators; most apps only need the root entry plus the capability subpaths.

Reference app

A full, runnable integration (a fictional "Pawtrail" app plus a minimal mint backend that holds the secret) lives at examples/minimal-integration.tsx in this repository; it is type-checked by npm run docs:check, so it cannot rot. It shows anonymous mode, the two-credential session mint, pet registration and sign-out. (A separate everfur-integration-example-rn app exists internally but is not published anywhere a partner can reach, so do not rely on references to it.) It gates every capability, and is the copy-paste source for the snippets above.

版本与发布记录

@everfur/sdk 共发布 43 个版本,首次发布于 2026/09/02;过去 12 个月发布了 43 个版本。

版本发布日期距今
1.0.1latest2026/11/14今天
1.0.02026/11/13今天
0.22.02026/11/13今天
0.21.02026/11/121 天前
0.20.02026/11/121 天前
0.19.02026/11/122 天前

关于 @everfur/sdk 的常见问题

@everfur/sdk 是什么?

Embed Everfur veterinary chat, health records, and pet-state surfaces in your React Native app.

如何安装 @everfur/sdk?

在项目目录运行 npm install @everfur/sdk,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=18)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@everfur/sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@everfur/sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@everfur/sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@everfur/sdk 可以商用吗?

@everfur/sdk 使用 SEE LICENSE IN LICENSE.md 许可。许可条款写在包内的独立文件中,请阅读该文件确认可否商用。以上为概括,不构成法律意见,请以许可原文为准。

@everfur/sdk 的下载量有多少?

根据 npm 官方下载统计,@everfur/sdk 在 2026/10/14 – 2026/11/12 这 30 天内被下载 2,874 次,日均约 96 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@everfur/sdk 还在维护吗?

最新版本 1.0.1 发布于 2026/11/14(今天);至今共发布 43 个版本,其中过去 12 个月发布了 43 个;首次发布于 2026/09/02。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。