@vestra-player/sdk 1.5.25 → 1.5.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,12 @@
1
+ import { capabilityInfo } from "./capability-rules.js";
1
2
  /**
2
3
  * Every manifest capability the player knows (`vestra/1`), in the player's
3
4
  * order, with the members it opens, the capabilities they also need and the
4
- * store's wording. The player's tests check that the names agree with the
5
- * player's `host/src/plugins.rs` and the labels with its plugin store.
5
+ * store's wording. Built from the player's own capability table (its
6
+ * `src/host/capabilities.json`, copied into the SDK) and the requests each
7
+ * member sends, the same rule `vestra-plugin check` and the SDK's
8
+ * TypeScript plugin apply; the player's tests check that the copy is
9
+ * current.
6
10
  *
7
11
  * A member not listed under any capability needs none (`plugin.tools`,
8
12
  * `plugin.storage`, `plugin.system`, `plugin.app`, `plugin.ui`,
@@ -21,237 +25,19 @@
21
25
  *
22
26
  * @since 1.5.14
23
27
  */
24
- export const CAPABILITY_INFO = Object.freeze({
25
- "producer.av": {
26
- apis: ["Player.openStream"],
27
- requires: ["playback.control"],
28
- label: "Provide audio and video it generates itself",
29
- risky: false,
30
- },
31
- "playback.read": {
32
- apis: [
33
- "Player.state",
34
- "Player.refresh",
35
- "Player.stats",
36
- "Player.chapters",
37
- "Player.audioDevices",
38
- "Timeline.marks",
39
- ],
40
- requires: [],
41
- label: "Read what is playing and the position",
42
- risky: false,
43
- },
44
- "playback.control": {
45
- apis: [
46
- "Player.open",
47
- "Player.setPaused",
48
- "Player.seek",
49
- "Player.setRate",
50
- "Player.restart",
51
- "Player.close",
52
- "Player.scrub",
53
- "Player.setOutput",
54
- "Player.provideSequence",
55
- "Player.next",
56
- "Player.previous",
57
- "Player.addAudioTrack",
58
- "Player.selectAudioTrack",
59
- "Player.step",
60
- "Player.setStepSeek",
61
- "Player.setLoop",
62
- "Player.setAudioDevice",
63
- "Power.preventSleep",
64
- ],
65
- requires: [],
66
- label: "Open media, control playback, keep the computer awake",
67
- risky: false,
68
- },
69
- "source.resolve": {
70
- apis: ["Resolvers.add"],
71
- requires: [],
72
- label: "Resolve online video addresses",
73
- risky: false,
74
- },
75
- events: {
76
- apis: [
77
- "Player.onChange",
78
- "Subtitles.onChange",
79
- "Timeline.onChange",
80
- "MainWindow.onChange",
81
- "Appearance.onChange",
82
- "PanelSlot.onChange",
83
- "Panel.onChange",
84
- "BackdropSlot.onChange",
85
- "Resolvers.onFileTypesChange",
86
- ],
87
- requires: [],
88
- label: "Receive playback changes as they happen",
89
- risky: false,
90
- },
91
- commands: {
92
- apis: ["Commands.add", "Host.run"],
93
- requires: [],
94
- label: "Add menu commands and shortcuts",
95
- risky: false,
96
- },
97
- "ui.window": {
98
- apis: ["Windows.create"],
99
- requires: [],
100
- label: "Open tool windows",
101
- risky: false,
102
- },
103
- "ui.mount": {
104
- apis: [
105
- "Panels.mount",
106
- "Panels.bottom",
107
- "Panels.sidebar",
108
- "Panels.backdrop",
109
- "PanelSlot.onChange",
110
- "Panel.onChange",
111
- "BackdropSlot.onChange",
112
- ],
113
- requires: [],
114
- label: "Show panels in the bottom bar, the sidebar or behind the video",
115
- risky: false,
116
- },
117
- "ui.dialog": {
118
- apis: [
119
- "Dialogs.confirm",
120
- "Dialogs.openFile",
121
- "Dialogs.openFiles",
122
- "Dialogs.openDirectory",
123
- "Dialogs.saveFile",
124
- ],
125
- requires: [],
126
- label: "Show file pickers and confirmations",
127
- risky: false,
128
- },
129
- "plugins.manage": {
130
- apis: [
131
- "Packages.validate",
132
- "Packages.validateArchive",
133
- "Packages.setEnabled",
134
- "Packages.reload",
135
- "Packages.uninstall",
136
- ],
137
- requires: [],
138
- label: "Install, uninstall and disable other plugins",
139
- risky: true,
140
- },
141
- "processing.video": {
142
- apis: ["Plugin.video.filter", "Plugin.video.view", "Plugin.video.render"],
143
- requires: ["playback.read"],
144
- label: "Process the picture (filters, shaders)",
145
- risky: false,
146
- },
147
- "processing.audio": {
148
- apis: ["Plugin.audio.filter"],
149
- requires: ["playback.read", "events"],
150
- label: "Process the sound",
151
- risky: false,
152
- },
153
- "subtitles.read": {
154
- apis: ["Subtitles.state", "Subtitles.onChange", "Subtitles.refresh"],
155
- requires: [],
156
- label: "Read subtitles",
157
- risky: false,
158
- },
159
- "subtitles.control": {
160
- apis: [
161
- "Subtitles.select",
162
- "Subtitles.load",
163
- "Subtitles.remove",
164
- "Subtitles.setStyle",
165
- ],
166
- requires: [],
167
- label: "Load and change subtitles",
168
- risky: false,
169
- },
170
- "media.read": {
171
- apis: ["MediaCapture.capture", "Snapshot.export"],
172
- requires: ["media.control"],
173
- label: "Take snapshots and export clips",
174
- risky: false,
175
- },
176
- "media.control": {
177
- apis: ["MediaCapture.capture", "Snapshot.export"],
178
- requires: ["media.read"],
179
- label: "Take snapshots and export clips",
180
- risky: false,
181
- },
182
- "ui.osd": {
183
- apis: ["Osd.show", "Osd.panel"],
184
- requires: [],
185
- label: "Show messages on the video",
186
- risky: false,
187
- },
188
- clipboard: {
189
- apis: [
190
- "Clipboard.readText",
191
- "Clipboard.writeText",
192
- "Clipboard.writeImage",
193
- ],
194
- requires: [],
195
- label: "Read and write the clipboard",
196
- risky: true,
197
- },
198
- "window.control": {
199
- apis: [
200
- "MainWindow.setVideoArea",
201
- "MainWindow.setAlwaysOnTop",
202
- "MainWindow.setCursorAutoHide",
203
- "MainWindow.setPictureInPicture",
204
- ],
205
- requires: [],
206
- label: "Adjust the main window (size, on top, picture-in-picture)",
207
- risky: false,
208
- },
209
- "bindings.manage": {
210
- apis: [
211
- "Bindings.list",
212
- "Bindings.set",
213
- "Bindings.reset",
214
- "Bindings.onChange",
215
- ],
216
- requires: [],
217
- label: "Change all shortcuts",
218
- risky: true,
219
- },
220
- "timeline.marks": {
221
- apis: ["Timeline.layer"],
222
- requires: [],
223
- label: "Show marks on the progress bar",
224
- risky: false,
225
- },
226
- "ui.drop": {
227
- apis: ["Drops.provide", "Panel.onDrop"],
228
- requires: [],
229
- label: "Receive files dropped on the player",
230
- risky: false,
231
- },
232
- "ui.settings": {
233
- apis: ["Settings.page", "Settings.show"],
234
- requires: [],
235
- label: "Add pages to the Options window",
236
- risky: false,
237
- },
238
- "ui.theme": {
239
- apis: ["Appearance.set", "Appearance.setLanguage"],
240
- requires: [],
241
- label: "Change the player's palette",
242
- risky: false,
243
- },
244
- secrets: {
245
- apis: ["Secrets.get", "Secrets.set", "Secrets.delete"],
246
- requires: [],
247
- label: "Store sign-in details in the system keychain",
248
- risky: true,
249
- },
250
- });
28
+ export const CAPABILITY_INFO = Object.freeze(Object.fromEntries(Object.entries(capabilityInfo()).map(([name, info]) => [
29
+ name,
30
+ Object.freeze({
31
+ apis: Object.freeze(info.apis),
32
+ requires: Object.freeze(info.requires),
33
+ label: info.label,
34
+ risky: info.risky,
35
+ }),
36
+ ])));
251
37
  /**
252
38
  * Manifest capabilities the player knows (`vestra/1`): the keys of
253
39
  * {@link CAPABILITY_INFO}, in the player's order. The same list as the
254
- * player's `host/src/plugins.rs`; the player's tests check that they agree.
40
+ * player's `host/capabilities.json`; the player's tests check that they agree.
255
41
  */
256
42
  export const CAPABILITIES = Object.freeze(Object.keys(CAPABILITY_INFO));
257
43
  /** The plugin API version this SDK implements (`manifest.apiVersion`). */
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The capability checker: every use of a plugin API member whose capability
3
+ * the plugin's `manifest.json` does not declare. `vestra-plugin check` (and
4
+ * so `build` and `pack`) and the TypeScript language-service plugin
5
+ * (`@vestra-player/sdk/ts-plugin`) both run it; the rule is
6
+ * `capability-rules.ts` (the player's table and the SDK's requests).
7
+ *
8
+ * It is type-aware: a use is a property access, an element access with a
9
+ * literal name or a destructured property whose symbol is the SDK's
10
+ * declaration of a member in the table, however the object was reached
11
+ * (`plugin.player.seek`, `const { player } = plugin; player.seek`, a
12
+ * parameter typed `Player`, a `Panel` from `panels.mount`). Not detected:
13
+ * access with a computed name (`player[name]`), values typed `any` or
14
+ * `unknown`, plain JavaScript without types, members inherited from `View`
15
+ * (their object comes from a call that is checked) and options that need
16
+ * more (a `processed` capture, a file resolver).
17
+ *
18
+ * The TypeScript module is passed in, never imported: the CLI uses the
19
+ * SDK's own compiler, the editor its TypeScript server's.
20
+ *
21
+ * @internal
22
+ */
23
+ import type TS from "typescript";
24
+ import type { CapabilityRules } from "./capability-rules.ts";
25
+ /** @internal The module plugins import the API types from. */
26
+ export declare const SDK_MODULE = "@vestra-player/sdk";
27
+ /** @internal The diagnostic code the editor shows (and its quick fix keys on). */
28
+ export declare const CAPABILITY_DIAGNOSTIC = 95301;
29
+ /** @internal One use of a member whose capability is not declared. */
30
+ export interface CapabilityFinding {
31
+ /** The source file, as the program names it. */
32
+ readonly file: string;
33
+ /** 1-based line and column of the member's name. */
34
+ readonly line: number;
35
+ readonly column: number;
36
+ /** Offset and length of the member's name in the file. */
37
+ readonly start: number;
38
+ readonly length: number;
39
+ /** The member, `Interface.member` (`Player.seek`). */
40
+ readonly api: string;
41
+ /** The capability to declare. */
42
+ readonly capability: string;
43
+ /** The request that needs it. */
44
+ readonly method: string;
45
+ /** The declared-or-needed capability whose `requires` names it. */
46
+ readonly requiredBy?: string;
47
+ /** One line for people. */
48
+ readonly message: string;
49
+ }
50
+ /** @internal */
51
+ export interface CheckOptions {
52
+ /** The files to check (others in the program are only read for types). */
53
+ readonly files: readonly string[];
54
+ /** The manifest's `capabilities`. */
55
+ readonly capabilities: readonly string[];
56
+ /** Default: the SDK's rules. */
57
+ readonly rules?: CapabilityRules;
58
+ }
59
+ /**
60
+ * Every use, in `options.files`, of a member whose capability the manifest
61
+ * does not declare, in file and position order.
62
+ *
63
+ * @internal
64
+ */
65
+ export declare function findUndeclaredCapabilities(ts: typeof TS, program: TS.Program, options: CheckOptions): CapabilityFinding[];
66
+ /**
67
+ * Whether a file belongs to the plugin's production code, given the
68
+ * directory of its `manifest.json`: not in `tests/`, `node_modules/`,
69
+ * `dist/`, `vendor/` or a dot directory (what `vestra-plugin build` leaves
70
+ * out), and not a declaration file.
71
+ *
72
+ * @internal
73
+ */
74
+ export declare function isPluginSource(directory: string, file: string): boolean;
75
+ /**
76
+ * The edit that adds a capability to a manifest's `capabilities` array, in
77
+ * the array's own layout; `undefined` when the text has no such array.
78
+ *
79
+ * @internal
80
+ */
81
+ export declare function addCapabilityEdit(ts: typeof TS, text: string, capability: string): {
82
+ start: number;
83
+ length: number;
84
+ newText: string;
85
+ } | undefined;
@@ -0,0 +1,203 @@
1
+ import { memberNeeds, RULES } from "./capability-rules.js";
2
+ /** @internal The module plugins import the API types from. */
3
+ export const SDK_MODULE = "@vestra-player/sdk";
4
+ /** @internal The diagnostic code the editor shows (and its quick fix keys on). */
5
+ export const CAPABILITY_DIAGNOSTIC = 95_301;
6
+ const memberCache = new WeakMap();
7
+ /**
8
+ * The module `@vestra-player/sdk` as the program resolved it: from the
9
+ * first import or export of it in the program's own (non-declaration) files.
10
+ */
11
+ function sdkModule(ts, program, checker) {
12
+ for (const file of program.getSourceFiles()) {
13
+ if (file.isDeclarationFile || program.isSourceFileFromExternalLibrary(file))
14
+ continue;
15
+ for (const statement of file.statements) {
16
+ const specifier = ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement)
17
+ ? statement.moduleSpecifier
18
+ : undefined;
19
+ if (specifier &&
20
+ ts.isStringLiteral(specifier) &&
21
+ specifier.text === SDK_MODULE) {
22
+ const module = checker.getSymbolAtLocation(specifier);
23
+ if (module)
24
+ return module;
25
+ }
26
+ }
27
+ }
28
+ return undefined;
29
+ }
30
+ /** The SDK's declarations of the members in the table. */
31
+ function sdkMembers(ts, program, rules) {
32
+ const cached = memberCache.get(program);
33
+ if (cached && rules === RULES)
34
+ return cached;
35
+ const members = new Map();
36
+ const checker = program.getTypeChecker();
37
+ const module = sdkModule(ts, program, checker);
38
+ if (module) {
39
+ const exports = new Map();
40
+ for (const symbol of checker.getExportsOfModule(module))
41
+ exports.set(symbol.name, symbol.flags & ts.SymbolFlags.Alias
42
+ ? checker.getAliasedSymbol(symbol)
43
+ : symbol);
44
+ for (const api of Object.keys(rules.apis)) {
45
+ const [head, ...path] = api.split(".");
46
+ const root = exports.get(head);
47
+ if (!root || !path.length)
48
+ continue;
49
+ let type = checker.getDeclaredTypeOfSymbol(root);
50
+ let property;
51
+ for (const name of path) {
52
+ property = type?.getProperty(name);
53
+ type = property && checker.getTypeOfSymbol(property);
54
+ }
55
+ for (const declaration of property?.declarations ?? [])
56
+ if (!members.has(declaration))
57
+ members.set(declaration, api);
58
+ }
59
+ }
60
+ if (rules === RULES)
61
+ memberCache.set(program, members);
62
+ return members;
63
+ }
64
+ /**
65
+ * Every use, in `options.files`, of a member whose capability the manifest
66
+ * does not declare, in file and position order.
67
+ *
68
+ * @internal
69
+ */
70
+ export function findUndeclaredCapabilities(ts, program, options) {
71
+ const rules = options.rules ?? RULES;
72
+ const files = options.files
73
+ .map((file) => program.getSourceFile(file))
74
+ .filter((file) => !!file);
75
+ if (!files.length)
76
+ return [];
77
+ const members = sdkMembers(ts, program, rules);
78
+ if (!members.size)
79
+ return [];
80
+ const checker = program.getTypeChecker();
81
+ const findings = [];
82
+ const memberOf = (symbol) => {
83
+ for (const declaration of symbol?.declarations ?? []) {
84
+ const api = members.get(declaration);
85
+ if (api)
86
+ return api;
87
+ }
88
+ return undefined;
89
+ };
90
+ for (const file of files) {
91
+ const report = (name, symbol) => {
92
+ const api = memberOf(symbol);
93
+ if (!api)
94
+ return;
95
+ const start = name.getStart(file);
96
+ const { line, character } = file.getLineAndCharacterOfPosition(start);
97
+ for (const need of memberNeeds(api, options.capabilities, rules) ?? []) {
98
+ if (options.capabilities.includes(need.capability))
99
+ continue;
100
+ findings.push({
101
+ file: file.fileName,
102
+ line: line + 1,
103
+ column: character + 1,
104
+ start,
105
+ length: name.getEnd() - start,
106
+ api,
107
+ capability: need.capability,
108
+ method: need.method,
109
+ ...(need.requiredBy ? { requiredBy: need.requiredBy } : {}),
110
+ message: need.requiredBy
111
+ ? `${api} needs the capability "${need.capability}" (required by "${need.requiredBy}"), which manifest.json does not declare`
112
+ : `${api} needs the capability "${need.capability}", which manifest.json does not declare`,
113
+ });
114
+ }
115
+ };
116
+ const visit = (node) => {
117
+ if (ts.isPropertyAccessExpression(node))
118
+ report(node.name, checker.getSymbolAtLocation(node.name));
119
+ else if (ts.isElementAccessExpression(node) &&
120
+ ts.isStringLiteralLike(node.argumentExpression))
121
+ report(node.argumentExpression, checker.getSymbolAtLocation(node.argumentExpression));
122
+ else if (ts.isBindingElement(node) &&
123
+ ts.isObjectBindingPattern(node.parent)) {
124
+ const name = node.propertyName ?? node.name;
125
+ const text = ts.isIdentifier(name) || ts.isStringLiteralLike(name)
126
+ ? name.text
127
+ : undefined;
128
+ if (text !== undefined)
129
+ report(name, checker.getTypeAtLocation(node.parent).getProperty(text));
130
+ }
131
+ ts.forEachChild(node, visit);
132
+ };
133
+ visit(file);
134
+ }
135
+ return findings;
136
+ }
137
+ /**
138
+ * Whether a file belongs to the plugin's production code, given the
139
+ * directory of its `manifest.json`: not in `tests/`, `node_modules/`,
140
+ * `dist/`, `vendor/` or a dot directory (what `vestra-plugin build` leaves
141
+ * out), and not a declaration file.
142
+ *
143
+ * @internal
144
+ */
145
+ export function isPluginSource(directory, file) {
146
+ const normal = (path) => path.replaceAll("\\", "/").replace(/\/$/, "");
147
+ const base = normal(directory);
148
+ const path = normal(file);
149
+ const caseless = /^[a-z]:\//i.test(path);
150
+ const same = (a, b) => caseless ? a.toLowerCase() === b.toLowerCase() : a === b;
151
+ if (!same(path.slice(0, base.length + 1), `${base}/`))
152
+ return false;
153
+ if (/\.d\.[mc]?ts$/.test(path) || !/\.[mc]?tsx?$/.test(path))
154
+ return false;
155
+ const parts = path
156
+ .slice(base.length + 1)
157
+ .split("/")
158
+ .slice(0, -1);
159
+ return !parts.some((part) => part.startsWith(".") ||
160
+ ["tests", "node_modules", "dist", "vendor"].includes(part));
161
+ }
162
+ /**
163
+ * The edit that adds a capability to a manifest's `capabilities` array, in
164
+ * the array's own layout; `undefined` when the text has no such array.
165
+ *
166
+ * @internal
167
+ */
168
+ export function addCapabilityEdit(ts, text, capability) {
169
+ const json = ts.parseJsonText("manifest.json", text);
170
+ const root = json.statements[0]?.expression;
171
+ if (!root || !ts.isObjectLiteralExpression(root))
172
+ return undefined;
173
+ const property = root.properties.find((each) => ts.isPropertyAssignment(each) &&
174
+ ts.isStringLiteral(each.name) &&
175
+ each.name.text === "capabilities");
176
+ const array = property?.initializer;
177
+ if (!array || !ts.isArrayLiteralExpression(array))
178
+ return undefined;
179
+ const quoted = JSON.stringify(capability);
180
+ const last = array.elements.at(-1);
181
+ if (!last) {
182
+ const start = array.getStart(json);
183
+ return {
184
+ start,
185
+ length: array.getEnd() - start,
186
+ newText: `[${quoted}]`,
187
+ };
188
+ }
189
+ const first = array.elements[0];
190
+ const lineStart = text.lastIndexOf("\n", first.getStart(json)) + 1;
191
+ const multiline = text
192
+ .slice(array.getStart(json), first.getStart(json))
193
+ .includes("\n");
194
+ const indent = text.slice(lineStart, first.getStart(json));
195
+ return {
196
+ start: last.getEnd(),
197
+ length: 0,
198
+ // The manifest's own line endings (CRLF on many Windows checkouts).
199
+ newText: multiline
200
+ ? `,${text.includes("\r\n") ? "\r\n" : "\n"}${indent}${quoted}`
201
+ : `, ${quoted}`,
202
+ };
203
+ }
@@ -0,0 +1,102 @@
1
+ import type { ApiRequest } from "./api-requests.ts";
2
+ /**
3
+ * What a request needs, as the player's `capabilities.json` writes it:
4
+ * `null` (nothing), a capability name, a choice by a string parameter, or a
5
+ * choice by whether the plugin declared a capability.
6
+ *
7
+ * @internal
8
+ */
9
+ export type Needs = null | string | {
10
+ readonly param: string;
11
+ readonly cases: Readonly<Record<string, string | null>>;
12
+ readonly otherwise: string | null;
13
+ } | {
14
+ readonly ifDeclared: string;
15
+ readonly then: string;
16
+ readonly otherwise: string;
17
+ };
18
+ /** @internal One RPC method of the player's table (`prefix.*` covers a prefix). */
19
+ export interface HostMethod {
20
+ readonly name: string;
21
+ readonly needs: Needs;
22
+ }
23
+ /** @internal One manifest capability of the player's table. */
24
+ export interface HostCapability {
25
+ readonly name: string;
26
+ readonly label: string;
27
+ readonly risky: boolean;
28
+ readonly requires: readonly string[];
29
+ readonly residency: string;
30
+ readonly methods: readonly HostMethod[];
31
+ }
32
+ /** @internal The player's `src/host/capabilities.json`. */
33
+ export interface HostCapabilities {
34
+ readonly about: string;
35
+ readonly apiVersion: string;
36
+ readonly capabilities: readonly HostCapability[];
37
+ readonly uncapable: readonly HostMethod[];
38
+ }
39
+ /** @internal Both tables; tests pass their own. */
40
+ export interface CapabilityRules {
41
+ readonly host: HostCapabilities;
42
+ readonly apis: Readonly<Record<string, readonly ApiRequest[]>>;
43
+ }
44
+ /** @internal The SDK's rules: the synced player table and `API_REQUESTS`. */
45
+ export declare const RULES: CapabilityRules;
46
+ /**
47
+ * The player's entry for a method, as the player routes it: the exact name
48
+ * first, then the first `prefix.*` entry whose prefix it starts with.
49
+ * `undefined`: the player does not know the method.
50
+ *
51
+ * @internal
52
+ */
53
+ export declare function hostMethod(method: string, host?: HostCapabilities): HostMethod | undefined;
54
+ /**
55
+ * The capability one request needs, as the player decides it
56
+ * (`capabilities::Route::needs`): `null` for none, `undefined` for a method
57
+ * the player does not know.
58
+ *
59
+ * @param method - The wire method.
60
+ * @param params - Its string parameters that a `Needs::Param` rule reads.
61
+ * @param declared - Whether the plugin declared a capability.
62
+ *
63
+ * @internal
64
+ */
65
+ export declare function requestNeeds(method: string, params: Readonly<Record<string, string>> | undefined, declared: (capability: string) => boolean, host?: HostCapabilities): string | null | undefined;
66
+ /**
67
+ * One capability a member needs: the request that needs it, and the
68
+ * capability whose `requires` brought it in (absent when the request needs
69
+ * it directly).
70
+ *
71
+ * @internal
72
+ */
73
+ export interface MemberNeed {
74
+ readonly capability: string;
75
+ readonly method: string;
76
+ readonly requiredBy?: string;
77
+ }
78
+ /**
79
+ * Every capability a member of the plugin API needs with the given manifest:
80
+ * those its requests need and, through `requires`, those they need in turn.
81
+ * `undefined`: the member is not in the table (it needs none).
82
+ *
83
+ * @param api - The member as `Interface.member` (`Player.seek`).
84
+ * @param capabilities - The manifest's capabilities.
85
+ *
86
+ * @internal
87
+ */
88
+ export declare function memberNeeds(api: string, capabilities: readonly string[], rules?: CapabilityRules): MemberNeed[] | undefined;
89
+ /**
90
+ * `CAPABILITY_INFO` from the tables: the label, highlight and requirements
91
+ * from the player's table; the members from `API_REQUESTS` (every member a
92
+ * request of which needs the capability, in the table's order; requests
93
+ * marked `also` are left out, as they are documented at the member).
94
+ *
95
+ * @internal
96
+ */
97
+ export declare function capabilityInfo(rules?: CapabilityRules): Record<string, {
98
+ apis: string[];
99
+ requires: string[];
100
+ label: string;
101
+ risky: boolean;
102
+ }>;