Documentation
¶
Overview ¶
Package extend is the descriptor registry the owner console renders from.
Built-ins and plugins describe the same things in the same shape: ranking steps, rule fields, collection and Live TV source kinds, notification channel kinds, integration cards, pages, navigation, commands, settings schemas, slots and agent tools. The console never keeps its own copy of these lists; it asks the registry. A plugin's contribution is therefore indistinguishable from a built-in one except for its Owner and badge.
The registry is in memory. Built-ins register at startup; the plugin host replaces a plugin's descriptors atomically when it is installed, updated, disabled or removed. Nothing here is persisted.
Index ¶
- Constants
- Variables
- func Handler(r *Registry, resolve TextResolver) http.Handler
- func Known(k Kind) bool
- func MetaOf[T any](d Descriptor) (T, error)
- func PluginID(plugin, id string) string
- type ChannelKindMeta
- type CollectionSourceMeta
- type CommandMeta
- type Descriptor
- type DescriptorOption
- type EventKindMeta
- type FieldOption
- type IntegrationMeta
- type IntegrationSwitch
- type Kind
- type LiveTVSourceMeta
- type NavItemMeta
- type PageMeta
- type PageSection
- type ParamField
- type ParamOption
- type RankingEditorKind
- type RankingStepMeta
- type Registry
- func (r *Registry) Get(kind Kind, id string) (Descriptor, bool)
- func (r *Registry) List(kinds ...Kind) []Descriptor
- func (r *Registry) MustRegister(ds ...Descriptor)
- func (r *Registry) Owned(owner string) []Descriptor
- func (r *Registry) Register(ds ...Descriptor) error
- func (r *Registry) RemoveOwner(owner string)
- func (r *Registry) ReplaceOwner(owner string, ds []Descriptor) error
- func (r *Registry) Subscribe(fn func()) (unsubscribe func())
- func (r *Registry) Version() uint64
- type Response
- type RowSourceMeta
- type RuleFieldMeta
- type SettingsField
- type SettingsSchemaMeta
- type Text
- type TextResolver
Constants ¶
const ( )
Nav groups for pages and nav items. The empty group is the unheaded top group in the sidebar.
const ( FieldText = "text" FieldSecret = "secret" FieldURL = "url" FieldSelect = "select" FieldHidden = "hidden" )
Settings field kinds (SPEC §5.5). The console renders a field from its kind; Secret says the value is stored sealed and never returned.
const ( RuleValueText = "text" // free text with equals/contains/regex RuleValueChoice = "choice" // free text with suggested choices RuleValueLanguage = "language" // exact declared-language match RuleValueNumber = "number" // a min/max range RuleValueNone = "none" // takes no value at all )
Rule value classes: how a condition value is edited and validated.
const Builtin = "builtin"
Builtin is the Owner of every descriptor Ether itself registers.
Variables ¶
var ErrDuplicate = errors.New("descriptor already registered")
ErrDuplicate is returned when a (kind, id) pair is already registered.
Functions ¶
func Handler ¶
func Handler(r *Registry, resolve TextResolver) http.Handler
Handler serves the registry to the owner console. Query parameters: kinds (comma separated; all when absent) and locale (for plugin strings). The ETag hashes the resolved response. A process-local version alone can repeat after restart and wrongly retain old plugin labels or capabilities.
func MetaOf ¶
func MetaOf[T any](d Descriptor) (T, error)
MetaOf decodes d.Meta into T. An empty Meta yields T's zero value.
Types ¶
type ChannelKindMeta ¶
type ChannelKindMeta struct {
Fields []SettingsField `json:"fields"`
// SendTest reports the kind supports the Send test button.
SendTest bool `json:"sendTest"`
// Selectable is false for kinds created another way (browser push is
// created by connecting a device, not picked in the drawer).
Selectable bool `json:"selectable"`
// Formats lists payload formats: "text", and "html" for email.
Formats []string `json:"formats,omitempty"`
// MaxLength caps the delivered message text, in runes (0 = no cap).
MaxLength int `json:"maxLength,omitempty"`
}
ChannelKindMeta is the Meta of a channelKind descriptor: the schema the console renders the channel drawer from.
type CollectionSourceMeta ¶
type CollectionSourceMeta struct {
Params []ParamField `json:"params,omitempty"`
// Editor names the console widget family that edits this kind today:
// none, tmdbChart, lookup, urlList, listSelect, discover, titles.
// WS1f's served-schema renderer replaces this with Params alone.
Editor string `json:"editor,omitempty"`
// Defaults is the new-source template: the console creates a source as
// {"kind": <id>, ...defaults}, then the owner fills required params.
Defaults json.RawMessage `json:"defaults,omitempty"`
// MediaTypes lists the collection media types this kind can feed.
MediaTypes []string `json:"mediaTypes,omitempty"`
}
CollectionSourceMeta is Descriptor.Meta for KindCollectionSource.
type CommandMeta ¶
type CommandMeta struct {
Route string `json:"route,omitempty"`
Action string `json:"action,omitempty"`
Keywords Text `json:"keywords,omitempty"`
// AgentSuggestion marks the command as an Ask Ether suggestion.
AgentSuggestion bool `json:"agentSuggestion,omitempty"`
// RoutePatterns limits an agent suggestion to console routes. A pattern
// is an exact route or a prefix ending in "/*".
RoutePatterns []string `json:"routePatterns,omitempty"`
}
CommandMeta is the Meta of a KindCommand descriptor. A command either navigates (Route) or runs a named action (Action). Agent suggestions are commands Ask Ether offers on matching routes instead of ⌘K actions; the descriptor label is the suggestion text.
type Descriptor ¶
type Descriptor struct {
Kind Kind `json:"kind"`
ID string `json:"id"`
Owner string `json:"owner"`
Label Text `json:"label"`
Hint Text `json:"hint,omitempty"`
// Icon is a lucide icon name for built-ins or an asset URL for plugins.
Icon string `json:"icon,omitempty"`
Group string `json:"group,omitempty"`
Order int `json:"order,omitempty"`
// Meta is kind-specific. Each kind's Go type lives in meta_<kind>.go and
// is mirrored in web/src/lib/extend.ts.
Meta json.RawMessage `json:"meta,omitempty"`
}
Descriptor is one thing the console can render or pick.
func (Descriptor) Validate ¶
func (d Descriptor) Validate() error
Validate checks the fields every descriptor needs.
func (Descriptor) WithMeta ¶
func (d Descriptor) WithMeta(v any) Descriptor
WithMeta returns d with Meta set to the JSON encoding of v.
type DescriptorOption ¶
DescriptorOption is one selectable value in a descriptor editor. Label is optional: when empty the console shows Value.
func Options ¶
func Options(values ...string) []DescriptorOption
Options builds DescriptorOptions from plain values whose display text is the value itself (codec names, source kinds, language codes).
type EventKindMeta ¶
type EventKindMeta struct {
// Automatic reports Ether produces the event itself, as opposed to the
// owner API only.
Automatic bool `json:"automatic"`
// Critical marks the events in the Critical alerts preset.
Critical bool `json:"critical,omitempty"`
// ChannelKinds, when set, restricts the event to these channel kinds
// (the weekly newsletter goes by email only).
ChannelKinds []string `json:"channelKinds,omitempty"`
}
EventKindMeta is the Meta of an eventKind descriptor. The label and hint carry the event's name and description.
type FieldOption ¶
FieldOption is one choice of a select field.
type IntegrationMeta ¶
type IntegrationMeta struct {
// Category is the Integrations page group key: required,
// watch-tracking, artwork-ratings, playback-extras or smart-features.
Category string `json:"category"`
// Logo names a built-in brand mark (for example "trakt") or an asset
// URL for plugins.
Logo string `json:"logo,omitempty"`
// Adds are 1 to 4 short chips naming what the integration adds.
Adds []Text `json:"adds,omitempty"`
// Connect is the connect mode: fields, device_code, custom or none.
Connect string `json:"connect,omitempty"`
// Tab is the integrations sub-tab key this card configures on.
Tab string `json:"tab,omitempty"`
// ConfigKey is the config section key (built-ins only).
ConfigKey string `json:"configKey,omitempty"`
// Endpoint is the config section endpoint used to save (built-ins only).
Endpoint string `json:"endpoint,omitempty"`
// Test names the test behaviour the console runs (built-ins only); it is
// a key into the console's compiled test map, like a page component.
Test string `json:"test,omitempty"`
// Flag is the boolean field the card switch writes.
Flag string `json:"flag,omitempty"`
// RequiredField must be non-empty before the integration can be
// enabled; the switch opens the setup drawer instead.
RequiredField string `json:"requiredField,omitempty"`
// ReadyField is the field whose presence means "configured" for the
// card status. It defaults to RequiredField.
ReadyField string `json:"readyField,omitempty"`
// PublicFallback means the integration works without ReadyField, at
// public limits; the card says so instead of asking for a key.
PublicFallback bool `json:"publicFallback,omitempty"`
// Form names a compiled console form for built-ins whose settings are
// not a plain schema form (for example "simkl" or "llm").
Form string `json:"form,omitempty"`
// Switches are the capability switches the card declares.
Switches []IntegrationSwitch `json:"switches,omitempty"`
}
IntegrationMeta is the Meta of a KindIntegration descriptor. The descriptor's Group is the card category and Hint the card description. Tab, ConfigKey, Endpoint and Flag are owner-API wiring for built-ins; plugin integrations leave them empty and configure through their plugin.
type IntegrationSwitch ¶
type IntegrationSwitch struct {
Key string `json:"key"`
Label Text `json:"label"`
Hint Text `json:"hint,omitempty"`
}
IntegrationSwitch is one capability switch an integration card exposes.
type Kind ¶
type Kind string
Kind names one family of descriptors. Adding a Kind is a contract change.
const ( KindRankingStep Kind = "rankingStep" KindRuleField Kind = "ruleField" KindCollectionSource Kind = "collectionSource" KindRowSource Kind = "rowSource" KindLiveTVSource Kind = "liveTVSource" KindChannelKind Kind = "channelKind" KindEventKind Kind = "eventKind" KindSubtitleProvider Kind = "subtitleProvider" KindMetadataProvider Kind = "metadataProvider" KindPluginDirectory Kind = "pluginDirectory" KindMarkerSource Kind = "markerSource" KindSourceKind Kind = "sourceKind" KindIntegration Kind = "integration" KindPage Kind = "page" KindCommand Kind = "command" KindSettingsSchema Kind = "settingsSchema" KindSlot Kind = "slot" KindWidget Kind = "widget" KindTool Kind = "tool" )
type LiveTVSourceMeta ¶
type LiveTVSourceMeta struct {
Params []ParamField `json:"params,omitempty"`
Editor string `json:"editor,omitempty"`
YieldsChannels bool `json:"yieldsChannels,omitempty"`
YieldsGuide bool `json:"yieldsGuide,omitempty"`
// Test reports whether the kind supports a connection test.
Test bool `json:"test,omitempty"`
}
LiveTVSourceMeta is Descriptor.Meta for KindLiveTVSource.
type NavItemMeta ¶
type NavItemMeta struct {
}
NavItemMeta is the Meta of a KindNavItem descriptor. The descriptor's Group is the nav group, Icon the lucide icon name (built-ins) or asset URL (plugins), and Order the position inside the group.
type PageMeta ¶
type PageMeta struct {
// Route is the console path, for example "/releases". Plugin pages omit
// it; their route is /p/<plugin>/<page>.
Route string `json:"route,omitempty"`
// a plugin page in its declared group.
NavGroup string `json:"navGroup,omitempty"`
// appear in the sidebar whenever they declare a nav group.
InNav bool `json:"inNav,omitempty"`
// Mobile keeps the page in the mobile tab bar.
Mobile bool `json:"mobile,omitempty"`
// Exact matches only the route itself in the sidebar.
Exact bool `json:"exact,omitempty"`
// Keywords are search aliases for the page itself.
Keywords Text `json:"keywords,omitempty"`
// Sections are the page's in-page sections in display order.
Sections []PageSection `json:"sections,omitempty"`
}
PageMeta is the Meta of a KindPage descriptor. Built-in pages stay compiled React components; the console maps the descriptor ID to its component. A plugin page renders through the plugin page route.
type PageSection ¶
type PageSection struct {
Key string `json:"key"`
// Label is declared with T for built-ins so the i18n extractor sees it.
Label Text `json:"label"`
// Keywords are search aliases, declared with T for built-ins. The
// console applies its search-terms translation to the source string,
// keeping the English terms beside the translated ones.
Keywords Text `json:"keywords,omitempty"`
}
PageSection is one in-page section (tab) the sitemap and ⌘K can target.
type ParamField ¶
type ParamField struct {
Name string `json:"name"`
Kind string `json:"kind"`
Label Text `json:"label"`
Hint Text `json:"hint,omitempty"`
Required bool `json:"required,omitempty"`
Options []ParamOption `json:"options,omitempty"`
// Lookup names a catalogue lookup ("person", "keyword", "collection",
// "company", "network", "watchProvider") for id-valued fields.
Lookup string `json:"lookup,omitempty"`
// Placeholder is display-only example text (a URL shape).
Placeholder string `json:"placeholder,omitempty"`
}
ParamField is one source-kind parameter. Kind uses the settings field kinds from docs/extend/SPEC.md §5.5 (text, url, select, number, bool, list, title, …), so the served-schema form (WS1f) can render it without translation. Name maps onto the persisted source object's JSON field.
type ParamOption ¶
type ParamOption struct {
Value string `json:"value"`
Label Text `json:"label"`
Media []string `json:"media,omitempty"`
}
ParamOption is one choice of a select-shaped source parameter.
type RankingEditorKind ¶
type RankingEditorKind string
RankingEditorKind names the inline editor a ranking step offers in the "What matters most" ladder.
const ( RankingEditorNone RankingEditorKind = "none" RankingEditorOrderedList RankingEditorKind = "ordered_list" RankingEditorChoice RankingEditorKind = "choice" RankingEditorRange RankingEditorKind = "range" RankingEditorToggle RankingEditorKind = "toggle" )
func (*RankingEditorKind) UnmarshalJSON ¶
func (k *RankingEditorKind) UnmarshalJSON(data []byte) error
UnmarshalJSON accepts a plain editor kind string, null, or a plugin manifest editor object ({"kind": "choice"}).
type RankingStepMeta ¶
type RankingStepMeta struct {
// Editor is the inline editor kind for this step's own setting.
Editor RankingEditorKind `json:"editor"`
// Options are the values the editor offers: the ordered choices for
// ordered_list and choice editors, the selectable bounds for range.
Options []DescriptorOption `json:"options,omitempty"`
// Choices are optional selectable choices when a step distinguishes
// choices from options.
Choices []DescriptorOption `json:"choices,omitempty"`
// Required steps must stay in every ladder (connection, verified).
Required bool `json:"required,omitempty"`
// CanMove is false for steps the ladder renders as Automatic: the owner
// cannot reorder them.
CanMove bool `json:"canMove"`
// DefaultPosition is the step's 0-based place in the default ladder.
// "Add a step" inserts a step ahead of the first ladder step with a
// larger DefaultPosition. Plugin steps are never auto-inserted; this
// only says where one lands when the owner adds it.
DefaultPosition int `json:"defaultPosition"`
// ConfigKey names the QualityProfile field the editor edits for
// built-in steps (preferredLanguages, preferredSources,
// preferredCodecs, preferredAudio, preferLargest, heightRange, hdr,
// customFormatScores).
// Plugin steps leave it empty; their settings arrive with WS9h.
ConfigKey string `json:"configKey,omitempty"`
}
RankingStepMeta is the Meta of a KindRankingStep descriptor.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds every descriptor. It is safe for concurrent use.
func (*Registry) Get ¶
func (r *Registry) Get(kind Kind, id string) (Descriptor, bool)
Get returns one descriptor.
func (*Registry) List ¶
func (r *Registry) List(kinds ...Kind) []Descriptor
List returns descriptors of the given kinds (all kinds when none are given), ordered by kind, group, order, then id.
func (*Registry) MustRegister ¶
func (r *Registry) MustRegister(ds ...Descriptor)
MustRegister is Register for built-ins whose descriptors are static: a failure is a programming error caught by tests.
func (*Registry) Owned ¶
func (r *Registry) Owned(owner string) []Descriptor
Owned returns every descriptor owned by owner.
func (*Registry) Register ¶
func (r *Registry) Register(ds ...Descriptor) error
Register adds built-in or plugin descriptors. It is all or nothing: on any error nothing is added.
func (*Registry) RemoveOwner ¶
RemoveOwner drops every descriptor owned by a plugin.
func (*Registry) ReplaceOwner ¶
func (r *Registry) ReplaceOwner(owner string, ds []Descriptor) error
ReplaceOwner atomically swaps every descriptor owned by owner for ds. The plugin host calls it on install, update, enable and disable. Built-ins cannot be replaced.
type Response ¶
type Response struct {
Version uint64 `json:"version"`
Descriptors []Descriptor `json:"descriptors"`
}
Response is the body of GET /api/extend/registry.
type RowSourceMeta ¶
type RowSourceMeta struct {
// HubID is the row pattern the Home Designer policy targets.
HubID string `json:"hubId"`
// RowKind is the personalization row kind, when the row is kind-backed.
RowKind string `json:"rowKind,omitempty"`
// Sections lists the pages this row can appear on (empty = every page).
Sections []string `json:"sections,omitempty"`
// Source is the row provenance (core.RowSource* for built-ins).
Source string `json:"source,omitempty"`
// CollectionSource links a list-backed row source to the collection
// source kind that gathers its titles (WS3/WS9 list sources).
CollectionSource string `json:"collectionSource,omitempty"`
}
RowSourceMeta is Descriptor.Meta for KindRowSource.
type RuleFieldMeta ¶
type RuleFieldMeta struct {
// ValueClass is one of the RuleValue* constants.
ValueClass string `json:"valueClass"`
// Operators are the comparisons the field accepts, in display order
// (equals, contains, regex, range).
Operators []string `json:"operators,omitempty"`
// DefaultOperator is preselected when a condition is created or
// retargeted to this field.
DefaultOperator string `json:"defaultOperator,omitempty"`
// Choices are suggested values for choice and language fields. Choice
// fields still accept free text; language choices are ISO codes the
// console labels through Intl.DisplayNames.
Choices []DescriptorOption `json:"choices,omitempty"`
// Unit is the value's unit for number fields ("GiB", "p").
Unit string `json:"unit,omitempty"`
// SingleValue fields can hold one value per release, so two required
// rules demanding different values conflict.
SingleValue bool `json:"singleValue,omitempty"`
// Editor carries a plugin manifest's raw ruleConditions[].editor
// (SPEC §5.1.3) when ValueClass was not set directly.
Editor json.RawMessage `json:"editor,omitempty"`
}
RuleFieldMeta is the Meta of a KindRuleField descriptor.
func (RuleFieldMeta) Resolved ¶
func (m RuleFieldMeta) Resolved() RuleFieldMeta
Resolved folds a plugin manifest Editor declaration into ValueClass, Choices, Unit and SingleValue when those fields were not set directly.
type SettingsField ¶
type SettingsField struct {
Key string `json:"key"`
Kind string `json:"kind"`
Label Text `json:"label"`
Hint Text `json:"hint,omitempty"`
Required bool `json:"required,omitempty"`
Secret bool `json:"secret,omitempty"`
Default string `json:"default,omitempty"`
Options []FieldOption `json:"options,omitempty"`
Wide bool `json:"wide,omitempty"`
}
SettingsField is one field of a kind's settings schema.
type SettingsSchemaMeta ¶
type SettingsSchemaMeta struct {
// ConfigKey is the config.json section a built-in form edits, when it
// edits one. Plugin settings leave it empty.
ConfigKey string `json:"configKey,omitempty"`
Schema json.RawMessage `json:"schema"`
}
SettingsSchemaMeta is the Meta of a KindSettingsSchema descriptor. Schema is an encoded internal/config/schema.Schema; it stays raw here so this package does not import the schema package (which imports this one for Text). The TypeScript mirror is SettingsSchemaMeta in web/src/pages/settings/SchemaForm.tsx.
type Text ¶
type Text struct {
Source string `json:"source,omitempty"`
Plugin string `json:"plugin,omitempty"`
Key string `json:"key,omitempty"`
Resolved string `json:"resolved,omitempty"`
}
Text is user-visible copy. Built-ins carry an English source string that the console translates through its catalogue (declare it with T so the i18n extractor finds it). Plugins carry a key into their own strings; the registry handler resolves it for the requested locale into Resolved.
func PluginText ¶
PluginText refers to key in plugin's strings.
type TextResolver ¶
TextResolver looks up a plugin string for a locale. It returns false when neither the locale nor English has the key.