npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@streamfox/plugin-sdk

v0.7.2

Published

Unified Node SDK for StreamFox media plugins

Readme

StreamFox Plugin SDK

@streamfox/plugin-sdk is the Node.js SDK for StreamFox remote plugins.

It includes:

  • declarative plugin contract with definePlugin(...)
  • runtime server with createServer(...) / serve(...)
  • strict schema-major validation (schemaVersion.major === 1)
  • built-in installer UI and typed settings parsing
  • canonical contract parity with swift-media-plugin-kit

Install

npm i @streamfox/plugin-sdk

Quick Start

import { definePlugin, ids, serve } from "@streamfox/plugin-sdk";

const plugin = definePlugin({
  plugin: {
    id: ids.plugin("com.example.demo"),
    name: "Demo",
    version: "0.1.0",
  },
  resources: {
    stream: {
      mediaType: "movie",
      handler: async () => ({
        streams: [
          {
            transport: {
              kind: "http",
              url: "https://cdn.example.com/movie.mp4",
              mode: "stream",
            },
          },
        ],
      }),
    },
  },
});

const server = await serve(plugin, {
  port: 7000,
  integration: {
    installScheme: "streamfox",
    launchBaseURL: "https://streamfox.app/#",
    autoOpen: "none",
  },
});

console.log(server.url); // manifest URL
console.log(server.installURL); // install deeplink
console.log(server.launchURL); // launch URL

Progressive Shorthand

definePlugin(...) accepts shorthand aliases and normalizes them to canonical manifest fields:

  • type -> mediaTypes: [type]
  • mediaType -> mediaTypes: [mediaType]

Conflict guard:

  • equivalent canonical+alias values are accepted
  • mismatched canonical+alias values throw MANIFEST_INVALID

Defaults (when omitted):

  • meta.mediaTypes: ["movie", "series"]
  • stream.mediaTypes: ["movie", "series"]
  • subtitles.mediaTypes: ["movie", "episode"]
  • stream.supportedTransports: ["http"]
  • subtitles.defaultLanguages: ["en"]
  • catalog.endpoint.mediaTypes: ["movie"]

Validation Lifecycle

Validation runs automatically in three phases:

  1. createPlugin(...) / definePlugin(...): manifest + capability shape validation.
  2. Incoming request handling (createServer routes): request validation against manifest capabilities.
  3. Outgoing handler responses: response validation before JSON emission.

Redirect responses are validated too (redirect.url, redirect.status) before the redirect is returned.

Canonical Routes

  • GET /manifest
  • GET /studio-config
  • GET /catalog/:mediaType/:catalogID
  • GET /meta/:mediaType/:itemID
  • GET /stream/:mediaType/:itemID
  • GET /subtitles/:mediaType/:itemID
  • GET /plugin_catalog/:catalogID/:pluginKind

GET Query Style

Resource routes use one HTTP style:

  • path params for identity
  • plain query aliases for request shaping

Examples:

  • /catalog/movie/browse?genre=action&year=2024&locale=el-GR&page=0&pageSize=20&orderBy=popular
  • /catalog/movie/browse?year=2000..2024&rating=7..10
  • /catalog/movie/browse?orderBy=rating&order=asc
  • /meta/movie/tt0133093?locale=el-GR&regionCode=GR
  • /stream/movie/tt0133093?videoID=trailer&startPositionSeconds=123&networkProfile=wifi
  • /stream/movie/tt0133093?quality=1080p&videoID=trailer&startPositionSeconds=123&networkProfile=wifi
  • /subtitles/movie/tt0133093?source=opensubtitles&hearingImpaired=true&videoHash=abc123&videoSize=1234567&filename=matrix.mkv&languagePreferences=en,el
  • /plugin_catalog/featured/catalog?page=0&experimental=streamfox:beta

Legacy structured query params such as request, schemaVersion, context, experimental, filters, sort, playback, and videoFingerprint are rejected on GET resource routes.

Strong ID Helpers

Use ids.* helpers when authoring manifests and responses:

  • ids.plugin("com.example.demo")
  • ids.catalog("browse")
  • ids.item("tt0133093")
  • ids.video("main")
  • ids.imdb("tt0133093") for strict IMDb format (tt + digits)

IMDb-Only IDs

This SDK enforces IMDb-only media IDs:

  • Item/media IDs must match tt<digits>
  • Episodic video IDs must match tt<digits>:<season>:<episode> when colon format is used
  • ID prefix configuration is not part of the SDK API

Examples:

  • valid item ID: tt0133093
  • invalid item ID: tmdb:603
  • valid episodic video ID: tt0944947:1:1
  • invalid episodic video ID: episode-1

Migration Note

If older code used custom ID prefixes, migrate to IMDb IDs (tt...) and episodic video IDs in tt...:season:episode format.

Unified Filter Ergonomics

Catalog filters support:

  • reusable filterSets shared across endpoints
  • reusable sortSets shared across endpoints
  • richer filter metadata for UI and docs
  • richer sort metadata for UI and docs
  • filters.* and sorts.* helpers for authoring plain manifest-compatible specs
  • query alias normalization through options[].aliases
  • ordering aliases through orderBy
  • isRequired for required (always-visible) controls
  • index (>= 0) for deterministic UI ordering
  • optionsLimit and maxSelected for bounded multi-selects
  • dynamicOptions for provider-driven filter options with caching/fallback behavior
  • visibleWhen / enabledWhen for conditional filter UX
  • the same FilterSpec model on catalog, stream, and subtitles

Example:

import { definePlugin, filters, ids, sorts } from "@streamfox/plugin-sdk";

const plugin = definePlugin({
  plugin: {
    id: ids.plugin("com.example.catalog"),
    name: "Catalog Demo",
    version: "0.1.0",
  },
  resources: {
    catalog: {
      filterSets: {
        commonCatalogFilters: [
          filters.select("language", {
            label: "Language",
            group: "regional",
            options: [
              { label: "Japanese", value: "ja", aliases: ["Japanese (ja)"] },
              { label: "English", value: "en", aliases: ["English (en)"] },
            ],
          }),
          filters.select("genre", {
            options: [
              { label: "Action", value: "action", aliases: ["Action"] },
              { label: "Drama", value: "drama" },
            ],
          }),
        ],
      },
      sortSets: {
        browseSorts: [
          sorts.desc("popularity", {
            label: "Popular",
            aliases: ["popular"],
          }),
          sorts.choice("rating", {
            label: "Top Rated",
            aliases: ["top-rated"],
            directions: ["descending", "ascending"],
            defaultDirection: "descending",
          }),
        ],
      },
      endpoints: [
        {
          id: ids.catalog("browse"),
          name: "Browse",
          mediaTypes: ["movie"],
          filterSetRefs: ["commonCatalogFilters"],
          sortSetRefs: ["browseSorts"],
          filters: [filters.intOrRange("year"), filters.range("rating")],
        },
        {
          id: ids.catalog("episodes"),
          name: "Episodes",
          mediaTypes: ["series"],
          filters: [
            filters.number("season", {
              label: "Season",
              group: "episodes",
            }),
          ],
        },
      ],
      handler: async () => ({ items: [] }),
    },
  },
});

Prefer semantic endpoint IDs such as browse, discover, and search. Keep variable controls in the query string:

  • /catalog/movie/browse?language=ja
  • /catalog/movie/browse?year=2024
  • /catalog/movie/browse?year=2000..2024
  • /catalog/movie/browse?query=matrix
  • /catalog/movie/browse?orderBy=popular
  • /catalog/series/episodes?season=1
  • /catalog/series/episodes to return all episodes when no season is provided

stream and subtitles can declare their own filters too, while still using dedicated aliases like videoID, videoHash, and languagePreferences.

Reserved filter keys are rejected across all resource kinds:

  • query, page, pageSize, orderBy, order
  • videoID, videoHash, videoSize, filename, languagePreferences
  • locale, regionCode, traceID, experimental

Manifest Policy + Quality Metadata

Manifest now supports:

  • safety (adult, p2p) for install-time trust hints
  • configuration (required, fields) as the first-class plugin config schema
  • capabilityConstraints (accountRequired, bandwidth, geo allow/block regions)
  • qualitySignals (providerSuccessRate, timeoutRatio, freshnessTimestamp)

Capability-level guardrails:

  • IMDb-only ID enforcement for meta, stream, and subtitles capabilities
  • meta.embeddedVideoStreamStrategy (exclusive | merge | prefer_external)
  • catalog discovery metadata for UI defaults (mode, defaultSort, defaultFilters)

Rich Media Details

MediaSummary stays lightweight for browse/cards, but supports a few presentation fields:

  • background
  • runtime
  • yearLabel
  • logoURL
  • releasedAt
  • slug
  • imdbRating
  • sourceRatings
  • popularity

MediaDetail is the richer app-detail model and supports:

  • releasedAt
  • dvdReleaseAt
  • logoURL
  • language
  • country
  • awards
  • slug
  • imdbRating
  • sourceRatings
  • popularity
  • popularityBySource
  • cast, directors, writers
  • behaviorHints
  • videos
  • trailers
  • similarItems

behaviorHints follows the Cinemeta-style shape:

behaviorHints?: {
  defaultVideoId?: VideoID | null;
  hasScheduledVideos?: boolean;
}

Video entries support both:

  • releasedAt: generic availability date
  • firstAiredAt: schedule/broadcast date for upcoming-episode UX
  • rating: optional video/episode rating

There is no separate trailerStreams field; trailers are represented only through trailers.

ID Model

StreamFox uses typed IDs in the SDK contract:

  • MediaSummary.id
  • MediaDetail.summary.id
  • similarItems[].id
  • VideoUnit.id

ID semantics depend on the entity:

  • media/title IDs identify the title itself, for example tt0133093
  • video IDs identify the video resource itself, for example main or tt8599532:1:4

Recommended episodic video ID format:

  • {parentMediaID}:{season}:{episode}

defaultVideoID always points to one videos[].id.

Use ids.item(...) for media IDs, ids.video(...) for video IDs, and ids.imdb(...) when strict IMDb format is required.

Custom Frontends

There are two supported ways to own the installer/frontend experience:

  1. Headless mode:
await serve(plugin, {
  frontend: false,
});

Use your own app against GET /manifest, GET /studio-config, and the resource routes.

  1. Custom static bundle served by the SDK:
await serve(plugin, {
  frontend: {
    mountPath: "/installer",
    distPath: "/absolute/path/to/frontend-dist",
    assetsMountPath: "/installer/assets",
  },
});

/studio-config is the frontend contract for installer metadata, field definitions, deeplink scheme, and configurationRequired (derived from manifest.configuration.required).

Docs

Advanced API

import {
  createPlugin,
  parseJsonWithLimits,
  maximumJsonNestingDepth,
  validateManifest,
  validateRequest,
  validateResponse,
  ProtocolError,
} from "@streamfox/plugin-sdk";

JSON payload size/depth limit controls live in the schema utilities above, not in createServer(...) or serve(...).

Subpath export is also available:

import {
  validateManifest,
  validateRequest,
  validateResponse,
} from "@streamfox/plugin-sdk/advanced";

Development

npm install
npm run format
npm run build
npm test