@brunyee-studio/onus-sdk

中文文档
3.1.1发布于 2026/11/06MITTypeScriptESM + CJS零依赖近 30 天下载 2048

Official Onus SDK: browser errors, session replay, custom events, logs and web analytics, plus Node, pino, winston and Next.js log exporters (existing Sentry SDKs work unchanged; OTLP for server logs)

原始使用文档
文档目录18 节
  1. Install
  2. In-app frame marking ( allowUrls / denyUrls )
  3. Session replay
  4. Privacy defaults (always on)
  5. Opting down per integration
  6. Logs
  7. Tracing ( @brunyee-studio/onus-sdk/tracing )
  8. Node ( @brunyee-studio/onus-sdk/node )
  9. pino ( @brunyee-studio/onus-sdk/pino )
  10. winston ( @brunyee-studio/onus-sdk/winston )
  11. Next.js ( @brunyee-studio/onus-sdk/next )
  12. Transport semantics
  13. Tree-shaking & debug flag
  14. Browser support
  15. Publishing
  16. Web analytics ( @brunyee-studio/onus-sdk/analytics )
  17. API
  18. Behavior notes

@brunyee-studio/onus-sdk

Official Onus browser SDK for errors, session replay, custom events, and web analytics — it talks to the Onus envelope endpoint (/api/{ingest_project_id}/envelope/) and capture API (/api/{projectId}/analytics/batch/). The envelope endpoint also accepts an existing Sentry SDK unchanged, and the analytics calls mirror PostHog's core calls, so an existing PostHog integration ports over without rewriting call sites.

Install

pnpm add @brunyee-studio/onus-sdk

Analytics ships inside this package — the standalone @brunyee-studio/onus-analytics package was removed. Import analytics from the @brunyee-studio/onus-sdk/analytics subpath (see the Web analytics section).

import { init, captureException, setReplayCaptureAllowed } from '@brunyee-studio/onus-sdk';

init({
  dsn: 'https://<public-key>@<ingest-host>/<numeric-project-id>',
  release: '1.0.0',
  environment: 'production',
  // Configure replay; recording waits for explicit host authorization:
  replays: { sampleRate: 1 },
});

// Only after the host has determined capture is authorized:
await setReplayCaptureAllowed(true);

try {
  risky();
} catch (e) {
  captureException(e);
}

The DSN is public by design; abuse is handled server-side via rate limits and the optional per-key origin allowlist. A missing or malformed DSN never throws: every entry point (init, analytics init, startSessionReplay, ./node, ./next, ./pino) logs a console.warn, disables network sends and keeps the app running, and every capture, logger and analytics call becomes a no-op.

In-app frame marking (allowUrls / denyUrls)

By default, captureException marks stack frames in_app using the same-origin heuristic: relative/bare paths and same-origin URLs are application code, other absolute origins are third-party. Two init options narrow or extend this:

init({
  dsn,
  allowUrls: ['app.example.com', /\/static\/chunks/], // strings match by substring, RegExp by test
  denyUrls: ['/vendor/'], // always excludes, even when allowUrls matches
});
  • allowUrls (non-empty) marks only matching frames in_app; denyUrls always marks matching frames out-of-app, taking precedence over allowUrls.
  • An empty array behaves as "not set" (falls back to the same-origin heuristic); non-array values are ignored.
  • Applies to captureException paths only — payloads passed to captureEvent are not auto-marked.

Error grouping on the server hashes only in-app frames, so cross-origin vendor noise cannot fragment groups. Bundled webpack:// frames (including node_modules paths) are still marked in-app by default — use denyUrls to exclude them. Stacks with no in-app frame fall back to hashing all frames.

Session replay

Two ways to record. Auto-start (recommended) — pass a replays block to init(); recording is deferred until the host calls setReplayCaptureAllowed(true) after its own legal/CMP decision. The SDK's in-memory authorization starts closed on every page session and is never persisted. After authorization, every session records by default and sampling is an opt-down:

init({
  dsn,
  replays: {
    sampleRate: 1, // fraction of sessions recorded (default 1 = all users)
    // sessionId: () => analytics.get_session_id(), // link playback to analytics sessions
  },
});
await setReplayCaptureAllowed(true); // Only after host authorization
// On withdrawal: stop without flushing unsent events, abort in-flight sends,
// and remove the replay id from future errors.
await setReplayCaptureAllowed(false);
// Re-grant re-runs the configured auto-start (including sampling) and, if
// selected, starts a fresh recording with a new replay id.
await setReplayCaptureAllowed(true);

Errors captured afterwards carry contexts.replay.replay_id automatically — no extra wiring. The link starts once Onus has accepted the recording's first segment (the page snapshot playback starts from), so errors never point at a replay that cannot play; errors captured before then carry no replay id. A grant is idempotent while open; after withdrawal, a new grant re-runs the latest init({ replays }) configuration, whereas re-initializing without replays removes the parked auto-start. enabled: false (or omitting the block) opts out entirely.

Behavior change for existing manual callers: a direct startSessionReplay() now resolves null until the host explicitly calls setReplayCaptureAllowed(true). Withdrawal stops recording immediately without a tail flush, aborts in-flight requests, and clears the error↔replay link. Re-grant permits a fresh manual recording; it never resumes the withdrawn one.

Privacy defaults (always on)

Capture is privacy-safe by default, and masking happens before transport (the SDK resolves masks before rrweb serializes anything and before any segment leaves the page):

  • Non-password, text-like input values are masked via the forced mask map (maskAllInputs); password values are always masked and cannot be unmasked.
  • Ordinary DOM text nodes are masked by default (maskTextSelector: '*').
  • input[type="hidden"], input[type="radio"], and input[type="checkbox"] elements are blocked entirely by default: rrweb's masking path skips these types (hidden values and radio/checkbox value attributes would otherwise record verbatim), and their checked state is not replayed.
  • Subtrees marked data-onus-private are blocked entirely.
  • Canvas recording, cross-origin iframe recording, and inline images stay off.
  • Recorded page URLs omit query strings, fragments, and credentials.

What is NOT scrubbed: masking covers input values, ordinary DOM text nodes, and the blocked subtree only. DOM attributes (for example element title or the href attribute of an anchor), image URLs, <style> / stylesheet text (serialized by rrweb as _cssText, outside the maskTextSelector masking path), and non-replay payloads are recorded verbatim. Hosts must block sensitive subtrees with data-onus-private (or privacy.blockSelector) and review sensitive routes and every data path before enabling replay in production.

Opting down per integration

Pass a typed privacy object (manual startSessionReplay or the replays block) to deliberately relax non-password masking for that one integration. privacy flags always win over the bare recordOptions passthrough, and a hostile recordOptions can never defeat masking, enable canvas/iframe capture, replace the SDK's emit, or pre-pack events through rrweb packFn/plugins (those are stripped: they run before the SDK's emit and could bypass Meta href sanitization). Empty, whitespace, or CSS-invalid selectors fall back to the safe defaults instead of disabling masking (rrweb 2.1.1 silently records unmasked on an invalid selector). privacy.blockSelector is additive: it EXTENDS the built-in block list — it can never remove the default blocks (data-onus-private, hidden, radio, and checkbox inputs). There is no privacy: false convenience switch. URL minimization means sensitive data must still never live in the path — hosts should also block sensitive subtrees:

import { init, setReplayCaptureAllowed } from '@brunyee-studio/onus-sdk';
import { startSessionReplay } from '@brunyee-studio/onus-sdk/replay';

// Strict default — everything masked (after authorization):
await setReplayCaptureAllowed(true);
await startSessionReplay({ dsn });

// Selective text opt-down — only mask text inside a known-sensitive area:
init({
  dsn,
  replays: {
    privacy: { maskTextSelector: '.sensitive-area' },
  },
});

// Extra blocked subtree — privacy.blockSelector ADDS to the built-in block
// list; [data-onus-private], input[type=hidden], input[type=radio] and
// input[type=checkbox] stay blocked:
init({
  dsn,
  replays: {
    privacy: {
      blockSelector: '.payment-form',
      ignoreSelector: '.skip-recording',
    },
  },
});

Manual control still works via the separate entrypoint:

import { setReplayCaptureAllowed } from '@brunyee-studio/onus-sdk';
import { startSessionReplay, stopSessionReplay } from '@brunyee-studio/onus-sdk/replay';

await setReplayCaptureAllowed(true); // Manual starts also require authorization.
const replayId = await startSessionReplay({ dsn, environment: 'production' });
// Errors carry contexts.replay.replay_id once the first segment is accepted.

rrweb is an optional peer dependency — the recorder bundle is lazy-loaded only when recording actually starts, so apps that disable replay never download it. Replay segments are emitted as paired replay_event + replay_recording envelope items (gzipped) exactly as the ingest routes and the private Storage bucket expect. The first segment (rrweb Meta + FullSnapshot) is sent as soon as the snapshot is taken; later segments batch every segmentIntervalMs (default 5000) or maxEventsPerSegment events (default 100). Segment sends retry network errors, 408, 429 and 5xx up to 3 attempts with capped backoff; other 4xx responses are not retried. Each recording body is gzipped before upload (where CompressionStream exists), so it is stored compressed. A replay ends after idleTimeoutMs without user input (default 5 minutes) or once it is maxReplayDurationMs long (default and maximum 60 minutes; ingest drops later segments); the next user input starts a fresh replay, so an unattended tab stops recording. Replays expire per the team's retention setting (90 days by default).

Logs

Structured logs go to Onus's log viewer as log envelope items. Opt in with a logs block; without one, logger calls do nothing.

import { init, logger } from '@brunyee-studio/onus-sdk';

init({
  dsn: 'https://<key>@onus.example/<projectId>',
  release: '[email protected]',
  logs: {
    minLevel: 'info', // default 'trace'
    captureConsole: ['warn', 'error'], // also send these console calls
    sessionId: () => analytics.get_session_id(),
  },
});

logger.info('checkout started', { cartItems: 3, tier: 'pro' });
logger.error(logger.fmt`payment ${orderId} failed after ${attempts} tries`);
  • Levels: trace, debug, info, warn, error and fatal.
  • Attributes: strings, numbers and booleans, typed on the wire. Other values are JSON-stringified.
  • logger.fmt: keeps the message template and its parameters as attributes, so similar lines group together.
  • Automatic context: environment, release, SDK name and version, the active replay id, session.id, and the page URL without its query string or fragment.
  • Delivery:
    • Up to 100 records (and about 512 KiB) per envelope, sent every 5 s by default or as soon as a batch fills.
    • When the page is hidden or unloads, buffered records are sent with sendBeacon.
  • Limits and failures:
    • At most 1000 unsent records are kept, and the oldest are dropped first.
    • A 429 pauses sending for Retry-After. Other failures back off exponentially up to 60 s.
    • Dropped records are counted and reported to Onus in a client report.
    • The logger never throws.
  • Bundle size: the logger ships in the core entry. Importing it never pulls in replay or analytics code.

Tracing (@brunyee-studio/onus-sdk/tracing)

Browser traces: a pageload root, a navigation root per SPA route change, and an http.client child span per fetch/XMLHttpRequest. Each root is sent as one transaction envelope item and shows up on the Traces page.

import { init } from '@brunyee-studio/onus-sdk';
import { startBrowserTracing } from '@brunyee-studio/onus-sdk/tracing';

init({ dsn: 'https://<key>@onus.example/<projectId>', environment: 'production' });
const tracing = startBrowserTracing({
  tracesSampleRate: 0.1, // default 1
  service: 'web', // default 'browser'
  tracePropagationTargets: ['https://api.example.com', /^https:\/\/[a-z]+\.example\.com\//],
});
// tracing?.stop() removes every patch.
  • Needs init() first. startBrowserTracing returns null when the SDK has no valid DSN or tracing already started.
  • Roots: a root ends 1 s after its last request finishes (idleTimeoutMs), after 30 s at most (finalTimeoutMs), on the next navigation, or on pagehide. Requests still open then are sent with status cancelled. Pageload starts at performance.timeOrigin.
  • Navigation: history.pushState/replaceState and popstate; only a pathname change starts a new root. Routers that fetch the next page before changing the URL should call startNavigation(url) when the navigation starts, so those requests (and the server spans they cause) land in the new page's trace; the history change that follows doesn't start a second root for the same path. startNavigation is a no-op when tracing isn't running.
  • Requests between roots: a request made while no root is open (after the last one ended) gets a fresh trace id of its own with the last root's sampling decision, so an ended trace never keeps growing.

Next.js App Router fetches the next page's payload before pushState, so call startNavigation from instrumentation-client.ts:

// instrumentation-client.ts
import { startNavigation } from '@brunyee-studio/onus-sdk/tracing';

export function onRouterTransitionStart(url: string) {
  startNavigation(url);
}
  • Request spans: named METHOD /path, with http.request.method, url.full (without query or fragment) and http.response.status_code. A status ≥ 500 or a network error is internal_error. Requests to the SDK's own ingest routes are never traced.
  • traceparent propagation: by default only same-origin requests get the header, so CDNs and third parties never see your trace ids. tracePropagationTargets replaces the default: a RegExp is tested against the absolute URL; a string that is an absolute URL (https://api.example.com) matches only as a prefix on that exact origin; a path string (/api/) matches only same-origin requests whose path starts with it; any other string is a host (api.example.com, localhost:3000) that matches that host or its subdomains, never a lookalike such as api.example.com.evil.net. Strings never match on a substring of the URL, so a host with a path (api.example.com/v1) or a protocol-relative string (//cdn.example.com) never matches anything: use a full URL (https://api.example.com/v1) or a RegExp. tracePropagationTargets: [] turns propagation off entirely. An existing traceparent is never overwritten.
  • Sampling: decided once per root. An unsampled root sends nothing but still propagates traceparent with flags 00, so servers can respect the decision.
  • Correlation: while tracing runs, logger records carry the active trace_id and captured errors carry contexts.trace. The core entry only holds a tiny trace registry, not the tracer. Each replay segment lists the traces started or propagated since the previous one as trace_ids, so the replay's log sidebar also shows your backend's lines for those traces.
  • Bundle size: its own subpath with no OpenTelemetry dependency. Apps that never import it download none of it.

Node (@brunyee-studio/onus-sdk/node)

pnpm add @brunyee-studio/onus-sdk @opentelemetry/api @opentelemetry/api-logs @opentelemetry/core \
  @opentelemetry/otlp-transformer @opentelemetry/resources @opentelemetry/sdk-logs
import { init, logger, captureException, flush } from '@brunyee-studio/onus-sdk/node';

init({
  dsn: process.env.ONUS_DSN,
  service: 'api',
  environment: 'production',
  release: '1.4.0',
});
logger.info('order placed', { orderId });
captureException(err); // error event with contexts.trace from the active span
await flush(); // before a serverless function returns
  • Logs go over OTLP/JSON to /api/{projectId}/otlp/v1/logs, batched (≤ 512 records, 1 s), gzipped, and split to stay under the 1 MiB request cap. The DSN's key must have no allowed-origins list, because server senders have no Origin header.

  • logLevel (default info) filters logger.* and console capture. captureConsole: ['warn', 'error'] mirrors those console methods.

  • Records carry the active OpenTelemetry span.

  • Tracing is its own subpath, @brunyee-studio/onus-sdk/node/tracing, so apps that only send logs and errors never need its two extra packages. Add @opentelemetry/sdk-trace-base @opentelemetry/context-async-hooks to the install line and pass onusTracing to init:

    import { init } from '@brunyee-studio/onus-sdk/node';
    import { onusTracing } from '@brunyee-studio/onus-sdk/node/tracing';
    
    init({
      dsn: process.env.ONUS_DSN,
      service: 'api',
      tracing: onusTracing({ tracesSampleRate: 0.1 }),
    });
    

    init then also registers a global tracer provider, an async context manager and the W3C traceContext propagator, and exports spans over OTLP/JSON to /api/{projectId}/otlp/v1/traces (same batching, gzip and backoff as logs). Without tracing no tracer is registered; bring your own (@opentelemetry/sdk-node). Sampling is parent-based: an incoming traceparent decides (so a browser's sampled-out trace stays out), and only root spans use tracesSampleRate (0..1). ignoreSpanPaths: /\/health/ never samples roots whose name or http.target/url.path/http.route/next.route matches. The exporter's own requests run with tracing suppressed, so they never become spans.

  • Servers without HTTP auto-instrumentation join the caller's trace with continueTrace(headers, name, fn): fn runs in an active SERVER span parented by the request's traceparent, and a throw is recorded on the span and rethrown.

    import { continueTrace } from '@brunyee-studio/onus-sdk/node';
    
    app.get('/orders', (req, res) =>
      continueTrace(req.headers, 'GET /orders', async () => res.json(await listOrders())),
    );
    
  • init does not replace a global OTel logs provider that is already registered: the OTel API keeps the first one and ignores later registrations, so logger records would go to that provider instead of Onus (error capture is unaffected). If your app already registers one (e.g. @opentelemetry/sdk-node with log processors), forward pino logs through the ./pino transport below, which exports through its own provider rather than the global one; on Next.js, let registerOnus from ./next be what registers OTel.

  • On 429/503 the exporter pauses for Retry-After and drops batches until then. A rejected key or project (401/403/404, e.g. a key with an allowed-origins list) pauses forwarding for 10 minutes. Nothing throws into your app.

pino (@brunyee-studio/onus-sdk/pino)

Add pino-abstract-transport to the Node install line.

const log = pino({
  transport: {
    targets: [
      { target: 'pino/file', options: { destination: 1 } }, // keep stdout
      {
        target: '@brunyee-studio/onus-sdk/pino',
        options: { dsn: process.env.ONUS_DSN, service: 'api' },
      },
    ],
  },
});

The transport runs in pino's worker thread and exports on its own. pino rejects a custom formatters.level together with transport.targets, and the transport accepts both numeric levels and level labels. Lines keep trace_id/span_id (e.g. from @opentelemetry/instrumentation-pino) as the record's trace context.

If ./node init (or ./next registerOnus) already runs in-process, use createOnusPinoStream({ exclude? }) as a pino.multistream destination instead. It emits through that provider.

winston (@brunyee-studio/onus-sdk/winston)

Add winston-transport to the Node install line, and call init from ./node first.

import { OnusWinstonTransport } from '@brunyee-studio/onus-sdk/winston';

const log = winston.createLogger({
  transports: [new winston.transports.Console(), new OnusWinstonTransport()],
});

npm and syslog levels both map to OTel severities, metadata becomes attributes, and records carry the active span.

Next.js (@brunyee-studio/onus-sdk/next)

Add @vercel/otel to the Node install line, and @opentelemetry/sdk-trace-base if you set tracesSampleRate.

// instrumentation.ts
import { registerOnus } from '@brunyee-studio/onus-sdk/next';

export const register = () =>
  registerOnus({ dsn: process.env.ONUS_DSN, service: 'web', environment: process.env.VERCEL_ENV });
export { onRequestError } from '@brunyee-studio/onus-sdk/next';

On the Node runtime this registers @vercel/otel (request spans) with the Onus log processor (and, with tracesSampleRate, the Onus sampler and span processor alongside @vercel/otel's own 'auto' processors), so logger, createOnusPinoStream and OnusWinstonTransport export through it. Once registerOnus has run, captureException and logger from ./node work from any module in the app (route handlers, server actions, server components); you don't call init. onRequestError captures unhandled errors with the route and active trace. On the edge runtime only tracing is registered, and request errors are written as one JSON line with trace_id/span_id.

Transport semantics

  • POSTs envelopes with X-Sentry-Auth (sentry_key = your public key).
  • Gzips bodies via CompressionStream when available; the ingest route sniffs magic bytes and also accepts identity bodies.
  • Honors 429 + retry-after with capped backoff (per-key rate limits).
  • Envelopes over the server's caps (1 MiB on the wire after gzip, 10 MiB inflated) are dropped client-side.
  • navigator.sendBeacon is available for unload-time delivery (?sentry_key= query fallback, since beacons cannot set headers).

Tree-shaking & debug flag

  • Import only what you use; the replay recorder is isolated in @brunyee-studio/onus-sdk/replay.
  • Known limitation: do not mix CommonJS require('@brunyee-studio/onus-sdk') with ESM imports of the /replay subpath in one page. The two builds can create separate module instances with separate in-memory authorization gate state. Use one module system consistently for both SDK entrypoints until dual-instance compatibility has been resolved.
  • Debug logging is gated behind the compile-time flag __ONUS_DEBUG__. Strip it in production bundlers:
// webpack
new webpack.DefinePlugin({ __ONUS_DEBUG__: false });
// esbuild / tsup
define: {
  __ONUS_DEBUG__: 'false';
}

Browser support

BrowserMinimum
Chrome90
Edge90
Firefox90
Safari15.4
iOS Safari15.4

Requires crypto.randomUUID or crypto.getRandomValues (event ids), fetch, and optionally CompressionStream (gzip) / navigator.sendBeacon (unload delivery). Older browsers degrade gracefully: identity bodies instead of gzip, fetch instead of beacon.

Version policy: we support the current major line with fixes.

Publishing

@brunyee-studio/onus-sdk releases through the same semantic-release pipeline as packages/cli. Only dist/ and README.md are published (files in package.json).

Web analytics (@brunyee-studio/onus-sdk/analytics)

Product-analytics surface (init / capture / identify / alias / register / reset / get_distinct_id / get_session_id) on top of the Onus capture API. The core calls match an existing PostHog integration, so call sites port over without rewriting. Import it from its own subpath so apps that only need error tracking never download analytics code. The subpath is ESM-only (import condition, like ./replay), and the package is sideEffects: false, so bundlers drop any feature set you don't import.

import { init } from '@brunyee-studio/onus-sdk/analytics';

const onus = init({
  // Flat ingest DSN from your team's keys panel (public by design).
  dsn: 'https://<token>@<ingest-host>/<ingest-project-id>',
});

onus.capture('cta_click', { button: 'hero' });

Or with explicit parts:

init({ apiKey: '<token>', apiHost: 'https://app.onus.dev', projectId: 42 });

API

MethodBehavior
init(config)Parse DSN, restore persisted identity, wire pageleave flush.
capture(name, props?)Queue a custom event.
identify(id, $set?, $set_once?)Emit $identify; adopt the id; keep the anon id for server-side merge.
alias(id)Emit $create_alias linking the current distinct id to id.
register(props) / register_once(props, def?) / unregister(name)Persisted super properties attached to every event.
reset()Fresh anonymous id + session; clears super properties.
get_distinct_id() / get_session_id()Read current ids (session: uuidv7, 30-min idle / 24-h max).
flush()Await a transport flush (tests, SPAs before route teardown).

Behavior notes

  • Sessions are client-side uuidv7 ids shared across tabs via localStorage, rotated after 30 minutes idle or 24 hours total.
  • Persistence prefers localStorage and degrades to in-memory when blocked (Safari private mode). Cookie persistence is intentionally not supported in v1 (consent surface + 4KB budget).
  • Pageleave: buffered events flush via sendBeacon on pagehide, with a keepalive fetch fallback.
  • Retries: transient failures (5xx/network) retry with exponential backoff and drop after 4 attempts; 4xx responses are acknowledged, never retried — the server's rate-limit decision is authoritative.
  • Rate limiting: client-side token bucket (default 10 events/s, burst 10x); excess events are dropped locally rather than 429-hammering the endpoint.
  • DNT: capture is a no-op when navigator.doNotTrack is set and respectDnt is true (default).
  • before_send receives each event before it leaves the browser; return null to drop it.
  • Autocapture: $pageview on init + SPA history changes, $pageleave, and $autocapture clicks/submits with an elements chain and data-onus-capture-attribute-* property passthrough.

版本与发布记录

@brunyee-studio/onus-sdk 共发布 13 个版本,首次发布于 2026/09/23;过去 12 个月发布了 13 个版本。

版本发布日期距今
3.1.1latest2026/11/062 天前
3.1.02026/11/062 天前
3.0.02026/11/053 天前
2.4.02026/10/2613 天前
2.3.02026/10/1524 天前
2.2.02026/10/1425 天前

关于 @brunyee-studio/onus-sdk 的常见问题

@brunyee-studio/onus-sdk 是什么?

Official Onus SDK: browser errors, session replay, custom events, logs and web analytics, plus Node, pino, winston and Next.js log exporters (existing Sentry SDKs work unchanged; OTLP for server logs)

如何安装 @brunyee-studio/onus-sdk?

在项目目录运行 npm install @brunyee-studio/onus-sdk,或使用 pnpm add / yarn add / bun add。

@brunyee-studio/onus-sdk 支持 TypeScript 吗?

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

@brunyee-studio/onus-sdk 支持 ESM 还是 CommonJS?

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

@brunyee-studio/onus-sdk 可以商用吗?

@brunyee-studio/onus-sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@brunyee-studio/onus-sdk 的下载量有多少?

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

@brunyee-studio/onus-sdk 还在维护吗?

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