plugins

package
v0.3.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 4, 2026 License: GPL-3.0 Imports: 40 Imported by: 0

Documentation

Overview

Package plugins is Ether's plugin host core (SPEC §5.1, §5.4, §5.5, §5.7, §5.13, WS7): manifest parsing and validation, install and lifecycle, permissions and grants, settings and sealed secrets, KV, health, the pull scheduler, the event outbox and log, the journal (shadow and undo), update and rollback, removal, cluster placement, and the owner API.

Runtimes are separate workstreams and plug in through Runtime and RuntimeFactory: WS8a registers the hosted (wazero) runtime, WS8b the remote (URL and tunnel) runtimes. Capability adapters (WS9) call plugins through Manager.Call and read state through Manager's accessors; they never touch the tables directly. The conformance kit (WS11) reuses ParseManifest, Manifest.Validate and LintCopy.

Index

Constants

View Source
const (
	EventPlaybackStarted   = "playback.started"
	EventPlaybackPaused    = "playback.paused"
	EventPlaybackResumed   = "playback.resumed"
	EventPlaybackStopped   = "playback.stopped"
	EventPlaybackCompleted = "playback.completed"
	EventPlaybackError     = "playback.error"
	EventPlaybackFailover  = "playback.failover"
	EventTitleAvailable    = "title.available"
	EventViewerAdded       = "viewer.added"
	EventViewerAccess      = "viewer.access_changed"
	EventCollectionRebuilt = "collection.rebuilt"
	EventServerHealth      = "server.health_changed"
)

Normalized event names (SPEC §5.7).

View Source
const (
	KVQuotaBytes   = 50 << 20
	OutboxCap      = 1000
	CallsCap       = 200
	LogsCap        = 200
	EventLogMaxAge = 7 * 24 * time.Hour
	MaxFetchBytes  = 10 << 20
)

Limits from SPEC §5.1.5, §5.7 and §5.13.

View Source
const Protocol = 1

Protocol is the Ether Plugin Protocol major this host speaks (EPP/1).

View Source
const RelayPublicPrefix = "/ether/api/addon-relay/"

RelayPublicPrefix is where the console reaches the relay.

View Source
const StremioChannelsKey = "channels"

StremioChannelsKey is the channel-source key of a `tv` add-on.

Variables

View Source
var (
	ErrNotFound         = errors.New("plugins: not found")
	ErrAlreadyInstalled = errors.New("plugins: already installed")
	ErrPermissionDenied = errors.New("plugins: permission denied")
	ErrQuotaExceeded    = errors.New("plugins: storage quota exceeded")
	ErrDisabled         = errors.New("plugins: plugin is disabled")
	ErrNoRuntime        = errors.New("plugins: no runtime registered for this plugin")
	ErrUnavailable      = errors.New("plugins: host service unavailable")
	ErrShadow           = errors.New("plugins: write recorded in shadow mode")
	ErrDigestMismatch   = errors.New("plugins: artifact digest does not match")
	ErrRevoked          = errors.New("plugins: plugin was revoked")
)

Sentinel errors. Adapters and the API map them to plain reasons.

AddonCapabilities are the roles a plain Stremio add-on can play.

View Source
var ErrFetchFailed = errors.New("plugins: request failed")

ErrFetchFailed is returned when a plugin's outbound request fails. Its message names only the host and a category: request URLs can carry plugin secrets (API keys in the query), and net/http errors include the full URL, so neither the URL nor the underlying error text is exposed.

View Source
var ErrNoAddon = errors.New("plugins: this plugin is not bound to an add-on")

ErrNoAddon reports a plugin with no add-on link behind it.

View Source
var ErrNoAddonLink = errors.New("plugins: this plugin has no add-on link")

ErrNoAddonLink reports a plugin without a sealed add-on link.

View Source
var ErrStreamUnsupported = errors.New("plugins: runtime does not support streaming")

ErrStreamUnsupported indicates that a runtime cannot relay file bodies.

KnownEvents is the closed v1 event set.

View Source
var ReservedCapabilities = map[Capability]bool{
	"artwork": true, "ratings": true,
	"stats_export": true, "sign_in": true,
}

Reserved capabilities are known names a manifest may not claim in v1.

Functions

func AddonResult

func AddonResult(resp Response) (json.RawMessage, error)

AddonResult is the guest-facing answer for an add-on fetch made on a draft's behalf (Studio previews).

func CollectionPluginID

func CollectionPluginID(ctx context.Context) string

CollectionPluginID identifies the installed plugin for a backend write or undo. Only the host supplies this context value; plugin documents cannot set it.

func CompareSemVer

func CompareSemVer(a, b string) int

CompareSemVer returns -1, 0 or 1. Unparseable versions compare equal to 0.

func DeadlineFor

func DeadlineFor(path string) time.Duration

DeadlineFor returns the per-route deadline from SPEC §5.1.2.

func DescriptorsFor

func DescriptorsFor(man Manifest) []extend.Descriptor

DescriptorsFor builds the registry descriptors a manifest contributes. Exported for the kit and Studio previews.

func FetchResult

func FetchResult(resp FetchResponse) map[string]any

FetchResult is the guest-facing http.fetch answer. body is the response as text (JSON, HTML, playlists); bodyBase64 carries the exact bytes only for responses that are not valid UTF-8 (archives, images), so a large text response is not sent twice into the 4 MiB host-result cap.

func HotLimits

func HotLimits(m Manifest) wasm.Limits

HotLimits lowers the runtime's hot-path defaults to the tightest budget a manifest declares across its ranking steps. The engine clamps anything above its defaults, so a manifest can lower budgets but never raise them. Studio replay uses it to run a plugin under its installed budgets.

func IsGitHubURL

func IsGitHubURL(raw string) bool

IsGitHubURL classifies a github.com repo URL.

func LintCopy

func LintCopy(s string, max int) string

LintCopy checks one user-visible string against the copy rules: at most max characters (0 means no limit), no em or en dashes, no exclamation marks, no "simply", "just" or "easily", and sentence case. It returns a plain reason, or "" when the copy passes. Shared with the kit (WS11).

func LintSettingsSchema

func LintSettingsSchema(raw json.RawMessage) error

LintSettingsSchema validates a resolved settings schema document (SPEC §5.5 copy rules). Shared with the kit.

func NonSecretSettings

func NonSecretSettings(settings map[string]any, schema json.RawMessage, declaredSecrets []string) map[string]any

NonSecretSettings applies declared defaults, then stored public settings. Explicit zero, false, empty and null values take precedence over defaults. Studio uses the same secret field classifier as installed settings storage.

func NormalizeNotify

func NormalizeNotify(kind string) string

NormalizeNotify maps a notify event kind to §5.7 names.

func NormalizePlayback

func NormalizePlayback(kind, state string) string

NormalizePlayback maps a playback.Manager event kind to §5.7 names. state is the raw timeline state for updates (playing, paused, stopped). It returns "" for kinds with no normalized event.

func ParamFieldsFromList

func ParamFieldsFromList(pluginID string, fields []ListParamField) ([]extend.ParamField, string)

func ParseSemVer

func ParseSemVer(v string) ([3]int, bool)

ParseSemVer parses a plain major.minor.patch version.

func PermissionDiff

func PermissionDiff(cur, next Manifest) []string

PermissionDiff lists the permissions `next` asks for that `cur` does not have. A non-empty result pauses auto-update and asks the owner.

func PermissionSheet

func PermissionSheet(man Manifest) (lines []string, needsViewers, needsFolders bool)

PermissionSheet returns the plain-language lines the owner sees at install (SPEC §5.4). needsViewers and needsFolders say the sheet needs viewer or folder picks.

func PkgDigest

func PkgDigest(files map[string][]byte) string

PkgDigest computes an .etherpkg digest over its files (excluding the digest entry itself). pack (WS11) writes it; install verifies it.

func StremioCatalogKey

func StremioCatalogKey(c StremioCat) string

StremioCatalogKey is the list-source key of a Stremio catalog.

func StremioInstallID

func StremioInstallID(link string) (id, base string, err error)

StremioInstallID is the record id of a plain Stremio add-on installed from link, and the normalised base URL it is reached at. The id depends on the link alone: the same link pasted on any page is the same add-on, even when its manifest changes, and two configurations of one add-on (two debrid accounts) are two add-ons.

func ToolName

func ToolName(plugin, name string) string

ToolName namespaces a plugin tool for the catalogue: dots and dashes become underscores ("<plugin>__<name>").

Types

type AddonCat

type AddonCat struct {
	Key  string `json:"key"`
	Type string `json:"type"`
	ID   string `json:"id"`
	Name string `json:"name"`
}

AddonCat is one catalog an add-on lists, for pickers.

type AddonConfig

type AddonConfig struct {
	Configurable bool               `json:"configurable"`
	Required     bool               `json:"required"`
	Configured   bool               `json:"configured"`
	Layer        string             `json:"layer"` // declared, decoded, page, none
	Fields       []AddonConfigField `json:"fields"`
	Host         string             `json:"host"`
}

AddonConfig describes how an add-on is configured, for the console.

type AddonConfigChange

type AddonConfigChange struct {
	Values map[string]any `json:"values"`
	Link   string         `json:"link"`
}

AddonConfigChange is either new values for the drawn fields or a whole install link (from the add-on's own configure page, or pasted).

type AddonConfigField

type AddonConfigField struct {
	Key      string              `json:"key"`
	Label    string              `json:"label"`
	Kind     string              `json:"kind"` // text, number, switch, list, secret, select, fixed
	Value    any                 `json:"value,omitempty"`
	Set      bool                `json:"set,omitempty"` // a secret has a value (never sent)
	Options  []AddonConfigOption `json:"options,omitempty"`
	Required bool                `json:"required,omitempty"`
	Summary  string              `json:"summary,omitempty"` // fixed (structured) values, described
	// Group names the nested setting a field belongs to ("Languages"); its
	// Key is then a path ("languages/preferred").
	Group string `json:"group,omitempty"`
}

AddonConfigField is one setting as the console draws it.

type AddonConfigOption

type AddonConfigOption struct {
	Value string `json:"value"`
	Label string `json:"label"`
}

AddonConfigOption is one choice of a select field.

type AddonConfigResult

type AddonConfigResult struct {
	Name     string   `json:"name"`
	Added    []string `json:"added,omitempty"`   // catalogues it would add
	Removed  []string `json:"removed,omitempty"` // catalogues it would drop
	Roles    []string `json:"roles"`             // what it would do in Ether
	Sample   string   `json:"sample,omitempty"`  // a live result, in words
	Problems []string `json:"problems,omitempty"`
}

AddonConfigResult is what a configuration would change, before saving.

type AddonFetcher

type AddonFetcher struct {
	// contains filtered or unexported fields
}

AddonFetcher reads an installed add-on's routes through Call, so every request is guarded, metered, redacted and counted in its health.

func (AddonFetcher) Fetch

func (f AddonFetcher) Fetch(ctx context.Context, route string, limit int64) ([]byte, error)

Fetch implements stremio.Fetcher.

type AddonInstall

type AddonInstall struct {
	Link    string
	Name    string
	Roles   []Capability
	Offline []string
	// Exclusive turns every other role off on a newly created record
	// (migrations keep exactly what the legacy entry did).
	Exclusive bool
}

AddonInstall is one request to install (or reuse) the add-on at Link and switch Roles on. Name is a fallback display name when the manifest cannot be read; Offline lists the resources a legacy entry proves the add-on serves, used to record it while its host is unreachable.

type AddonPreview

type AddonPreview struct {
	ID               string      `json:"id"`
	Name             string      `json:"name"`
	Description      string      `json:"description,omitempty"`
	Version          string      `json:"version,omitempty"`
	Host             string      `json:"host"`
	Roles            []RoleState `json:"roles"`
	Catalogs         []AddonCat  `json:"catalogs,omitempty"`
	AlreadyInstalled bool        `json:"alreadyInstalled,omitempty"`
	ConfigureFirst   bool        `json:"configureFirst,omitempty"`
}

AddonPreview is what the console shows before adding a Stremio add-on: the add-on, the roles it can play, and whether that link is already installed (adding it again only switches roles on).

type Author

type Author struct {
	Name string `json:"name,omitempty"`
	URL  string `json:"url,omitempty"`
}

Author names a plugin's author.

type Backends

type Backends struct {
	CatalogSearch func(ctx context.Context, query string, limit, offset int) (json.RawMessage, error)
	CatalogGet    func(ctx context.Context, id string) (json.RawMessage, error)
	ViewerHistory func(ctx context.Context, viewerID int64, limit, offset int) (json.RawMessage, error)
	ViewerNames   func(ctx context.Context, ids []int64) (json.RawMessage, error)
	// SetWatchState applies a watch-state write and returns its inverse
	// (the previous state) for the journal.
	SetWatchState func(ctx context.Context, viewerID int64, titleID string, watched bool) (previous bool, err error)
	// CollectionsUpsert creates or updates a collection and returns the
	// previous document (empty when new) for the journal.
	CollectionsUpsert func(ctx context.Context, doc json.RawMessage) (previous json.RawMessage, err error)
	// CollectionsDelete removes a collection by the id in its document;
	// used by undo for a create.
	CollectionsDelete func(ctx context.Context, doc json.RawMessage) error
	// Notice posts a console notice ("console notice" in SPEC §5.2).
	Notice func(text string)
}

Backends are the Ether services host calls end in. Any may be nil; the matching host call then returns ErrUnavailable. App wiring fills them.

type CallRecord

type CallRecord struct {
	ID       int64     `json:"id"`
	Plugin   string    `json:"plugin"`
	Route    string    `json:"route"`
	Method   string    `json:"method"`
	Status   int       `json:"status"`
	MS       int64     `json:"ms"`
	Error    string    `json:"error,omitempty"`
	Request  string    `json:"request,omitempty"`
	Response string    `json:"response,omitempty"`
	Created  time.Time `json:"created"`
}

CallRecord is one inspector entry (redacted, capped per plugin).

type CandidateFacts

type CandidateFacts struct {
	ReleaseID     string     `json:"releaseId"`
	Name          string     `json:"name"`
	Size          int64      `json:"size,omitempty"`
	Protocol      string     `json:"protocol,omitempty"` // usenet|torrent|direct
	SourceClass   string     `json:"sourceClass,omitempty"`
	Indexer       string     `json:"indexer,omitempty"`
	Resolution    string     `json:"resolution,omitempty"`
	HDR           []string   `json:"hdr,omitempty"`
	VideoCodec    string     `json:"videoCodec,omitempty"`
	AudioCodecs   []string   `json:"audioCodecs,omitempty"`
	AudioChannels string     `json:"audioChannels,omitempty"`
	Languages     []string   `json:"languages,omitempty"`
	Subtitles     []string   `json:"subtitles,omitempty"`
	ReleaseType   string     `json:"releaseType,omitempty"`
	Group         string     `json:"group,omitempty"`
	Cached        bool       `json:"cached,omitempty"`
	Verified      bool       `json:"verified,omitempty"`
	ClientCaps    ClientCaps `json:"clientCaps,omitempty"`
}

CandidateFacts is the hot-path input for ranking steps and rule conditions (SPEC §5.8), JSON-encoded. It never carries viewer data.

type Capability

type Capability string

Capability is one thing a plugin can do (SPEC §5.1.3).

const (
	CapStreamSource    Capability = "stream_source"
	CapDebridProvider  Capability = "debrid_provider"
	CapListSource      Capability = "list_source"
	CapMetadata        Capability = "metadata"
	CapPluginDirectory Capability = "plugin_directory"
	CapLiveTV          Capability = "livetv_channels"
	CapSubtitles       Capability = "subtitles"
	CapMarkers         Capability = "markers"
	CapNotify          Capability = "notify"
	CapEvents          Capability = "events"
	CapRankingStep     Capability = "ranking_step"
	CapRuleCondition   Capability = "rule_condition"
	CapTools           Capability = "tools"
	CapUI              Capability = "ui"
)

type Catalogue

type Catalogue interface {
	http.Handler
	// Resolve looks up a plugin in the verified catalogue by plugin ID (or
	// artifact URL) and optional version (empty means latest). It fails when
	// the plugin is not in the catalogue or is revoked.
	Resolve(ctx context.Context, idOrURL, version string) (CatalogueArtifact, error)
}

Catalogue is the signed catalogue hook used by the owner API, install and update flows (WS12).

type CatalogueArtifact

type CatalogueArtifact struct {
	ID           string
	Version      string
	URL          string
	SHA256       string
	Kind         Kind
	Permissions  Permissions
	Capabilities []Capability
}

CatalogueArtifact is a resolved version from the signed plugin catalogue (WS12).

type ChannelsMeta

type ChannelsMeta struct {
	Refresh string `json:"refresh,omitempty"`
	Test    bool   `json:"test"`
}

ChannelsMeta is mandatory for livetv_channels.

type ClientCaps

type ClientCaps struct {
	MaxHeight int      `json:"maxHeight,omitempty"`
	HDR       []string `json:"hdr,omitempty"`
	Codecs    []string `json:"codecs,omitempty"`
}

ClientCaps is what the playing client can show.

type CollectionInputError

type CollectionInputError struct{ Message string }

CollectionInputError is a safe, actionable validation failure supplied by the collection backend. It survives the hosted runtime as a client error.

func (*CollectionInputError) Error

func (e *CollectionInputError) Error() string

type CommandMeta

type CommandMeta struct {
	Key    string `json:"key"`
	Label  string `json:"label"`
	Action string `json:"action"` // plugin action name
}

CommandMeta is one contributed ⌘K command.

type Contributes

type Contributes struct {
	Directories    []DirectoryMeta     `json:"directories,omitempty"`
	Metadata       *MetadataMeta       `json:"metadata,omitempty"`
	Source         *SourceMeta         `json:"source,omitempty"`
	Channels       *ChannelsMeta       `json:"channels,omitempty"`
	Subtitles      *SubtitlesMeta      `json:"subtitles,omitempty"`
	Markers        *MarkersMeta        `json:"markers,omitempty"`
	Notification   *NotificationMeta   `json:"notification,omitempty"`
	Events         *EventsMeta         `json:"events,omitempty"`
	Integration    *IntegrationMeta    `json:"integration,omitempty"`
	ListSources    []ListSourceMeta    `json:"listSources,omitempty"`
	RankingSteps   []RankingStepMeta   `json:"rankingSteps,omitempty"`
	RuleConditions []RuleConditionMeta `json:"ruleConditions,omitempty"`
	Tools          []ToolMeta          `json:"tools,omitempty"`
	Pages          []PageMeta          `json:"pages,omitempty"`
	Slots          []SlotMeta          `json:"slots,omitempty"`
	Commands       []CommandMeta       `json:"commands,omitempty"`
}

Contributes carries per-capability metadata and UI contributions.

type Deps

type Deps struct {
	DB            *sql.DB
	Sealer        config.FieldSealer
	Registry      *extend.Registry
	Tools         *tools.Catalogue
	Notifications *notify.Service
	Logger        *slog.Logger
	// IsLeader reports cluster leadership; nil means always leader.
	IsLeader func() bool
	// Bus broadcasts invalidate:plugins; nil disables it.
	Bus clusterbus.Bus
	// FetchClient fetches manifests and artifacts; nil builds one from
	// safefetch and egress CapPlugins. Tests inject an httptest client.
	FetchClient *http.Client
	// CheckPackage runs the conformance kit during install previews.
	CheckPackage func(context.Context, []byte, map[string][]byte) error
	Backends     Backends
	Now          func() time.Time
}

Deps are the plugin host's explicit dependencies.

type Detail

type Detail struct {
	Summary
	Manifest    Manifest `json:"manifest"`
	Permissions struct {
		Lines        []string    `json:"lines"`
		Declared     Permissions `json:"declared"`
		Grants       Grants      `json:"grants"`
		NeedsViewers bool        `json:"needsViewers"`
		NeedsFolders bool        `json:"needsFolders"`
	} `json:"permissions"`
	HealthDetail Health `json:"healthDetail"`
	Privacy      struct {
		DeclaredHosts []string       `json:"declaredHosts"`
		ObservedHosts map[string]int `json:"observedHosts"`
		Undeclared    []string       `json:"undeclared,omitempty"`
	} `json:"privacy"`
	Impact struct {
		Calls       int   `json:"calls"`
		Errors      int   `json:"errors"`
		P50MS       int64 `json:"p50Ms"`
		P95MS       int64 `json:"p95Ms"`
		EgressBytes int64 `json:"egressBytes"`
		KVBytes     int64 `json:"kvBytes"`
		Slow        int64 `json:"slow"`
		EventDrops  int64 `json:"eventDrops"`
		Restarts    int64 `json:"restarts"`
	} `json:"impact"`
	// AddedPermissions is set when an update is waiting and asks for more.
	AddedPermissions []string `json:"addedPermissions,omitempty"`
}

Detail is the plugin drawer payload: overview, permissions, health, the privacy label and impact.

type DirectoryEntry

type DirectoryEntry struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Version     string `json:"version,omitempty"`
	ManifestURL string `json:"manifestURL,omitempty"`
	Unsupported string `json:"unsupported,omitempty"`
}

DirectoryEntry is a discovery suggestion from a native or adapted provider. ManifestURL must pass the normal fetch/preview/install policy before use. Directory claims never confer verified status or install permissions.

type DirectoryMeta

type DirectoryMeta struct {
	Key   string `json:"key"`
	Label string `json:"label"`
}

DirectoryMeta declares a protocol-neutral plugin discovery source. Entries are suggestions, never installation authorization or verification.

type DirectoryResult

type DirectoryResult struct {
	Entries []DirectoryEntry `json:"entries"`
}

type EtherBlock

type EtherBlock struct {
	Protocol int `json:"protocol"`
	// Stremio marks a native plugin that answers Stremio add-on routes,
	// such as an add-on opened and edited in Studio: Ether talks to it
	// exactly as to the add-on it came from.
	Stremio      bool         `json:"stremio,omitempty"`
	Kind         Kind         `json:"kind"`
	Capabilities []Capability `json:"capabilities,omitempty"`
	// Summary is plain text or a "$t:key" reference into Strings.
	Summary string      `json:"summary,omitempty"`
	Author  Author      `json:"author,omitempty"`
	License string      `json:"license,omitempty"`
	Repo    string      `json:"repo,omitempty"`
	Icon    string      `json:"icon,omitempty"`
	Runtime RuntimeDecl `json:"runtime"`
	// Cluster is "leader" (default) or "every_node".
	Cluster     string      `json:"cluster,omitempty"`
	Health      HealthDecl  `json:"health,omitempty"`
	Permissions Permissions `json:"permissions,omitempty"`
	// Settings is a settings schema (SPEC §5.5), {} or {"$ref":"file"}.
	Settings json.RawMessage `json:"settings,omitempty"`
	// Strings maps a locale to a file path (string) or an inline
	// key-to-text object. "en" is required when any $t: key is used.
	Strings     map[string]json.RawMessage `json:"strings,omitempty"`
	Schedule    []ScheduleEntry            `json:"schedule,omitempty"`
	Contributes Contributes                `json:"contributes,omitempty"`
}

EtherBlock is the `ether` object of a manifest.

type Event

type Event struct {
	ID       string          `json:"id"`
	Name     string          `json:"name"`
	At       time.Time       `json:"at"`
	ViewerID int64           `json:"viewerId,omitempty"`
	Payload  json.RawMessage `json:"payload,omitempty"`
}

Event is one normalized Ether event (SPEC §5.7). Payload is JSON with no secrets; viewer identity appears only for plugins granted it (the host strips it per plugin at delivery).

type EventsMeta

type EventsMeta struct {
	Subscribe []string `json:"subscribe"`
}

EventsMeta is mandatory for events.

type FetchRequest

type FetchRequest struct {
	Method  string            `json:"method"`
	URL     string            `json:"url"`
	Headers map[string]string `json:"headers,omitempty"`
	Body    []byte            `json:"body,omitempty"`
}

FetchRequest is one plugin outbound HTTP call.

type FetchResponse

type FetchResponse struct {
	Status  int               `json:"status"`
	Headers map[string]string `json:"headers,omitempty"`
	Body    []byte            `json:"body,omitempty"`
}

FetchResponse is the answer to a FetchRequest (body capped at 10 MiB).

type FieldError

type FieldError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

FieldError is one validation problem, keyed by a dotted field path.

type FolderEntry

type FolderEntry struct {
	Name  string `json:"name"`
	Path  string `json:"path"`
	Dir   bool   `json:"dir"`
	Size  int64  `json:"size,omitempty"`
	Mtime int64  `json:"mtime,omitempty"` // unix seconds
}

FolderEntry is one entry from FoldersList or FoldersStat.

type Grants

type Grants struct {
	Viewers []int64  `json:"viewers,omitempty"`
	Folders []string `json:"folders,omitempty"`
	Revoked []string `json:"revoked,omitempty"`
	LAN     bool     `json:"lan,omitempty"`
}

Grants is what the owner has granted one plugin on top of its manifest permissions. Revoked lists permission keys the owner has taken back ("network", "catalog", "viewers.identity", "viewers.watch_history", "viewers.write_watch_state", "collections", "folders", "secrets", "events").

type Health

type Health struct {
	Status      string         `json:"status"` // ok|attention|error|disabled
	Reason      string         `json:"reason,omitempty"`
	Failures    []int64        `json:"-"` // unix seconds, pruned to 10 minutes
	Restarts    int64          `json:"restarts"`
	Slow        int64          `json:"slow"`
	Drops       int64          `json:"drops"`
	LastError   string         `json:"lastError,omitempty"`
	LastOK      time.Time      `json:"lastOk,omitempty"`
	NextRetry   time.Time      `json:"nextRetry,omitempty"`
	Observed    map[string]int `json:"observed,omitempty"` // host -> fetch count
	EgressBytes int64          `json:"egressBytes"`
	// RankingSkipped counts the resolves since the plugin's last complete
	// ranking that left its ranking step out because it ran past its time
	// budget; zero once a ranking finishes in time again.
	RankingSkipped   int64     `json:"rankingSkipped"`
	RankingSkippedAt time.Time `json:"rankingSkippedAt,omitempty"`
}

Health is one plugin's health and impact record.

type HealthDecl

type HealthDecl struct {
	Interval string `json:"interval,omitempty"`
}

HealthDecl declares the health-check interval (minimum 15s).

type Host

type Host interface {
	KVGet(ctx context.Context, key string) ([]byte, bool, error)
	KVPut(ctx context.Context, key string, value []byte) error
	KVDelete(ctx context.Context, key string) error
	KVList(ctx context.Context, prefix string) ([]string, error)

	// SecretGet returns a sealed setting the manifest declared in
	// permissions.secrets. Hosted plugins only.
	SecretGet(ctx context.Context, name string) (string, error)

	// HTTPFetch fetches through egress and safefetch; only manifest
	// network hosts are allowed. Observed hosts feed the privacy label.
	HTTPFetch(ctx context.Context, req FetchRequest) (FetchResponse, error)

	// CatalogSearch and CatalogGet need catalog: read. Results are JSON
	// title summaries produced by the injected catalog backend.
	CatalogSearch(ctx context.Context, query string, limit, offset int) (json.RawMessage, error)
	CatalogGet(ctx context.Context, id string) (json.RawMessage, error)

	// ViewerHistory needs viewers.watch_history for that viewer.
	ViewerGranted(ctx context.Context) (json.RawMessage, error)
	ViewerHistory(ctx context.Context, viewerID int64, limit, offset int) (json.RawMessage, error)
	// SetWatchState needs viewers.write_watch_state; journalled.
	SetWatchState(ctx context.Context, viewerID int64, titleID string, watched bool) error
	// CollectionsUpsert needs collections: write; journalled.
	CollectionsUpsert(ctx context.Context, collection json.RawMessage) error

	// FoldersList and FoldersStat need folders: read and an owner-picked
	// root. They never return file contents.
	FoldersList(ctx context.Context, root, path string) ([]FolderEntry, error)
	FoldersStat(ctx context.Context, root, path string) (FolderEntry, error)

	// Settings returns the plugin's current non-secret settings. Plugins
	// read them per request instead of keeping them in instance memory,
	// because hosted plugins run in a pool of instances that restart.
	// Secrets stay behind SecretGet.
	Settings(ctx context.Context) (map[string]any, error)

	// NotifyOwner posts a console notice (rate-limited).
	NotifyOwner(ctx context.Context, text string) error
	// Log appends to the plugin's log ring (redacted, rate-limited).
	Log(ctx context.Context, level, msg string, attrs map[string]any) error
}

Host is the set of Ether services a plugin may call (SPEC §5.1.5). Every method re-checks the plugin's live grants, so a revoke takes effect mid-call. Writes are journalled; in shadow mode they are recorded without applying. The hosted runtime exposes Host as imports; remote plugins reach it over /ether/plugin-host/* (WS8b).

type HotBudgets

type HotBudgets struct {
	PerCandidate time.Duration `json:"perCandidateNs"`
	PerBatch     time.Duration `json:"perBatchNs"`
}

HotBudgets are the effective hot-path limits for one plugin.

func EffectiveBudgets

func EffectiveBudgets(lim wasm.Limits) HotBudgets

EffectiveBudgets mirrors the engine's clamping: the budgets a manifest gets as SPEC 5.2 states them, in reference-machine time. The engine scales them by its measured speed (wasm.HotScale); Budgets reports that.

type HotOutcome

type HotOutcome struct {
	// Status is ok, error, timeout, forbidden, aborted or governance.
	Status   string        `json:"status"`
	Duration time.Duration `json:"durationNs"`
	Units    int64         `json:"units"`
	// Slow counts candidates that went neutral on budget.
	Slow     int  `json:"slow"`
	MemoHits int  `json:"memoHits"`
	Trap     bool `json:"trap,omitempty"`
}

HotOutcome classifies one hot-path call for health and replay.

func (HotOutcome) Timeout

func (o HotOutcome) Timeout() bool

Timeout reports whether the call ran out of time or budget.

type HotRuntime

type HotRuntime interface {
	Runtime
	// RankBatch returns one key per candidate (higher ranks first). A
	// candidate that exceeded its budget gets the neutral key 0 and counts
	// in HotOutcome.Slow; a batch that ran out of time returns neutral keys
	// for the rest.
	RankBatch(ctx context.Context, cands []CandidateFacts, config json.RawMessage) (keys []int64, out HotOutcome, err error)
	// CondMatch evaluates a rule condition; false when the budget runs out.
	CondMatch(ctx context.Context, cand CandidateFacts, operator string, value json.RawMessage) (match bool, out HotOutcome, err error)
	HasRankKey() bool
	HasCondMatch() bool
	// Budgets reports the effective per-candidate and per-batch limits.
	Budgets() HotBudgets
}

HotRuntime is implemented by hosted runtimes whose plugin exports the hot-path ABI (SPEC §5.2): ranking steps and rule conditions. The ranking adapters reach it through Manager.Hot. Budgets are the manifest's (lowered, never raised) and every call reports a classified outcome.

type InstallOptions

type InstallOptions struct {
	Grants     Grants
	Settings   map[string]any
	AutoUpdate bool
	Shadow     bool
	// AcceptAddedPermissions confirms an update that asks for more.
	AcceptAddedPermissions bool
}

InstallOptions carries the owner's install-time picks.

type Installed

type Installed struct {
	ID       string
	Manifest Manifest
	// SourceURL is where the manifest came from (remote endpoint base).
	SourceURL string
	// Files returns an artifact file (entry, module, strings, schema) for
	// the installed version, or false. Hosted runtimes load code through it.
	Files func(path string) ([]byte, bool)
	// Host is the per-plugin host-services handle (SPEC §5.1.5).
	Host Host
}

Installed is the host's record of one installed plugin, handed to a RuntimeFactory when an instance is opened.

type IntegrationMeta

type IntegrationMeta struct {
	Category string   `json:"category"`
	Adds     []string `json:"adds"`    // 1..4 plain lines or $t: keys
	Connect  string   `json:"connect"` // fields|device_code|none
	Test     bool     `json:"test,omitempty"`
}

IntegrationMeta is mandatory for the integration kind.

type JournalEntry

type JournalEntry struct {
	ID      int64           `json:"id"`
	Plugin  string          `json:"plugin"`
	Op      string          `json:"op"`
	Detail  string          `json:"detail,omitempty"`
	Inverse json.RawMessage `json:"inverse,omitempty"`
	Shadow  bool            `json:"shadow"`
	Undone  bool            `json:"undone"`
	Created time.Time       `json:"created"`
}

JournalEntry is one recorded write and its inverse.

type Kind

type Kind string

Kind decides where the owner meets a plugin (SPEC §5.1.1).

const (
	KindSource              Kind = "source"
	KindIntegration         Kind = "integration"
	KindNotificationChannel Kind = "notification_channel"
	KindLiveTVSource        Kind = "livetv_source"
	KindExtension           Kind = "extension"
)

type ListParamField

type ListParamField struct {
	Key         string `json:"key"`
	Name        string `json:"name"`
	Kind        string `json:"kind"`
	Label       string `json:"label"`
	Help        string `json:"help"`
	Hint        string `json:"hint"`
	Required    bool   `json:"required"`
	Placeholder string `json:"placeholder"`
	Default     any    `json:"default"`
	Options     []struct {
		Value string `json:"value"`
		Label string `json:"label"`
	} `json:"options"`
}

func ParseListParamFields

func ParseListParamFields(raw json.RawMessage) []ListParamField

type ListSourceMeta

type ListSourceMeta struct {
	Key      string          `json:"key"`
	Label    string          `json:"label"`
	Hint     string          `json:"hint,omitempty"`
	Catalog  string          `json:"catalog"`
	Params   json.RawMessage `json:"params,omitempty"`
	MaxItems int             `json:"maxItems,omitempty"` // ≤ 2000
	Refresh  string          `json:"refresh,omitempty"`  // ≥ 1h
}

ListSourceMeta is one contributed list source.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager is the plugin host. It is safe for concurrent use.

func New

func New(deps Deps) (*Manager, error)

New builds the host and re-registers descriptors for enabled plugins.

func (*Manager) Addon

func (m *Manager) Addon(rec Record) stremio.Addon

Addon is the installed plugin as a Stremio add-on: its Stremio manifest read through Fetcher.

func (*Manager) AddonConfig

func (m *Manager) AddonConfig(ctx context.Context, id string) (AddonConfig, error)

AddonConfig describes an installed add-on's settings for the console.

func (*Manager) AddonConfigPage

func (m *Manager) AddonConfigPage(ctx context.Context, id string) (string, error)

AddonConfigPage opens the configure page of an installed add-on.

func (*Manager) AddonFetch

func (m *Manager) AddonFetch(ctx context.Context, id, route string) (Response, error)

AddonFetch reads one Stremio route from the add-on behind plugin id: the add-on itself, or the link a Studio-edited version of it carries. It uses the same guarded, plain add-on transport as an imported add-on.

func (m *Manager) AddonLink(ctx context.Context, id string) (string, error)

AddonLink returns the sealed add-on link behind a plugin, for carrying it to a Studio-built replacement or copy. Never send it to a client.

func (*Manager) AddonLinkConfigPage

func (m *Manager) AddonLinkConfigPage(link string) (string, error)

AddonLinkConfigPage opens the configure page for an add-on not yet installed, from its install link.

func (*Manager) ApplyUpdate

func (m *Manager) ApplyUpdate(ctx context.Context, id string, acceptAdded bool) (Record, error)

ApplyUpdate installs a pending update previously stored by CheckUpdate.

func (*Manager) BrowseDirectory

func (m *Manager) BrowseDirectory(ctx context.Context, plugin, key string) (DirectoryResult, error)

BrowseDirectory is the common owner discovery path. No network request is made during registry rendering or ordinary media browse. Disabling/removing a provider prevents both cached descriptors and direct API calls from use.

func (*Manager) Call

func (m *Manager) Call(ctx context.Context, pluginID string, req Request) (Response, error)

Call makes one call to a plugin through its runtime, records it in the inspector ring (secrets redacted) and feeds failures into health. Adapters (WS9) call plugins only through here.

func (*Manager) CallStream

func (m *Manager) CallStream(ctx context.Context, pluginID string, req Request) (Response, io.ReadCloser, error)

CallStream returns a streaming response. The call deadline remains active until the body is closed; callers must close it on every path.

func (*Manager) CheckHealth

func (m *Manager) CheckHealth(ctx context.Context, id string) (Health, error)

CheckHealth calls /ether/health once and updates the health record. A failing call feeds the failure policy through Call already; a plugin reporting attention or error is recorded without counting as a crash.

func (*Manager) CheckUpdate

func (m *Manager) CheckUpdate(ctx context.Context, id string) (*Preview, bool, error)

CheckUpdate fetches the plugin's source again. When a newer version exists it is stored as a pending update; auto-update applies it at once only for verified plugins whose permissions did not grow (SPEC §5.4).

func (*Manager) Configure

func (m *Manager) Configure(ctx context.Context, id string, incoming map[string]any) (map[string]string, error)

Configure validates incoming settings against the schema, stores them (secrets sealed per plugin; a "Saved" value keeps the stored secret), then calls the plugin's /ether/configure and surfaces its field errors.

func (*Manager) DeliverOutbox

func (m *Manager) DeliverOutbox(ctx context.Context, pluginID string) error

DeliverOutbox sends queued events to one plugin in batches of 50 and deletes the ones it acks. A missing ack keeps events for redelivery (at least once).

func (*Manager) FetchPickedSubtitle

func (m *Manager) FetchPickedSubtitle(ctx context.Context, plugin, title, subtitle, token, rawURL string) ([]byte, error)

FetchPickedSubtitle consumes the same one-shot authorization as a plugin download call. Every redirect is checked against the declared hosts.

func (*Manager) Fetcher

func (m *Manager) Fetcher(plugin string) AddonFetcher

Fetcher returns the stremio.Fetcher for an installed plugin.

func (*Manager) Get

func (m *Manager) Get(ctx context.Context, id string) (Record, error)

Get returns one installed plugin.

func (*Manager) GetSettings

func (m *Manager) GetSettings(ctx context.Context, id string) (map[string]any, json.RawMessage, error)

GetSettings returns settings with secrets replaced by "Saved".

func (*Manager) GrantsOf

func (m *Manager) GrantsOf(ctx context.Context, id string) (Grants, error)

GrantsOf returns one plugin's grants.

func (*Manager) Handler

func (m *Manager) Handler() http.Handler

Handler serves the owner API. It is mounted at /api/plugins by the app, behind owner auth; paths here are relative to that mount.

func (*Manager) HealthOf

func (m *Manager) HealthOf(ctx context.Context, id string) (Health, error)

HealthOf returns one plugin's health record.

func (*Manager) HostFor

func (m *Manager) HostFor(pluginID string) Host

HostFor returns the host-services handle for one plugin (SPEC §5.1.5).

func (*Manager) Hot

func (m *Manager) Hot(ctx context.Context, pluginID string) (HotRuntime, bool)

Hot returns the hosted runtime of an enabled plugin when it exports the hot-path ABI, loading it if needed. It may block on a load, so it is for background work (replay, Studio); the play path uses HotReady.

func (*Manager) HotReady

func (m *Manager) HotReady(pluginID string) (HotRuntime, bool)

HotReady is the play-path accessor. It never blocks: it returns the plugin's hosted runtime only when it is already loaded, and otherwise starts loading it in the background and reports false, so the caller treats the plugin's steps and conditions as neutral for this resolve. Plugins in shadow mode are never ready on the play path. Runtimes are dropped on update, disable, remove and failure (closeRuntime) and warmed again when descriptors sync.

func (*Manager) Install

func (m *Manager) Install(ctx context.Context, p *Preview, opts InstallOptions) (Record, error)

Install installs (or updates to) a previewed source.

func (*Manager) InstallAddon

func (m *Manager) InstallAddon(ctx context.Context, in AddonInstall) (Record, error)

InstallAddon installs the add-on at link, or finds it when that link is already installed, and switches the requested roles on. A private host is trusted as LAN: the owner pasted it.

func (*Manager) Journal

func (m *Manager) Journal(ctx context.Context, id string, limit int, shadowOnly bool) ([]JournalEntry, error)

Journal returns a plugin's journal (liveOnly: applied and not undone; shadowOnly is handled by the caller through Journal).

func (*Manager) List

func (m *Manager) List(ctx context.Context) ([]Record, error)

List returns every installed plugin.

func (*Manager) MintSubtitlePick

func (m *Manager) MintSubtitlePick(plugin, title, subtitle string) (string, error)

MintSubtitlePick is called only after a viewer selects a listed subtitle.

func (*Manager) MintSubtitleURLPick

func (m *Manager) MintSubtitleURLPick(plugin, title, subtitle, rawURL string) (string, error)

MintSubtitleURLPick binds a user's selection to the exact URL returned by a Stremio subtitle search. A foreign manifest grants no arbitrary host access.

func (*Manager) OnRemove

func (m *Manager) OnRemove(fn func(ctx context.Context, id string))

OnRemove registers a callback run after a plugin is uninstalled.

func (*Manager) OnSync

func (m *Manager) OnSync(fn func(ctx context.Context, r Record))

OnSync registers a callback notified whenever a plugin's enabled state or manifest changes (install, update, rollback, enable, disable, auto-disable, or remove). Any already-installed plugins are synced immediately.

func (*Manager) Options

func (m *Manager) Options(ctx context.Context, id, field string) (json.RawMessage, error)

Options calls /ether/options/{field} for runtime field options.

func (*Manager) PackageFiles

func (m *Manager) PackageFiles(ctx context.Context, id string) (map[string][]byte, error)

PackageFiles returns an installed plugin's own files for its current version, so Studio can open it for editing.

func (*Manager) PluginSettings

func (m *Manager) PluginSettings(ctx context.Context, pluginID string) (map[string]any, error)

PluginSettings returns a plugin's stored settings with its own secrets, for delivery to that plugin only (remote sidecars read it from the authenticated plugin-host API after a restart or reconnect).

func (*Manager) PreviewCatalogGet

func (m *Manager) PreviewCatalogGet(ctx context.Context, id string) (json.RawMessage, bool, error)

func (*Manager) PreviewCatalogSearch

func (m *Manager) PreviewCatalogSearch(ctx context.Context, query string, limit, offset int) (json.RawMessage, bool, error)

PreviewCatalogSearch and PreviewCatalogGet give a draft read access to the real catalogue, the way catalog: read does once installed. ok is false when the server has no catalogue backend (tests, early startup).

func (*Manager) PreviewFetch

func (m *Manager) PreviewFetch(ctx context.Context, network []string, req FetchRequest) (FetchResponse, error)

PreviewFetch performs a draft plugin's http.fetch for Studio's preview: the same SSRF-guarded client and host allowlist as an installed plugin, checked against the draft's own declared network permissions, so a preview shows the real data the plugin will see once installed.

func (*Manager) PreviewInstall

func (m *Manager) PreviewInstall(ctx context.Context, src Source) (*Preview, error)

PreviewInstall fetches and validates a source without installing it.

func (*Manager) Publish

func (m *Manager) Publish(ctx context.Context, ev Event)

Publish normalizes and fans out one Ether event: it is written to the 7-day event log and queued for every enabled plugin that subscribed to it (and was not revoked). Viewer identity is stripped per plugin unless the plugin's grants allow it.

func (*Manager) RecordFailure

func (m *Manager) RecordFailure(ctx context.Context, pluginID, reason string)

RecordFailure reports an adapter-observed failure (such as an HTTP 5xx or malformed payload) into the plugin's health record and failure policy.

func (*Manager) RecordRanking

func (m *Manager) RecordRanking(ctx context.Context, pluginID string, r RankingReport)

RecordRanking records a resolve's hot-path outcome for one plugin. A ranking step left out for running past its budget flags the plugin for the owner until one of its rankings finishes in time again; the periodic health check does not clear it, since the plugin's own health answer says nothing about its ranking. A resolve where the plugin kept to time and was not being skipped writes nothing. Safe for concurrent use; the ranking adapters call it when a resolve ends.

func (*Manager) RecordSlow

func (m *Manager) RecordSlow(ctx context.Context, pluginID string, n int)

RecordSlow adds n slow calls or slow hot-path candidates to a plugin's health and flags it for the owner's attention. It is safe for concurrent use; the ranking adapters call it after a resolve.

func (*Manager) RegisterRuntime

func (m *Manager) RegisterRuntime(kind string, f RuntimeFactory)

RegisterRuntime adds a runtime factory for a runtime kind ("hosted" or "remote"). WS8 calls it at wiring time. Safe to call after Start.

func (*Manager) RelayHandler

func (m *Manager) RelayHandler() http.Handler

RelayHandler serves /api/addon-relay/{token}/{path...} without the console's sign-in: the token is the permission.

func (*Manager) Remove

func (m *Manager) Remove(ctx context.Context, id string, undoFirst bool) error

Remove uninstalls a plugin: it offers undo first (when undoFirst, every live journal inverse is replayed), then purges KV, secrets, grants, artifacts, logs and its registry entries.

func (*Manager) ResolveText

func (m *Manager) ResolveText(ctx context.Context, plugin, key, locale string) (string, bool)

ResolveText looks up key in the plugin's strings for locale, then en.

func (*Manager) Revoke

func (m *Manager) Revoke(ctx context.Context, id, reason string) (bool, error)

Revoke disables an installed plugin that was marked revoked in the signed catalogue, records the reason in its health, and posts a console notice (SPEC WS12, §10). It returns true when an installed plugin was newly disabled or updated with the revocation reason.

func (*Manager) Rollback

func (m *Manager) Rollback(ctx context.Context, id string) (Record, error)

Rollback returns a plugin to its previous version (one step).

func (*Manager) Run

func (m *Manager) Run(ctx context.Context)

Run drives health checks, scheduled jobs and outbox delivery until ctx ends. Leader plugins run jobs and event delivery only on the leader; every_node plugins run everywhere. Health checks run on every node.

func (*Manager) SaveAddonConfig

func (m *Manager) SaveAddonConfig(ctx context.Context, id string, ch AddonConfigChange) (Record, error)

SaveAddonConfig applies new settings in place: the plugin keeps its id, roles, pins and collections; only its sealed link and manifest change.

func (*Manager) SetCatalogue

func (m *Manager) SetCatalogue(c Catalogue)

SetCatalogue attaches the signed plugin catalogue (WS12).

func (*Manager) SetEnabled

func (m *Manager) SetEnabled(ctx context.Context, id string, enabled bool) error

SetEnabled enables or disables a plugin and refreshes its descriptors.

func (*Manager) SetGrants

func (m *Manager) SetGrants(ctx context.Context, id string, g Grants) error

SetGrants replaces one plugin's grants. Host calls re-check grants on every call, so a revoke applies mid-call.

func (*Manager) SetRoles

func (m *Manager) SetRoles(ctx context.Context, id string, off []Capability) (Record, error)

SetRoles switches an installed add-on's roles on or off: off lists the capabilities it should stop playing (its catalogs, streams, subtitles, Live TV channels…). Only roles its manifest offers can be named.

func (*Manager) SetShadow

func (m *Manager) SetShadow(ctx context.Context, id string, shadow bool) error

SetShadow turns shadow mode on or off.

func (*Manager) SettingsSchema

func (m *Manager) SettingsSchema(ctx context.Context, id string) (json.RawMessage, error)

SettingsSchema returns the resolved settings schema for a plugin.

func (*Manager) Start

func (m *Manager) Start(ctx context.Context) error

Start re-registers descriptors and subscribes to cluster invalidations. Run starts the health, scheduler and outbox loops.

func (*Manager) StremioResource

func (m *Manager) StremioResource(ctx context.Context, plugin, resource, typ, id string, extra map[string]string) (Response, error)

StremioResource calls declared read-only resources through the same guarded, metered runtime used by native adapters. It never treats registration as a call.

func (*Manager) SubscribeInvalidations

func (m *Manager) SubscribeInvalidations() func()

SubscribeInvalidations re-syncs descriptors when another node changes a plugin. Returns an unsubscribe function (nil bus: no-op).

func (*Manager) Test

func (m *Manager) Test(ctx context.Context, id string) (TestResult, error)

Test calls the plugin's /ether/test.

func (*Manager) TestAddonConfig

func (m *Manager) TestAddonConfig(ctx context.Context, id string, ch AddonConfigChange) (AddonConfigResult, error)

TestAddonConfig loads the add-on with the new settings without saving.

func (*Manager) TextResolver

func (m *Manager) TextResolver() extend.TextResolver

TextResolver resolves a plugin string for a locale, falling back to en.

func (*Manager) UndoAll

func (m *Manager) UndoAll(ctx context.Context, pluginID string) (int, error)

UndoAll replays a plugin's live journal inverses, newest first, and returns how many entries were undone.

func (*Manager) UndoSince

func (m *Manager) UndoSince(ctx context.Context, pluginID string, since int64) (int, error)

UndoSince reverses the plugin's changes back to and including entry since (0 means all of them), newest first, so an older change is never reversed underneath a later one.

type Manifest

type Manifest struct {
	ID            string                `json:"id"`
	Version       string                `json:"version"`
	Name          string                `json:"name"`
	Description   string                `json:"description,omitempty"`
	Background    string                `json:"background,omitempty"`
	Types         []string              `json:"types,omitempty"`
	AddonCatalogs []StremioCat          `json:"addonCatalogs,omitempty"`
	Catalogs      []StremioCat          `json:"catalogs,omitempty"`
	Resources     stremio.Resources     `json:"resources,omitempty"`
	IDPrefixes    []string              `json:"idPrefixes,omitempty"`
	BehaviorHints stremio.BehaviorHints `json:"behaviorHints,omitzero"`
	Ether         *EtherBlock           `json:"ether,omitempty"`
	// contains filtered or unexported fields
}

Manifest is a plugin's manifest.json: the Stremio manifest fields, typed by the one Stremio model (internal/stremio), plus Ether's fields under Ether (SPEC §5.1.1). A manifest with a nil Ether is a plain Stremio add-on. Every Stremio rule (resources, ids, catalogs, extras) is the library's, reached through Stremio().

func ParseManifest

func ParseManifest(raw []byte) (Manifest, error)

ParseManifest decodes manifest JSON. Unknown fields are ignored (SPEC §5.1.1). Call Validate before trusting the result.

func (Manifest) AcceptsResource

func (m Manifest) AcceptsResource(name, typ, id string) bool

AcceptsResource reports whether the add-on serves resource for typ and id (internal/stremio.Manifest.TakesID).

func (Manifest) AddonRoles

func (m Manifest) AddonRoles() []Capability

AddonRoles are the roles a plain Stremio add-on can play, derived from its manifest, whether or not the owner switched them off.

func (Manifest) CatalogsOfType

func (m Manifest) CatalogsOfType(typ string) []StremioCat

CatalogsOfType returns the catalogs of one content type.

func (Manifest) ConfigurationRequired

func (m Manifest) ConfigurationRequired() bool

ConfigurationRequired reports an add-on installed from its unconfigured link.

func (Manifest) EffectiveEther

func (m Manifest) EffectiveEther() *EtherBlock

EffectiveEther describes native adapters without modifying the foreign manifest stored on disk or inventing permissions for a third-party add-on.

func (Manifest) HasCapability

func (m Manifest) HasCapability(c Capability) bool

HasCapability reports whether the manifest declares c.

func (Manifest) HasResource

func (m Manifest) HasResource(name string) bool

HasResource reports whether the manifest declares the named resource.

func (Manifest) IsStremioOnly

func (m Manifest) IsStremioOnly() bool

IsStremioOnly reports whether m is a plain Stremio add-on (no ether block).

func (Manifest) RuntimeKind

func (m Manifest) RuntimeKind() string

RuntimeKind is the runtime that serves the plugin. A plain Stremio add-on (no ether block) is always reached over HTTP: "remote".

func (Manifest) Serves

func (m Manifest) Serves(name, typ string) bool

Serves reports whether the named resource answers content type typ.

func (Manifest) ServesTV

func (m Manifest) ServesTV() bool

ServesTV reports whether a plain Stremio add-on is a Live TV source: it lists `tv` catalogs and answers `tv` streams.

func (Manifest) SpeaksStremio

func (m Manifest) SpeaksStremio() bool

SpeaksStremio reports whether Ether reads this plugin's catalogues, metadata, subtitles, channels and directories over Stremio routes: a plain add-on, or a native plugin built from one in Studio.

func (Manifest) Stremio

func (m Manifest) Stremio() stremio.Manifest

Stremio is the manifest's Stremio part.

func (*Manifest) UnmarshalJSON

func (m *Manifest) UnmarshalJSON(b []byte) error

UnmarshalJSON completes a Stremio-backed plugin's roles from its catalogs and resources wherever a manifest is read: packages, stored records and drafts alike.

func (Manifest) Validate

func (m Manifest) Validate() error

Validate checks every field rule in SPEC §5.1.1 and §5.1.3 and the copy rules for summary and contribution labels. It returns a *ValidationError listing all problems, or nil.

type MarkersMeta

type MarkersMeta struct {
	Kinds []string `json:"kinds"` // intro|credits|recap|preview
}

MarkersMeta is mandatory for markers.

type MetadataMeta

type MetadataMeta struct {
	Types []string `json:"types"`
}

MetadataMeta declares a provider of canonical catalogue records. Resolution happens during explicit materialization, never as part of ordinary browsing. Ether-native providers implement POST /ether/metadata/resolve using core.MetadataRequest and a {"title": core.Title} response.

type NotificationMeta

type NotificationMeta struct {
	Fields    json.RawMessage `json:"fields,omitempty"`
	SendTest  bool            `json:"sendTest"`
	Formats   []string        `json:"formats,omitempty"`
	MaxLength int             `json:"maxLength,omitempty"`
}

NotificationMeta is mandatory for notify.

type PageMeta

type PageMeta struct {
	Key   string `json:"key"`
	Label string `json:"label"`
	Route string `json:"route"` // plugin route returning {spec,data}
	Group string `json:"group,omitempty"`
	Icon  string `json:"icon,omitempty"`
}

PageMeta is one contributed console page (ViewSpec, SPEC §5.6).

type PermissionIncreaseError

type PermissionIncreaseError struct{ Added []string }

PermissionIncreaseError reports an update that asks for more access.

func (*PermissionIncreaseError) Error

func (e *PermissionIncreaseError) Error() string

type Permissions

type Permissions struct {
	Network     []string    `json:"network,omitempty" yaml:"network,omitempty"`
	Catalog     string      `json:"catalog,omitempty" yaml:"catalog,omitempty"` // ""|none|read
	Viewers     ViewerPerms `json:"viewers,omitempty" yaml:"viewers,omitempty"`
	Collections string      `json:"collections,omitempty" yaml:"collections,omitempty"` // ""|none|write
	Folders     string      `json:"folders,omitempty" yaml:"folders,omitempty"`         // ""|none|read
	Secrets     []string    `json:"secrets,omitempty" yaml:"secrets,omitempty"`
}

Permissions is what a plugin asks for; anything not declared is denied (SPEC §5.4). Empty means none.

type Preview

type Preview struct {
	Manifest         Manifest `json:"manifest"`
	PermissionLines  []string `json:"permissions"`
	NeedsViewers     bool     `json:"needsViewers"`
	NeedsFolders     bool     `json:"needsFolders"`
	Verified         bool     `json:"verified"`
	StremioOnly      bool     `json:"stremioOnly"`
	AlreadyInstalled bool     `json:"alreadyInstalled,omitempty"`
	InstalledVersion string   `json:"installedVersion,omitempty"`
	AddedPermissions []string `json:"addedPermissions,omitempty"`
	// contains filtered or unexported fields
}

Preview is what the install sheet shows before the owner confirms.

type RankingReport

type RankingReport struct {
	// Slow counts candidates and conditions that went neutral on budget.
	Slow int
	// Skipped counts ranking steps left out because the plugin ran past
	// its time budget; Ranked counts ranking steps that finished in time.
	Skipped, Ranked int
}

RankingReport is what one resolve saw of a plugin on the hot path.

type RankingStepMeta

type RankingStepMeta struct {
	Key             string          `json:"key"`
	Label           string          `json:"label"`
	Hint            string          `json:"hint,omitempty"`
	Editor          json.RawMessage `json:"editor,omitempty"`
	DefaultPosition int             `json:"defaultPosition,omitempty"`
	Budget          json.RawMessage `json:"budget,omitempty"`
}

RankingStepMeta is one contributed ranking step (hosted only).

type Record

type Record struct {
	ID            string
	Name          string
	Version       string
	Kind          string
	Manifest      Manifest
	ManifestRaw   json.RawMessage
	SourceKind    string // url|github|pkg|studio|catalogue
	SourceURL     string
	Enabled       bool
	Verified      bool
	Shadow        bool
	AutoUpdate    bool
	UpdateVersion string

	PrevVersion string
	InstalledAt time.Time
	UpdatedAt   time.Time
	// RolesOff are the capabilities the owner switched off (a Stremio
	// add-on's catalogs, streams, subtitles…). Manifest already reflects
	// them: EffectiveEther leaves them out.
	RolesOff []Capability
	// contains filtered or unexported fields
}

Record is one installed plugin as stored.

type Request

type Request struct {
	PluginID string            `json:"pluginId"`
	Method   string            `json:"method"`
	Path     string            `json:"path"`
	Query    map[string]string `json:"query,omitempty"`
	Headers  map[string]string `json:"headers,omitempty"`
	Body     json.RawMessage   `json:"body,omitempty"`
	// Locale is the console locale for calls that return copy.
	Locale string `json:"locale,omitempty"`
}

Request is one HTTP-shaped call from Ether to a plugin (SPEC §5.1.2). Hosted plugins receive the same object in-process.

type Response

type Response struct {
	Status  int               `json:"status"`
	Headers map[string]string `json:"headers,omitempty"`
	Body    json.RawMessage   `json:"body,omitempty"`
}

Response is a plugin's answer to a Request.

type RoleState

type RoleState struct {
	Role Capability `json:"role"`
	On   bool       `json:"on"`
}

RoleState is one role of an add-on for the console.

type RuleConditionMeta

type RuleConditionMeta struct {
	Key       string          `json:"key"`
	Label     string          `json:"label"`
	Operators []string        `json:"operators,omitempty"`
	Editor    json.RawMessage `json:"editor,omitempty"`
}

RuleConditionMeta is one contributed rule condition (hosted only).

type Runtime

type Runtime interface {
	Call(ctx context.Context, req Request) (Response, error)
	Close() error
}

Runtime is how the host reaches one installed plugin. WS8a (hosted) and WS8b (remote) implement it; tests use a fake. Call must honour ctx deadlines. Close releases the instance; the host may open a fresh one after a failure (restart with backoff).

type RuntimeDecl

type RuntimeDecl struct {
	Kind   string `json:"kind"` // "hosted" or "remote"
	Entry  string `json:"entry,omitempty"`
	Module string `json:"module,omitempty"`
}

RuntimeDecl declares how a plugin runs: hosted (Entry JS or Module WASM) or remote (the manifest URL or a tunnel is the endpoint).

type RuntimeFactory

type RuntimeFactory func(inst Installed) (Runtime, error)

RuntimeFactory opens a Runtime for one installed plugin. The factory is chosen by manifest ether.runtime.kind ("hosted" or "remote").

func HostedRuntimeFactory

func HostedRuntimeFactory(eng *wasm.Engine) RuntimeFactory

HostedRuntimeFactory opens hosted plugins (runtime.kind "hosted") on the wazero engine: JavaScript and TypeScript entries run on QuickJS, raw modules through the documented WASM ABI.

type ScheduleEntry

type ScheduleEntry struct {
	Every string `json:"every"`
	Call  string `json:"call"`
}

ScheduleEntry is one pull job Ether runs (minimum interval 15m).

type SlotMeta

type SlotMeta struct {
	Slot   string `json:"slot"` // title.panel|viewer.tab|dashboard.panel|actionMenu.*
	Key    string `json:"key"`
	Label  string `json:"label"`
	Route  string `json:"route,omitempty"`
	Action string `json:"action,omitempty"`
}

SlotMeta is one contributed console slot.

type Source

type Source struct {
	Kind string // url|github|pkg|studio|catalogue
	URL  string
	// ID and Version select an entry from the signed catalogue (kind "catalogue").
	ID      string
	Version string
	// SHA256 is the expected lowercase hex SHA-256 of the downloaded artifact.
	SHA256 string

	// Pkg is the .etherpkg bytes (kind "pkg") or a Studio package.
	Pkg []byte
	// Manifest and Files install directly (Studio with loose files).
	Manifest json.RawMessage
	Files    map[string][]byte
	// Verified is set only by the signed catalogue (WS12).
	Verified bool
	// LAN allows a private-network manifest URL (owner confirmed).
	LAN bool
	// contains filtered or unexported fields
}

Source describes where an install comes from.

type SourceMeta

type SourceMeta struct {
	Class     string          `json:"class"` // debrid|usenet|indexer|direct
	Abilities []string        `json:"abilities,omitempty"`
	Limits    json.RawMessage `json:"limits,omitempty"`
}

SourceMeta is mandatory for stream_source.

type StremioCat

type StremioCat = stremio.Catalog

StremioCat is one Stremio catalog declaration.

type StremioExtra

type StremioExtra = stremio.Extra

StremioExtra is one catalog extra declaration.

type SubtitlesMeta

type SubtitlesMeta struct {
	Languages json.RawMessage `json:"languages,omitempty"`
	Formats   []string        `json:"formats,omitempty"`
}

SubtitlesMeta is mandatory for subtitles. Languages is "any" or a list.

type Summary

type Summary struct {
	ID            string `json:"id"`
	Name          string `json:"name"`
	Version       string `json:"version"`
	Kind          string `json:"kind,omitempty"`
	Summary       string `json:"summary,omitempty"`
	Icon          string `json:"icon,omitempty"`
	Enabled       bool   `json:"enabled"`
	Verified      bool   `json:"verified"`
	Shadow        bool   `json:"shadow"`
	AutoUpdate    bool   `json:"autoUpdate"`
	UpdateVersion string `json:"updateVersion,omitempty"`
	PrevVersion   string `json:"prevVersion,omitempty"`
	Health        string `json:"health"`
	// HealthReason is the plain-language reason behind a non-ok health,
	// such as "Recent calls were slow."
	HealthReason string   `json:"healthReason,omitempty"`
	Capabilities []string `json:"capabilities,omitempty"`
	SourceKind   string   `json:"sourceKind,omitempty"`
	StremioOnly  bool     `json:"stremioOnly,omitempty"`
	InstalledAt  int64    `json:"installedAt"`
	// Roles and Host describe a Stremio add-on: what it can do, which of
	// that is switched on, and the host it is reached at (never the link,
	// which carries the owner's configuration).
	Roles []RoleState `json:"roles,omitempty"`
	Host  string      `json:"host,omitempty"`
}

Summary is one row of the installed list.

type TestResult

type TestResult struct {
	OK      bool     `json:"ok"`
	Message string   `json:"message,omitempty"`
	Details []string `json:"details,omitempty"`
}

TestResult is the plugin's /ether/test answer.

type ToolMeta

type ToolMeta struct {
	Name        string          `json:"name"`
	Title       string          `json:"title"`
	Description string          `json:"description"`
	InputSchema json.RawMessage `json:"inputSchema"`
	ReadOnly    bool            `json:"readOnly,omitempty"`
	Destructive bool            `json:"destructive,omitempty"`
	OpenWorld   bool            `json:"openWorld,omitempty"`
}

ToolMeta is one contributed agent tool.

type ValidationError

type ValidationError struct{ Fields []FieldError }

ValidationError carries every field problem found at once.

func (*ValidationError) Error

func (e *ValidationError) Error() string

type ViewerPerms

type ViewerPerms struct {
	Identity        string `json:"identity,omitempty" yaml:"identity,omitempty"`                   // none|granted_viewers|all
	WatchHistory    string `json:"watch_history,omitempty" yaml:"watch_history,omitempty"`         // none|granted_viewers|all
	WriteWatchState string `json:"write_watch_state,omitempty" yaml:"write_watch_state,omitempty"` // none|granted_viewers
}

ViewerPerms holds the three viewer permission levels.

Directories

Path Synopsis
Package adapters bridges runtime plugin contributions (WS9a list sources and WS9c Live TV channel sources) into Ether's collection, personalization and Live TV registries.
Package adapters bridges runtime plugin contributions (WS9a list sources and WS9c Live TV channel sources) into Ether's collection, personalization and Live TV registries.
Package kit implements Ether's plugin conformance kit and copy linter (SPEC §5.1–§5.6, WS11g), shared across install previews, Studio, the `ether plugin` CLI, and catalogue CI.
Package kit implements Ether's plugin conformance kit and copy linter (SPEC §5.1–§5.6, WS11g), shared across install previews, Studio, the `ether plugin` CLI, and catalogue CI.
Package remote is the remote plugin runtime (SPEC §5.3, WS8b).
Package remote is the remote plugin runtime (SPEC §5.3, WS8b).
Package studio provides the plugin Studio replay runner (WS11c) and the Studio and extensibility MCP and agent tools (WS11h).
Package studio provides the plugin Studio replay runner (WS11c) and the Studio and extensibility MCP and agent tools (WS11h).
Package wasm is Ether's hosted plugin runtime: it runs plugin code inside the Ether process, sandboxed, with no ports and no operating-system access.
Package wasm is Ether's hosted plugin runtime: it runs plugin code inside the Ether process, sandboxed, with no ports and no operating-system access.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL