完整类型参考
本页汇集 SDK 与 Issuer 的公开类型声明。Guest、分享与 Storage 可从 SDK 导入,无需从桌面源码复制类型。
查询方式
安装依赖见开发环境与依赖。本页用于集中查阅;具体工程的类型提示以已安装依赖的声明文件为准。
本页适合在写代码时查询参数和返回值。首次开发先从动手教程开始;功能能否运行还要看宿主支持表。
按需要展开下面一个领域即可,不必从头读完,也不要把跨文件声明整体粘进插件入口。
新代码的入口
优先使用当前 SDK 暴露的 ctx API,并以工程中安装的 .d.ts 为最终类型依据。下面标有 @deprecated、preview 或兼容字样的声明只用于读取旧成品和理解兼容边界,不是新插件的推荐写法。
上下文、账号与原生页面类型
index.d.ts
ts
/// <reference path="./host-modules.d.cts" />
/** This package defines the v2 wire/authoring contract. It does not grant Host permissions. */
import type { LoDashStatic } from 'lodash';
import type { HostIconName, HostAssetName, LODASH_METHODS } from './catalog.js';
export * from './catalog.js';
export * from './http.js';
export * from './library.js';
export * from './sockets.js';
import type { SocketAPI } from './sockets.js';
import type { GuestAPI, GuestBootstrapAPI } from './guests.js';
import type { PluginStorageAPI } from './storage.js';
import type { createHttpClient, HttpClientOptions } from './http.js';
export * from './music.js';
export * from './lyrics.js';
export * from './quality.js';
export * from './guests.js';
export * from './share.js';
export * from './storage.js';
export * from './accounts.js';
export * from './navigation.js';
export * from './permissions.js';
export * from './services.js';
export * from './modules.js';
import type { PluginModules } from './modules.js';
import type { HostServices, HostUI } from './services.js';
import type { PermissionGrant, PermissionGroupRequest, PermissionGroupResult } from './permissions.js';
import type { ChartMetadata, LyricsDocument, PlaylistMetadata, TrackMetadata } from './music.js';
import type { LibraryAPI, PlaylistImporterImplementation } from './library.js';
/** 宿主在插件自己的隔离环境提供这些 Lodash 方法;不会打入插件发行文件。 */
export type HostLodash = Pick<LoDashStatic, (typeof LODASH_METHODS)[number]>;
export * from './manifest.js';
import type { JsonValue, JsonObject, MaybePromise, Disposable, PluginManifest } from './manifest.js';
export interface ResourceRef {
pluginId: string;
providerId: string;
connectionId?: string;
kind: string;
id: string;
/** Opaque plugin-owned JSON persisted by the Host and returned only to this plugin. */
data?: JsonObject;
}
export interface AssetHandle {
readonly kind: 'asset';
readonly id: string;
}
export interface MediaLease {
/** @deprecated v0.2 providers return direct playback URLs. */
readonly kind: 'media';
readonly id: string;
}
export interface CredentialRef {
readonly kind: 'credential';
readonly id: string;
}
export interface UserIntentHandle {
readonly kind: 'user-intent';
readonly id: string;
}
export interface OperationContext {
id: string;
deadlineAt: number;
signal: AbortSignal;
connectionId?: string;
userIntent?: UserIntentHandle;
}
export interface ContentEntity {
ref: ResourceRef;
title: string;
subtitle?: string;
artwork?: AssetHandle;
playable?: boolean;
durationMs?: number;
capabilities: string[];
extensions?: JsonObject;
/** Standard metadata consumed directly by the Host; never raw platform response objects. */
metadata?: TrackMetadata;
playlist?: PlaylistMetadata;
chart?: ChartMetadata;
}
export interface Page<T> {
items: T[];
nextCursor?: string;
snapshotId?: string;
totalEstimate?: number;
}
export interface SearchRequest {
query: string;
kinds: string[];
filters: JsonObject;
cursor?: string;
/** Requested size; providers may clamp to their upstream API's supported range. */
limit: number;
}
export interface MusicFault {
code: 'RATE_LIMITED' | 'AUTH_REQUIRED' | 'ENTITLEMENT_EXPIRED' | 'NOT_FOUND' | 'REGION_UNAVAILABLE' | 'NETWORK_ERROR' | 'PERMISSION_DENIED' | 'UNSUPPORTED' | 'CANCELLED' | 'INTERNAL';
message: string;
retryAfterMs?: number;
retryable?: boolean;
recovery?: {
mode: 'default' | 'retry-later' | 'await-user' | 'stop-current';
maxWaitMs?: number;
actions?: ({
kind: 'retry' | 'choose-source' | 'cancel';
label?: string;
} | {
kind: 'plugin-command';
commandId: string;
label: string;
})[];
};
}
export type ResolveResult = {
ok: true;
url: string;
expiresAt?: number;
/** Headers the Host must attach while fetching this exact media URL. */
requestHeaders?: Record<string, string>;
} | {
ok: false;
error: MusicFault;
};
export interface TrackProvider {
search?(request: SearchRequest, operation: OperationContext): Promise<Page<ContentEntity>>;
resolve?(resource: ResourceRef, quality: string | undefined, operation: OperationContext): Promise<ResolveResult>;
lyrics?(resource: ResourceRef, operation: OperationContext): Promise<LyricsDocument>;
}
export interface PlaylistProvider {
search?(request: SearchRequest, operation: OperationContext): Promise<Page<ContentEntity>>;
categories?(operation: OperationContext): Promise<Page<ContentEntity>>;
list?(resource: ResourceRef, cursor: string | undefined, operation: OperationContext): Promise<Page<ContentEntity>>;
get?(resource: ResourceRef, cursor: string | undefined, operation: OperationContext): Promise<import('./library.js').PlaylistTrackPage>;
}
export interface ChartProvider {
list?(operation: OperationContext): Promise<Page<ContentEntity>>;
getTracks?(resource: ResourceRef, cursor: string | undefined, operation: OperationContext): Promise<Page<ContentEntity>>;
}
export interface SharingProvider {
describe?(resource: ResourceRef, policy: JsonObject, operation: OperationContext): Promise<JsonObject>;
}
export interface ProviderImplementation {
tracks?: TrackProvider;
playlists?: PlaylistProvider;
charts?: ChartProvider;
sharing?: SharingProvider;
/** @deprecated Use tracks.lyrics. Kept for v2 preview compatibility. */
lyrics?(resource: ResourceRef, operation: OperationContext): Promise<LyricsDocument>;
/** @deprecated Use tracks.search. */
search?(request: SearchRequest, operation: OperationContext): Promise<Page<ContentEntity>>;
/** @deprecated Use tracks.resolve. */
resolve?(resource: ResourceRef, quality: string | undefined, operation: OperationContext): Promise<ResolveResult>;
/** @deprecated Use playlists.categories. */
categories?(operation: OperationContext): Promise<Page<ContentEntity>>;
/** @deprecated Use playlists.list/get or charts.getTracks. */
list?(resource: ResourceRef, cursor: string | undefined, operation: OperationContext): Promise<Page<ContentEntity>>;
/** @deprecated Use sharing.describe. */
share?(resource: ResourceRef, policy: JsonObject, operation: OperationContext): Promise<JsonObject>;
}
export interface PluginContext extends HostServices {
modules: PluginModules;
readonly plugin: {
id: string;
version: string;
/** The validated installed manifest. Runtime configuration is manifest.config. */
manifest: Readonly<PluginManifest>;
};
/** 当前 Host 的协议版本与共享资源版本。 */
readonly host: {
apiVersion: string;
libraries: {
lodash: string;
icons: string;
assets: string;
vue?: string;
react?: string;
'react-dom'?: string;
};
mode: 'development' | 'production';
};
/** 本地纯计算工具,不通过 RPC 逐项执行。没有模板执行、mixin 或任意上下文构造能力。 */
readonly utils: {
readonly lodash: HostLodash;
};
/** 使用宿主已经拥有的图标,无需将 SVG/PNG 打入插件。 */
readonly icons: {
list(): readonly HostIconName[];
url(name: HostIconName): Promise<string>;
};
/** Host 静态资源与本插件资源句柄;不是任意文件路径访问接口。 */
readonly assets: {
list(): readonly HostAssetName[];
url(name: HostAssetName | AssetHandle): Promise<string>;
};
/** 已验证的配置。凭据字段由 Host 替换为引用,不返回主密钥。 */
config: {
get<T = JsonObject>(): Promise<Readonly<T>>;
};
/** Uses the application's own local/cloud playlists after Host permission checks. */
library: LibraryAPI;
/** Host-provided Socket.IO / WebSocket; no socket library is bundled into the plugin. */
sockets: SocketAPI;
playlistImporters: {
register(id: string, implementation: PlaylistImporterImplementation): Disposable;
};
lyricConverters: {
register(id: string, implementation: import('./lyrics.js').LyricConverter): Disposable;
};
providers: {
register(id: string, implementation: ProviderImplementation): Disposable;
};
actions: {
register<TInput extends JsonValue = JsonValue, TResult extends JsonValue | void = JsonValue | void>(id: string, handler: (input: TInput, operation: OperationContext) => MaybePromise<TResult>): Disposable;
};
permissions: {
getGranted(): Promise<PermissionGrant[]>;
requestGroup(request: PermissionGroupRequest): Promise<PermissionGroupResult>;
query(request: {
key: string;
scope?: JsonObject;
}): Promise<{
status: PermissionStatus;
}>;
request(request: {
key: string;
scope?: JsonObject;
intent?: UserIntentHandle;
}): Promise<{
status: PermissionStatus;
}>;
};
http: {
/** Axios-backed Host client with JSON/form helpers and typed results. */
create(options: HttpClientOptions): ReturnType<typeof createHttpClient>;
request(request: {
permissionKey: string;
url: string;
method?: string;
headers?: Record<string, string>;
body?: string;
timeoutMs?: number;
credential?: CredentialRef;
operation: OperationContext;
}): Promise<{
status: number;
headers: Record<string, string>;
body: JsonValue;
}>;
};
credentials: {
get(connectionId: string): Promise<CredentialRef | null>;
};
playback: {
failure(error: MusicFault): ResolveResult;
};
ui: HostUI & {
toast(message: {
message: string;
level?: 'info' | 'success' | 'warning' | 'error';
}): Promise<void>;
/** Opens the application's existing import dialog. This does not create a plugin Surface. */
playlistImport: {
/** Only this plugin's importers are offered. Omit importerId to let the user choose. */
open(request: {
importerId?: string;
initialValue?: string;
title?: string;
}): Promise<void>;
};
/** Requests an update through the Host UI. A queued result does not mean it was installed. */
pluginUpdate: {
request(request: {
version: string;
url: string;
notes?: string;
}): Promise<{
accepted: boolean;
updated: boolean;
version?: string;
queued?: boolean;
}>;
};
setState(surfaceId: string, state: JsonObject): Promise<void>;
notify(message: {
key: string;
level: 'info' | 'success' | 'warning' | 'error';
message: string;
}): Promise<void>;
openView(surfaceId: string): Promise<void>;
closeView(surfaceId: string): Promise<void>;
};
storage: PluginStorageAPI;
guests: GuestAPI & {
/** Legacy install-draft contract; availability depends on the Host. Prefer import on desktop. */
prepareInstall(request: {
adapterId: string;
artifactHandle: string;
operation: OperationContext;
}): Promise<{
draftId: string;
}>;
requestInstall(draftId: string, operation: OperationContext): Promise<{
guestId: string;
} | null>;
};
log: {
debug(message: string, data?: JsonValue): void;
info(message: string, data?: JsonValue): void;
warn(message: string, data?: JsonValue): void;
error(message: string, data?: JsonValue): void;
};
effects: {
add(dispose: Disposable): void;
};
}
export type PermissionStatus = 'undeclared' | 'prompt' | 'granted' | 'denied' | 'expired' | 'restricted' | 'unavailable';
export interface SurfaceContext extends Pick<PluginContext, 'host' | 'utils' | 'icons' | 'assets'> {
readonly root: HTMLElement;
readonly mount: {
kind: 'page' | 'slot';
slot?: import('./manifest.js').UISlotName;
mode?: 'append' | 'prepend' | 'wrap' | 'replace';
};
invoke(action: string, input: JsonValue): Promise<JsonValue>;
/** Close this mounted Surface after its current action has returned. */
close(): Promise<void>;
subscribe(handler: (state: JsonObject) => void): Disposable;
}
export interface GuestContext extends Pick<PluginContext, 'host' | 'utils'>, GuestBootstrapAPI {
readonly guestId: string;
expose(name: string, value: unknown): void;
invokeHost(method: string, input: JsonValue): Promise<JsonValue>;
}
export type LogicEntry = (ctx: PluginContext) => MaybePromise<void | Disposable>;
export type SurfaceEntry = (ctx: SurfaceContext) => MaybePromise<void | Disposable>;
export type GuestEntry = (ctx: GuestContext) => MaybePromise<void | Disposable>;
export declare function definePlugin(entry: LogicEntry): LogicEntry;
export declare function defineSurface(entry: SurfaceEntry): SurfaceEntry;
export { defineNativeView, assertNativeView } from './native-view.js';
export type { NativeView, NativeViewSection, NativeViewAction } from './native-view.js';
export declare function defineGuestAdapter(entry: GuestEntry): GuestEntry;
export declare function defineManifest(manifest: PluginManifest): PluginManifest;
export declare function definePluginConfig<const T extends JsonObject>(config: T): T;
export declare function failure(error: MusicFault): ResolveResult;
/** 基础声明式界面;由 Host 渲染,不会把插件函数放进主界面组件树。 */
export type UINode = {
type: 'section' | 'stack' | 'grid' | 'form';
title?: string;
submitAction?: string;
children: UINode[];
} | {
type: 'text';
text?: string;
label?: string;
bind?: string;
} | {
type: 'button';
label: string;
action: string;
} | {
type: 'host-content';
} | {
type: 'text-input' | 'input' | 'number' | 'toggle';
label: string;
bind: string;
placeholder?: string;
} | {
type: 'host-credential';
label: string;
bind: string;
permissionKey: string;
};
export interface UISchema {
schemaVersion: '1.0';
id?: string;
root: UINode;
}
export declare function defineUISchema(schema: UISchema): UISchema;manifest.d.ts
ts
import type { HostIconName, PermissionName } from './catalog.js';
import type { MenuContribution } from './services.js';
export type JsonValue = null | boolean | number | string | JsonValue[] | {
[key: string]: JsonValue;
};
export type JsonObject = {
[key: string]: JsonValue;
};
export type MaybePromise<T> = T | Promise<T>;
export type Disposable = () => MaybePromise<void>;
export type IconRef = {
kind: 'host';
name: HostIconName;
} | {
kind: 'asset';
resource: string;
};
export interface PermissionDeclaration {
key: string;
name: PermissionName;
scope?: JsonObject;
reason: string;
optional?: boolean;
requiredFor?: string[];
}
export interface ProviderDeclaration {
id: string;
name: string;
protocols: string[];
/** Ordered from lowest to highest; names have no intrinsic rank. */
qualities?: string[];
icon?: IconRef;
connectionMode?: 'none' | 'single' | 'multiple';
}
export interface SurfaceDeclaration {
id: string;
kind: 'schema' | 'web' | 'native';
/** Native: declared action returning NativeView; Web: module; Schema: JSON resource. */
entry: string;
/** Host chrome only. The plugin owns all content inside a web Surface. */
title?: string;
presentation?: {
kind: 'drawer' | 'modal';
placement?: 'left' | 'right' | 'top' | 'bottom';
size?: number;
};
/** Declared logic actions. Open runs after mounting; close runs once per session. */
lifecycle?: {
openAction?: string;
closeAction?: string;
};
}
export type HomeSectionKind = 'playlists' | 'charts' | 'custom';
export type UISlotName = 'home.header' | 'home.content.before' | 'home.content.after' | 'search.source-selector.after' | 'playlist.header.actions' | 'playlist.item.actions' | 'player.actions' | 'settings.sections';
export interface HomeSectionContribution {
id: string;
title: string;
kind: HomeSectionKind;
icon?: IconRef;
/** Required for custom sections; built-in playlist/chart sections keep the existing Host UI. */
view?: string;
providerIds?: string[];
order?: number;
}
export interface UIExtensionContribution {
id: string;
slot: UISlotName;
mode: 'append' | 'prepend' | 'wrap' | 'replace';
/** Sandboxed visible Surface. Plugin JavaScript never runs in the application renderer. */
view: string;
order?: number;
when?: {
loggedIn?: boolean;
route?: string;
};
}
export interface StyleContribution {
id: string;
resource: string;
scope: 'surface' | 'slot' | 'application';
slots?: UISlotName[];
order?: number;
}
export interface PluginManifest {
manifestVersion: 2;
id: string;
name: string;
version: string;
description?: string;
author?: string;
publisher?: string;
license?: string;
homepage?: string;
/** Static defaults for ctx.config. Build-time @file sugar is expanded into this object. */
config?: JsonObject;
engines: {
hostApi: string;
logicRuntime: string;
uiSchema?: string;
libraries?: Partial<Record<'vue' | 'react' | 'react-dom', string>>;
};
modules: {
logic?: {
entry: string;
activation?: string[];
};
/** Separate server-side playback resolver. Requires a compatible builder and share Host. */
share?: {
entry: string;
configKeys?: string[];
guestAdapterId?: string;
guestGlobals?: string[];
};
surfaces?: SurfaceDeclaration[];
};
contributes?: {
lyricConverters?: {
id: string;
title: string;
formats: import('./lyrics.js').LyricInputFormat[];
}[];
providers?: ProviderDeclaration[];
/** Home tabs exist only while at least one enabled plugin contributes them. */
homeSections?: HomeSectionContribution[];
/** Native sections inside the Host's existing local/cloud playlist page. */
playlistSections?: {
id: string;
title: string;
view: string;
order?: number;
}[];
/** Controlled UI composition. The Host owns the target DOM and lifecycle. */
uiExtensions?: UIExtensionContribution[];
/** Surface/slot styles are scoped. Application styles require ui.styles.global. */
styles?: StyleContribution[];
menus?: MenuContribution[];
/** Entries for the application's existing playlist import menu/dialog. */
playlistImporters?: {
id: string;
title: string;
providerId?: string;
examples?: {
label: string;
value: string;
}[];
instructions?: string[];
description?: string;
placeholder?: string;
}[];
commands?: {
id: string;
title: string;
description?: string;
action: string;
view?: string;
}[];
/** Subaccounts in the Host account menu. action returns AccountSummary; view owns login UI. */
accountItems?: {
id: string;
title: string;
view: string;
action: string;
logoutAction?: string;
}[];
sidebarItems?: {
id: string;
group: string;
title: string;
view: string;
}[];
settingsPages?: {
id: string;
title: string;
view: string;
}[];
guestAdapters?: {
id: string;
title?: string;
extensions?: string[];
format: string;
badge?: {
label: string;
backgroundColor: string;
textColor: string;
};
compatibilityProfile: string;
bootstrap: string;
runtime: string;
projectableProtocols: string[];
}[];
};
permissions?: PermissionDeclaration[];
guestPolicy?: {
maxDepth: 1;
allowedCapabilities: string[];
networkScopeMode: 'per-guest-user-approved';
allowNativeCode: false;
allowRemoteCodeExecution: false;
};
dataSchemas?: {
config: number;
state: number;
};
}accounts.d.ts
ts
/** Public account presentation. Credentials and platform login flows remain in plugin logic. */
export type AccountSummary = {
signedIn: boolean;
displayName: string;
avatarUrl?: string;
badge?: string;
};
export declare function assertAccountSummary(value: unknown): asserts value is AccountSummary;native-view.d.ts
ts
import type { ContentEntity, JsonValue, MaybePromise, OperationContext } from './index.js';
export interface NativeViewAction {
label: string;
action: string;
input?: JsonValue;
primary?: boolean;
}
export interface NativeViewSection {
id: string;
title?: string;
layout: 'grid' | 'list';
items: ContentEntity[];
/** Receives { ref }; typically opens a native playlist page. */
onOpen?: string;
/** Receives { ref, refs }; typically replaces the native playback queue. */
onPlay?: string;
/** Receives the action input merged with { ref }; the Host supplies the item's ref. */
itemActions?: NativeViewAction[];
}
/** Rendered using Host components. No plugin DOM, iframe, or framework runs in this surface. */
export interface NativeView {
type: 'page';
title?: string;
description?: string;
actions?: NativeViewAction[];
sections: NativeViewSection[];
}
/** A typed render callback that can be passed directly to ctx.actions.register. */
export declare function defineNativeView<TInput extends JsonValue = JsonValue>(render: (input: TInput, operation: OperationContext) => MaybePromise<NativeView>): (input: TInput, operation: OperationContext) => Promise<NativeView & JsonValue>;
export declare function assertNativeView(value: unknown, actions?: ReadonlySet<string>): asserts value is NativeView;宿主服务完整签名
services.d.ts
ts
import type { Disposable, JsonObject, JsonValue, IconRef } from './manifest.js';
import type { ContentEntity, MusicFault, OperationContext, Page, ResourceRef, AssetHandle } from './index.js';
import type { LibraryPlaylist, PlaylistReference } from './library.js';
import type { LyricsDocument } from './music.js';
import type { PermissionGroup } from './permissions.js';
import type { HostNavigationRequest } from './navigation.js';
/** Supplied by Host actions/events. Never construct trusted intents from plugin JSON. */
export interface ServiceCall {
operation: OperationContext;
permissionKey: string;
}
export interface ServiceAvailability {
service: string;
version: string;
available: boolean;
/** When present, only these methods are connected by the Host. */
methods?: string[];
reason?: 'host-not-connected' | 'unsupported' | 'not-logged-in' | 'disabled';
permissionGroups: PermissionGroup[];
}
export interface AccountProfile {
id: string;
displayName: string;
avatar?: AssetHandle;
/** Stable plugin-scoped identity; no email, phone, tokens or auth provider subject by default. */
identityScope: 'plugin';
}
export interface AccountSession {
loggedIn: boolean;
profile: AccountProfile | null;
}
export interface PlayerState {
status: 'idle' | 'loading' | 'playing' | 'paused' | 'ended' | 'error';
track: ContentEntity | null;
positionMs: number;
durationMs: number;
volume: number;
muted: boolean;
repeat: 'off' | 'one' | 'all';
shuffle: boolean;
}
export interface QueueState {
items: ContentEntity[];
currentIndex: number;
revision: string;
}
export interface DownloadTask {
id: string;
track: ContentEntity;
status: 'queued' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled';
receivedBytes: number;
totalBytes?: number;
error?: MusicFault;
}
export interface FileHandle {
kind: 'file';
id: string;
name: string;
size?: number;
mime?: string;
}
export interface DirectoryHandle {
kind: 'directory';
id: string;
name: string;
}
export interface AudioDevice {
id: string;
name: string;
kind: 'local' | 'dlna';
available: boolean;
}
export interface ShareDescriptor {
version: 1;
track: ResourceRef;
title: string;
artists: string[];
canonicalUrl?: string;
/** Public resolver identity; never executable plugin code, master keys or auth tokens. */
resolver?: {
id: string;
resourceId: string;
};
}
export interface RoomState {
joined: boolean;
id?: string;
title?: string;
role?: 'owner' | 'member';
members?: number;
}
export interface AppInfo {
name: string;
version: string;
platform: 'win32' | 'darwin' | 'linux';
locale: string;
theme: 'light' | 'dark';
hostApi: string;
}
export interface HostServiceEvents {
'account.changed': AccountSession;
'library.changed': {
target: PlaylistReference;
reason: 'created' | 'updated' | 'deleted' | 'synced';
};
'player.changed': PlayerState;
'queue.changed': QueueState;
'lyrics.changed': LyricsDocument | null;
'downloads.changed': DownloadTask;
'settings.changed': {
keys: string[];
};
'theme.changed': {
theme: 'light' | 'dark';
};
'rooms.changed': RoomState;
'devices.changed': AudioDevice[];
'permissions.changed': {
keys: string[];
};
}
/** Contract first: availability must be checked before using services not connected by this Host. */
export interface HostServices {
capabilities: {
list(): Promise<ServiceAvailability[]>;
get(service: string): Promise<ServiceAvailability>;
};
account: {
getSession(call: ServiceCall): Promise<AccountSession>;
getProfile(call: ServiceCall): Promise<AccountProfile | null>;
/** Opens the application's own account page. Plugins do not implement login or receive its token. */
openLogin(operation: OperationContext): Promise<void>;
};
app: {
getInfo(): Promise<AppInfo>;
openSettings(section?: string): Promise<void>;
openExternal(url: string, call: ServiceCall): Promise<void>;
};
player: {
getState(call: ServiceCall): Promise<PlayerState>;
play(track: ResourceRef | undefined, call: ServiceCall): Promise<void>;
pause(call: ServiceCall): Promise<void>;
next(call: ServiceCall): Promise<void>;
previous(call: ServiceCall): Promise<void>;
seek(positionMs: number, call: ServiceCall): Promise<void>;
setVolume(volume: number, call: ServiceCall): Promise<void>;
setMode(mode: {
repeat?: PlayerState['repeat'];
shuffle?: boolean;
muted?: boolean;
}, call: ServiceCall): Promise<void>;
};
queue: {
get(call: ServiceCall): Promise<QueueState>;
append(items: ContentEntity[], call: ServiceCall): Promise<QueueState>;
replace(items: ContentEntity[], call: ServiceCall): Promise<QueueState>;
remove(refs: ResourceRef[], call: ServiceCall): Promise<QueueState>;
reorder(refs: ResourceRef[], revision: string, call: ServiceCall): Promise<QueueState>;
};
favorites: {
contains(refs: ResourceRef[], call: ServiceCall): Promise<boolean[]>;
add(refs: ResourceRef[], call: ServiceCall): Promise<void>;
remove(refs: ResourceRef[], call: ServiceCall): Promise<void>;
};
history: {
list(cursor: string | undefined, call: ServiceCall): Promise<Page<ContentEntity>>;
};
downloads: {
list(call: ServiceCall): Promise<DownloadTask[]>;
create(request: {
tracks: ResourceRef[];
quality?: string;
directory?: DirectoryHandle;
}, call: ServiceCall): Promise<DownloadTask[]>;
pause(ids: string[], call: ServiceCall): Promise<void>;
resume(ids: string[], call: ServiceCall): Promise<void>;
cancel(ids: string[], call: ServiceCall): Promise<void>;
retry(ids: string[], call: ServiceCall): Promise<void>;
reveal(id: string, call: ServiceCall): Promise<void>;
};
files: {
pick(request: {
title?: string;
extensions?: string[];
multiple?: boolean;
}, operation: OperationContext): Promise<FileHandle[]>;
pickDirectory(operation: OperationContext): Promise<DirectoryHandle | null>;
readText(file: FileHandle, call: ServiceCall): Promise<string>;
readBase64(file: FileHandle, call: ServiceCall): Promise<string>;
saveText(request: {
suggestedName: string;
text: string;
}, operation: OperationContext): Promise<FileHandle | null>;
writeText(file: FileHandle, text: string, call: ServiceCall): Promise<void>;
};
clipboard: {
readText(call: ServiceCall): Promise<string>;
writeText(text: string, call: ServiceCall): Promise<void>;
};
localMusic: {
list(cursor: string | undefined, call: ServiceCall): Promise<Page<ContentEntity>>;
scan(directories: DirectoryHandle[], call: ServiceCall): Promise<{
taskId: string;
}>;
getTags(track: ResourceRef, call: ServiceCall): Promise<JsonObject>;
writeTags(track: ResourceRef, tags: {
title?: string;
artists?: string[];
album?: string;
lyrics?: LyricsDocument;
}, call: ServiceCall): Promise<void>;
};
settings: {
/** Only explicit public setting names. Secret/unsafe settings are not part of this API. */
get(keys: string[], call: ServiceCall): Promise<JsonObject>;
update(values: JsonObject, call: ServiceCall): Promise<void>;
};
window: {
control(action: 'show' | 'minimize' | 'maximize' | 'restore' | 'mini-player', call: ServiceCall): Promise<void>;
};
hotkeys: {
register(request: {
id: string;
accelerator: string;
commandId: string;
}, call: ServiceCall): Promise<Disposable>;
};
sharing: {
create(request: ShareDescriptor, call: ServiceCall): Promise<{
id: string;
url: string;
expiresAt?: number;
}>;
revoke(id: string, call: ServiceCall): Promise<void>;
resolve(url: string, operation: OperationContext): Promise<ShareDescriptor>;
};
rooms: {
getState(call: ServiceCall): Promise<RoomState>;
join(inviteCode: string, call: ServiceCall): Promise<RoomState>;
leave(call: ServiceCall): Promise<void>;
requestTrack(track: ResourceRef, call: ServiceCall): Promise<void>;
};
devices: {
list(call: ServiceCall): Promise<AudioDevice[]>;
select(id: string, call: ServiceCall): Promise<void>;
};
ai: {
generate(request: {
prompt: string;
maxOutputChars?: number;
}, call: ServiceCall): Promise<{
text: string;
}>;
};
tasks: {
schedule(request: {
id: string;
commandId: string;
intervalMs: number;
}, call: ServiceCall): Promise<{
id: string;
}>;
cancel(id: string, call: ServiceCall): Promise<void>;
};
events: {
on<K extends keyof HostServiceEvents>(event: K, listener: (value: HostServiceEvents[K]) => void, call?: ServiceCall): Disposable;
};
}
export interface HostUI {
dialogs: {
confirm(request: {
title: string;
message: string;
confirmText?: string;
}): Promise<boolean>;
prompt(request: {
title: string;
label: string;
value?: string;
secret?: boolean;
}): Promise<string | null>;
pickPlaylist(request?: {
location?: 'local' | 'cloud';
writable?: boolean;
}): Promise<LibraryPlaylist | null>;
};
navigation: {
open(request: HostNavigationRequest): Promise<void>;
};
notifications: {
show(request: {
title: string;
body: string;
}, call: ServiceCall): Promise<void>;
};
progress: {
create(request: {
title: string;
cancellable?: boolean;
}): Promise<{
id: string;
}>;
update(id: string, request: {
value?: number;
message?: string;
}): Promise<void>;
close(id: string): Promise<void>;
};
}
export interface MenuContribution {
id: string;
slot: 'playlist.import' | 'playlist.actions' | 'track.actions' | 'player.actions' | 'search.tools';
title: string;
description?: string;
commandId: string;
icon?: IconRef;
when?: {
kinds?: ('track' | 'playlist')[];
loggedIn?: boolean;
};
}
/** Explicit allowlist for future Core routing; never forward arbitrary IPC channel names. */
export declare const HOST_SERVICE_METHODS: {
readonly capabilities: readonly ["list", "get"];
readonly account: readonly ["getSession", "getProfile", "openLogin"];
readonly app: readonly ["getInfo", "openSettings", "openExternal"];
readonly player: readonly ["getState", "play", "pause", "next", "previous", "seek", "setVolume", "setMode"];
readonly queue: readonly ["get", "append", "replace", "remove", "reorder"];
readonly favorites: readonly ["contains", "add", "remove"];
readonly history: readonly ["list"];
readonly downloads: readonly ["list", "create", "pause", "resume", "cancel", "retry", "reveal"];
readonly files: readonly ["pick", "pickDirectory", "readText", "readBase64", "saveText", "writeText"];
readonly clipboard: readonly ["readText", "writeText"];
readonly localMusic: readonly ["list", "scan", "getTags", "writeTags"];
readonly settings: readonly ["get", "update"];
readonly window: readonly ["control"];
readonly hotkeys: readonly ["register"];
readonly sharing: readonly ["create", "revoke", "resolve"];
readonly rooms: readonly ["getState", "join", "leave", "requestTrack"];
readonly devices: readonly ["list", "select"];
readonly ai: readonly ["generate"];
readonly tasks: readonly ["schedule", "cancel"];
};
export type SerializedHostValue = JsonValue | AssetHandle;navigation.d.ts
ts
import type { ResourceRef } from './index.js';
import type { PluginManifest } from './manifest.js';
export type HostNavigationRequest = {
query?: string;
ref?: ResourceRef;
} & ({
page: 'playlist';
sectionId?: string;
} | {
page: 'search' | 'charts' | 'downloads' | 'account' | 'settings';
sectionId?: never;
});
/** Validate using the calling plugin's manifest, never a caller-supplied owner ID. */
export declare function assertNavigationRequest(value: unknown, manifest?: PluginManifest): asserts value is HostNavigationRequest;library.d.ts
ts
import type { ContentEntity, OperationContext, Page } from './index.js';
export type PlaylistLocation = 'local' | 'cloud';
export interface PlaylistReference {
/** Host ID; never a path or backend database credential. */
id: string;
location: PlaylistLocation;
}
export interface LibraryPlaylist {
ref: PlaylistReference;
name: string;
description?: string;
trackCount?: number;
writable: boolean;
}
export interface PlaylistImportRequest {
/** Omit to let the Host show its existing playlist picker/create dialog. */
target?: PlaylistReference;
suggestedName?: string;
items: ContentEntity[];
/** Reuse this key when retrying the same batch, so the Host can avoid duplicates. */
requestId: string;
permissionKey: string;
operation: OperationContext;
}
export interface PlaylistImportResult {
cancelled: boolean;
target?: PlaylistReference;
added: number;
skipped: number;
}
export interface PlaylistImporterImplementation {
/** Resolve a pasted link/ID and return one page. Host owns paging and the import UI. */
getTracks(request: {
value: string;
cursor?: string;
limit: number;
}, operation: OperationContext): Promise<PlaylistTrackPage>;
}
export interface PlaylistTrackPage extends Page<ContentEntity> {
name?: string;
playlist?: import('./music.js').PlaylistMetadata;
}
/** Existing application library services. No plugin-owned playlist database. */
export interface LibraryAPI {
playlists: {
list(request: {
location?: PlaylistLocation;
cursor?: string;
permissionKey: string;
operation: OperationContext;
}): Promise<Page<LibraryPlaylist>>;
getTracks(request: {
target: PlaylistReference;
cursor?: string;
permissionKey: string;
operation: OperationContext;
}): Promise<Page<ContentEntity>>;
/** Host handles destination selection, auth, deduplication, persistence and cloud sync. */
import(request: PlaylistImportRequest): Promise<PlaylistImportResult>;
};
}存储类型
storage.d.ts
ts
import type { JsonValue } from './manifest.js';
export type PluginStorageReadKey = string | {
key: string;
pluginId?: string;
};
export type PluginStorageWriteKey = string | {
key: string;
pluginId?: string;
/** Omitted preserves the existing policy; new keys are private. Empty array revokes sharing. */
readableBy?: '*' | string[];
};
/** JSON storage. Structured keys and shared reads require a supporting desktop Host. */
export interface PluginStorageAPI {
/** Missing local keys return null. Unauthorized shared reads reject. */
get<T extends JsonValue = JsonValue>(key: PluginStorageReadKey): Promise<T | null>;
set(key: PluginStorageWriteKey, value: JsonValue): Promise<void>;
delete(key: PluginStorageReadKey): Promise<void>;
}网络与权限类型
http.d.ts
ts
import type { CredentialRef, OperationContext, PluginContext } from './index.js';
export type QueryValue = string | number | boolean | null | undefined;
export interface HttpResponse<T> {
status: number;
headers: Record<string, string>;
data: T;
}
export interface HttpRequestOptions {
operation: OperationContext;
permissionKey?: string;
query?: Record<string, QueryValue | QueryValue[]>;
headers?: Record<string, string>;
method?: string;
json?: unknown;
form?: Record<string, QueryValue>;
body?: string;
credential?: CredentialRef;
timeoutMs?: number;
/** Default: true. Set false to inspect non-2xx platform responses yourself. */
throwHttpErrors?: boolean;
}
export interface HttpClientOptions {
baseURL?: string;
headers?: Record<string, string>;
permissionKey?: string | ((url: URL) => string);
/** Prompt only when a permission is in the prompt state. Never bypass a denial. */
requestPermission?: boolean;
}
export declare class HttpError<T = unknown> extends Error {
readonly response: HttpResponse<T>;
readonly origin: string;
readonly name = "HttpError";
constructor(response: HttpResponse<T>, origin: string);
get status(): number;
}
export declare function networkPermissionKey(origin: string): string;
/** Typed convenience layer over the Host broker. Does not use fetch or grant permissions. */
export declare function createHttpClient(ctx: {
http: Pick<PluginContext['http'], 'request'>;
permissions: Pick<PluginContext['permissions'], 'query' | 'request'>;
}, options: HttpClientOptions): {
request: <T = unknown>(address: string, input: HttpRequestOptions) => Promise<HttpResponse<T>>;
authorize: (key: string, _origin: string, operation: OperationContext) => Promise<void>;
get<T = unknown>(address: string, input: Omit<HttpRequestOptions, "method">): Promise<T>;
post<T = unknown>(address: string, input: Omit<HttpRequestOptions, "method">): Promise<T>;
};sockets.d.ts
ts
import type { Disposable, JsonValue } from './manifest.js';
import type { OperationContext } from './index.js';
export interface SocketConnectOptions {
/** Defaults to network.socket. */
permissionKey?: string;
operation: OperationContext;
/** wss:// for native WebSocket; https:// for Socket.IO. */
url: string;
kind: 'websocket' | 'socket.io';
path?: string;
auth?: Record<string, JsonValue>;
/** Socket.IO reconnects at most five times. Native WebSocket does not auto-reconnect. */
reconnection?: boolean;
}
export interface HostSocket {
readonly id: string;
/** Incoming Socket.IO payloads are arrays of event arguments. WebSocket messages are text. */
on<T extends JsonValue = JsonValue>(event: string, handler: (data: T) => void): Disposable;
emit(event: string, data: JsonValue): Promise<void>;
send(data: JsonValue): Promise<void>;
disconnect(): Promise<void>;
}
export interface SocketAPI {
connect(options: SocketConnectOptions): Promise<HostSocket>;
}permissions.d.ts
ts
import type { JsonObject } from './manifest.js';
import type { PermissionName } from './catalog.js';
import type { PermissionStatus, UserIntentHandle } from './index.js';
/** Permission groups are user-facing decisions, not one prompt per method. */
export declare const PERMISSION_GROUPS: {
readonly network: {
readonly title: "访问公网服务";
readonly permissions: readonly ["network.request", "network.socket"];
};
readonly localNetwork: {
readonly title: "访问局域网与本机服务";
readonly permissions: readonly ["network.private", "network.discovery"];
};
readonly account: {
readonly title: "读取登录状态与基本账号资料";
readonly permissions: readonly ["account.profile"];
};
readonly libraryRead: {
readonly title: "读取歌单与收藏";
readonly permissions: readonly ["library.read"];
};
readonly libraryManage: {
readonly title: "创建和修改歌单与收藏";
readonly permissions: readonly ["library.write"];
};
readonly libraryDelete: {
readonly title: "删除歌单与歌曲记录";
readonly permissions: readonly ["library.delete"];
};
readonly playbackRead: {
readonly title: "读取播放状态和队列";
readonly permissions: readonly ["player.read"];
};
readonly playbackControl: {
readonly title: "控制播放和播放队列";
readonly permissions: readonly ["player.control", "playback.fallback.hold"];
};
readonly downloads: {
readonly title: "管理下载任务";
readonly permissions: readonly ["downloads.create", "downloads.manage"];
};
readonly files: {
readonly title: "使用用户选定的文件";
readonly permissions: readonly ["files.read", "files.write"];
};
readonly localMusic: {
readonly title: "索引本地音乐和编辑标签";
readonly permissions: readonly ["localMusic.read", "localMusic.write"];
};
readonly clipboardRead: {
readonly title: "读取剪贴板";
readonly permissions: readonly ["clipboard.read"];
};
readonly clipboardWrite: {
readonly title: "写入剪贴板";
readonly permissions: readonly ["clipboard.write"];
};
readonly notifications: {
readonly title: "发送系统通知";
readonly permissions: readonly ["notifications.system"];
};
readonly external: {
readonly title: "在系统浏览器打开链接";
readonly permissions: readonly ["external.open"];
};
readonly settingsRead: {
readonly title: "读取软件偏好";
readonly permissions: readonly ["settings.read"];
};
readonly settingsWrite: {
readonly title: "修改软件偏好";
readonly permissions: readonly ["settings.write"];
};
readonly window: {
readonly title: "控制软件窗口";
readonly permissions: readonly ["window.control"];
};
readonly hotkeys: {
readonly title: "注册全局快捷键";
readonly permissions: readonly ["hotkeys.register"];
};
readonly sharing: {
readonly title: "发布和撤销分享";
readonly permissions: readonly ["sharing.publish", "sharing.revoke"];
};
readonly roomsRead: {
readonly title: "读取一起听房间状态";
readonly permissions: readonly ["rooms.read"];
};
readonly roomsControl: {
readonly title: "加入房间和控制一起听";
readonly permissions: readonly ["rooms.control"];
};
readonly ai: {
readonly title: "使用软件 AI 服务";
readonly permissions: readonly ["ai.use"];
};
readonly devices: {
readonly title: "控制音频设备与投放";
readonly permissions: readonly ["devices.control"];
};
readonly uiAppearance: {
readonly title: "修改软件全局外观";
readonly permissions: readonly ["ui.styles.global"];
};
readonly background: {
readonly title: "后台任务";
readonly permissions: readonly ["background.run"];
};
readonly credentials: {
readonly title: "使用本插件已保存的连接凭据";
readonly permissions: readonly ["credentials.use"];
};
readonly credentialExport: {
readonly title: "读取本插件连接凭据明文";
readonly permissions: readonly ["credentials.read"];
};
readonly guestPlugins: {
readonly title: "安装和运行兼容子插件";
readonly permissions: readonly ["guests.manage", "guests.run"];
};
readonly services: {
readonly title: "调用其他插件提供的服务";
readonly permissions: readonly ["services.consume"];
};
};
export type PermissionGroup = keyof typeof PERMISSION_GROUPS;
export declare function permissionGroup(name: PermissionName): PermissionGroup | undefined;
export interface PermissionGrant {
key: string;
name: PermissionName;
group?: PermissionGroup;
scope: JsonObject;
status: PermissionStatus;
expiresAt?: number;
}
export interface PermissionGroupRequest {
group: PermissionGroup;
/** Omit to request all statically scoped declarations in this group. */
keys?: string[];
/** Dynamic scopes keyed by declared permission key. */
scopes?: Record<string, JsonObject>;
intent?: UserIntentHandle;
}
export interface PermissionGroupResult {
group: PermissionGroup;
status: PermissionStatus;
grants: PermissionGrant[];
}歌词与音质类型
music.d.ts
ts
import type { ContentEntity, ResourceRef, ResolveResult } from './index.js';
/** Milliseconds throughout. Platform-specific formats/decryption stay inside the plugin. */
export interface LyricWord {
romanization?: string;
startTimeMs: number;
endTimeMs: number;
text: string;
}
export interface LyricLine {
isBackground?: boolean;
isDuet?: boolean;
startTimeMs: number;
endTimeMs?: number;
text: string;
translation?: string;
romanization?: string;
words?: LyricWord[];
}
export interface CrLyric {
format: 'crlyric';
version: 1;
track: ResourceRef;
offsetMs: number;
lines: LyricLine[];
/** Untimed lyrics only. Empty lines + no plainText means no available lyrics. */
plainText?: string;
}
/** @deprecated Use CrLyric. */
export type LyricsDocument = CrLyric;
export interface TrackMetadata {
artists: string[];
album?: {
id?: string;
title: string;
};
qualities?: string[];
artworkUrl?: string;
durationMs?: number;
}
export interface MusicTrack extends ContentEntity {
ref: ResourceRef & {
kind: 'track';
};
metadata: TrackMetadata;
}
export interface PlaylistMetadata {
trackCount?: number;
description?: string;
author?: string;
artworkUrl?: string;
}
export interface ChartMetadata {
updateFrequency?: string;
artworkUrl?: string;
}
export interface MusicPlaylist extends ContentEntity {
ref: ResourceRef & {
kind: 'playlist';
};
playlist?: PlaylistMetadata;
}
export interface MusicChart extends ContentEntity {
ref: ResourceRef & {
kind: 'chart';
};
chart?: ChartMetadata;
}
export declare function assertResourceRef(value: unknown): asserts value is ResourceRef;
export declare function assertContentPage(value: unknown): void;
export declare function assertResolveResult(value: unknown): asserts value is ResolveResult;
export declare function assertLyricsDocument(value: unknown): asserts value is LyricsDocument;lyrics.d.ts
ts
import type { CrLyric } from './music.js';
import type { ResourceRef, OperationContext } from './index.js';
export type LyricInputFormat = 'auto' | 'lrc' | 'enhanced-lrc' | 'yrc' | 'qrc' | 'krc' | 'ttml' | 'plain';
export type LyricExportFormat = 'lrc' | 'enhanced-lrc' | 'yrc';
export interface LyricParseRequest {
track: ResourceRef;
format: LyricInputFormat;
text: string;
translation?: string;
romanization?: string;
}
export interface LyricExportRequest {
document: CrLyric;
format: LyricExportFormat;
}
export interface LyricExportResult {
format: LyricExportFormat;
text: string;
mime: 'text/plain';
extension: 'lrc' | 'yrc';
}
/** Parsing/decryption/serialization belongs to plugins, not the player or download manager. */
export interface LyricConverter {
parse(request: LyricParseRequest, operation: OperationContext): Promise<CrLyric>;
export(request: LyricExportRequest, operation: OperationContext): Promise<LyricExportResult>;
}quality.d.ts
ts
/** Manifest order is authoritative: first is lowest, last is highest. */
export declare function compareQualities(order: readonly string[], a: string, b: string): -1 | 0 | 1 | undefined;
export declare function selectQuality(order: readonly string[], available?: readonly string[], requested?: string): string | undefined;Guest 与分享类型
guests.d.ts
ts
import type { Disposable, JsonValue } from './manifest.js';
import type { OperationContext } from './index.js';
export interface GuestProvider {
id: string;
name: string;
/** Lowest to highest, as declared by the imported script. */
qualities: string[];
protocols: string[];
}
export interface GuestInfo {
id: string;
adapterId: string;
name: string;
version: string;
author?: string;
state: 'ready' | 'stopped' | 'error';
selected: boolean;
providers: GuestProvider[];
error?: string;
}
/** Requires a Host with Guest management support; the SDK does not install Guest runtimes. */
export interface GuestAPI {
list(): Promise<GuestInfo[]>;
/** The Host owns file selection and approval. Cancellation returns null. */
import(adapterId: string): Promise<GuestInfo | null>;
select(guestId: string | null): Promise<void>;
remove(guestId: string): Promise<void>;
invoke(guestId: string, method: string, input: JsonValue, operation: OperationContext): Promise<JsonValue>;
}
export interface GuestBootstrapAPI {
readonly scriptInfo: Readonly<{
name: string;
version: string;
author?: string;
description?: string;
homepage?: string;
rawScript: string;
}>;
ready(metadata: {
providers: GuestProvider[];
}): void;
handle(handler: (method: string, input: JsonValue, operation: OperationContext) => Promise<JsonValue>): Disposable;
invokeHost(method: string, input: JsonValue): Promise<JsonValue>;
}share.d.ts
ts
import type { JsonObject, MaybePromise } from './manifest.js';
export interface ShareMusicInfo {
songmid?: string | number;
hash?: string;
id?: string | number;
[key: string]: unknown;
}
/** Context supplied by the separate share-resolver Host, not the desktop PluginContext. */
export interface ShareResolverContext<Config = JsonObject> {
config: Readonly<Config>;
plugin: {
name: string;
version: string;
author?: string;
};
sources: Record<string, {
name: string;
qualitys: string[];
}>;
request<T = unknown>(url: string, options?: {
method?: string;
headers?: Record<string, string>;
body?: string;
timeout?: number;
}): Promise<{
body: T;
statusCode: number;
headers: Record<string, string>;
}>;
/** Only available when the manifest declares a selected Guest adapter. */
guest?: {
name: string;
version: string;
author?: string;
rawScript: string;
};
runGuest?(bindings: Record<string, unknown>): void;
utils: {
buffer: {
from(data: unknown, encoding?: 'base64' | 'hex' | 'utf8'): Uint8Array;
bufToString(data: Uint8Array, encoding?: 'base64' | 'hex' | 'utf8'): string;
};
crypto: {
md5(value: string): string;
randomBytes(size: number): Uint8Array;
aesEncrypt(data: unknown, mode: string, key: unknown, iv?: unknown): Uint8Array;
rsaEncrypt(data: string, key: string): string;
};
};
}
export interface ShareResolver {
musicUrl(source: string, musicInfo: ShareMusicInfo, quality: string): Promise<string>;
}
export type ShareResolverEntry<Config = JsonObject> = (context: ShareResolverContext<Config>) => MaybePromise<ShareResolver>;资源目录
catalog.d.ts
ts
/** Stable names for Host-provided resources. Platform names identify icons, not mandatory providers. */
export declare const HOST_ICON_NAMES: readonly ["platform.tx", "platform.kg", "platform.kw", "platform.wy", "platform.mg", "platform.local", "platform.all", "search", "music-note", "library", "cloud", "server", "import", "chart", "settings", "play", "pause", "download", "share", "account", "heart", "refresh", "close", "add", "info"];
export type HostIconName = (typeof HOST_ICON_NAMES)[number];
export declare const HOST_ASSET_NAMES: readonly ["app.logo", "placeholder.cover", "placeholder.avatar"];
export type HostAssetName = (typeof HOST_ASSET_NAMES)[number];
export declare const PERMISSION_NAMES: readonly ["account.profile", "downloads.manage", "localMusic.read", "localMusic.write", "settings.read", "settings.write", "window.control", "hotkeys.register", "rooms.read", "rooms.control", "ai.use", "devices.control", "ui.styles.global", "network.request", "network.socket", "network.private", "network.discovery", "credentials.use", "credentials.read", "library.read", "library.write", "library.delete", "player.read", "player.control", "playback.fallback.hold", "files.read", "files.write", "downloads.create", "clipboard.read", "clipboard.write", "notifications.system", "external.open", "background.run", "services.consume", "sharing.publish", "sharing.revoke", "guests.manage", "guests.run"];
export type PermissionName = (typeof PERMISSION_NAMES)[number];
/** Runtime modules available to hand-written plugins without bundling. */
export declare const HOST_MODULE_NAMES: readonly ["ceru", "@ceru/http", "@ceru/ui", "@ceru/socket", "@ceru/library", "@ceru/account", "@ceru/player", "@ceru/tools", "@ceru/crypto", "@ceru/compression", "@ceru/encoding", "@ceru/legacy-http", "lodash"];
export type HostModuleName = (typeof HOST_MODULE_NAMES)[number];
export declare const LODASH_METHODS: readonly ["chunk", "compact", "concat", "difference", "drop", "dropRight", "flatten", "flattenDeep", "head", "last", "intersection", "uniq", "uniqBy", "union", "zip", "take", "takeRight", "groupBy", "keyBy", "countBy", "orderBy", "sortBy", "partition", "shuffle", "sample", "map", "filter", "find", "some", "every", "reduce", "includes", "get", "has", "pick", "omit", "mapKeys", "mapValues", "cloneDeep", "isEqual", "isEmpty", "isNil", "isString", "isNumber", "isArray", "isPlainObject", "camelCase", "kebabCase", "snakeCase", "startCase", "capitalize", "escape", "unescape", "trim", "truncate", "debounce", "throttle", "once", "memoize", "clamp", "range", "sum", "sumBy", "round"];
/** 引用宿主图标;例如 hostIcon('platform.tx'),不内嵌图片。 */
export declare function hostIcon(name: HostIconName): {
kind: 'host';
name: HostIconName;
};工具与模块类型
modules.d.ts
ts
import type { PluginContext, HostLodash } from './index.js';
export interface BuiltinModules {
ceru: PluginContext;
'@ceru/http': PluginContext['http'];
'@ceru/ui': PluginContext['ui'];
'@ceru/socket': PluginContext['sockets'];
'@ceru/library': PluginContext['library'];
'@ceru/account': PluginContext['account'];
'@ceru/player': PluginContext['player'];
'@ceru/tools': PluginContext['utils'];
lodash: HostLodash;
'@ceru/crypto': typeof import('./compat/crypto.js');
'@ceru/compression': typeof import('./compat/zlib.js');
'@ceru/encoding': typeof import('./compat/encoding.js');
'@ceru/legacy-http': typeof import('./legacy-http.js');
}
export interface PluginModules {
require<K extends keyof BuiltinModules>(name: K): BuiltinModules[K];
}host-library.d.ts
ts
import type { LibraryAPI, LibraryPlaylist, PlaylistImportRequest, PlaylistLocation, PlaylistReference } from './library.js';
import type { ContentEntity, OperationContext, Page } from './index.js';
/** Bind these functions to existing app services. No database, account or UI is created here. */
export interface HostLibraryServices {
authorize(permissionKey: string, capability: 'library.read' | 'library.write', operation: OperationContext, target?: PlaylistReference): Promise<void>;
chooseTarget(suggestedName: string | undefined, operation: OperationContext): Promise<PlaylistReference | null>;
list(location: PlaylistLocation | undefined, cursor: string | undefined, operation: OperationContext): Promise<Page<LibraryPlaylist>>;
getTracks(target: PlaylistReference, cursor: string | undefined, operation: OperationContext): Promise<Page<ContentEntity>>;
/** Existing local/cloud service must deduplicate requestId per plugin/user/target. */
append(input: PlaylistImportRequest & {
target: PlaylistReference;
}): Promise<{
added: number;
skipped: number;
}>;
/** Notify the existing store/event bus only after persistence succeeds. */
changed(target: PlaylistReference): void;
}
/** Core-side validation/delegation. Pass only a Host-authenticated OperationContext. */
export declare function createHostLibraryBridge(services: HostLibraryServices): LibraryAPI;host-modules.d.cts
ts
declare module 'ceru' {
const core: import('./index.js').PluginContext
export = core
}
declare module '@ceru/http' {
const http: import('./index.js').PluginContext['http']
export = http
}
declare module '@ceru/ui' {
const ui: import('./index.js').PluginContext['ui']
export = ui
}
declare module '@ceru/socket' {
const sockets: import('./index.js').PluginContext['sockets']
export = sockets
}
declare module '@ceru/library' {
const library: import('./index.js').PluginContext['library']
export = library
}
declare module '@ceru/account' {
const account: import('./index.js').PluginContext['account']
export = account
}
declare module '@ceru/player' {
const player: import('./index.js').PluginContext['player']
export = player
}
declare module '@ceru/tools' {
const tools: import('./index.js').PluginContext['utils']
export = tools
}
declare module '@ceru/crypto' {
const crypto: typeof import('./compat/crypto.js')
export = crypto
}
declare module '@ceru/compression' {
const compression: typeof import('./compat/zlib.js')
export = compression
}
declare module '@ceru/encoding' {
const encoding: typeof import('./compat/encoding.js')
export = encoding
}
declare module '@ceru/legacy-http' {
export const createLegacyHttpBridge: typeof import('./legacy-http.js').createLegacyHttpBridge
}legacy-http.d.ts
ts
import type { OperationContext, PluginContext } from './index.js';
export interface LegacyOptions {
method?: string;
headers?: Record<string, unknown>;
form?: Record<string, unknown>;
body?: unknown;
[key: string]: unknown;
}
export declare function createLegacyHttpBridge(host: PluginContext): {
httpFetch: (address: string, options?: LegacyOptions) => {
promise: Promise<{
body: any;
statusCode: number;
headers: {
location?: string | undefined;
};
raw: import("./manifest.js").JsonValue;
url: string;
}>;
cancelHttp(): void;
};
withOperation: <T>(operation: OperationContext, work: () => Promise<T>) => Promise<T>;
ensurePermission: (key: string, _origin: string, operation: OperationContext) => Promise<void>;
permissionKey: () => string;
};compat/crypto.d.ts
ts
export function createHash(name: any): {
update(value: any): /*elided*/ any;
/** @returns {string | Buffer} */
digest(encoding: any): string | Buffer;
};
/** @returns {Buffer} */
export function publicEncrypt(options: any, value: any): Buffer;
export function createCipheriv(mode: any, key: any, iv: any): {
/** @returns {Buffer} */
update(value: any): Buffer;
/** @returns {Buffer} */
final(): Buffer;
};
export function createDecipheriv(mode: any, key: any, iv: any): {
/** @returns {Buffer} */
update(value: any): Buffer;
/** @returns {Buffer} */
final(): Buffer;
};
export function randomBytes(size: any): Buffer;
export function randomUUID(): `${string}-${string}-${string}-${string}-${string}`;
export namespace constants {
let RSA_NO_PADDING: number;
}
declare namespace _default {
export { createHash };
export { createCipheriv };
export { createDecipheriv };
export { publicEncrypt };
export { randomBytes };
export { randomUUID };
export { constants };
}
export default _default;
import { Buffer } from 'buffer';compat/encoding.d.ts
ts
declare namespace _default {
function decode(bytes: any, encoding: any): string;
}
export default _default;compat/zlib.d.ts
ts
export function inflate(input: any, callback: any): void;
export function unzipSync(input: any): Buffer;
declare namespace _default {
export { unzipSync };
export { inflate };
}
export default _default;
import { Buffer } from 'buffer';compat/format.d.ts
ts
export function decodeName(value: any): string;
export function formatPlayTime(seconds: any): string;
export function sizeFormate(size: any): string;
export function formatPlayCount(value: any): string;
export function dateFormat(value: any): string;
export function dateFormat2(value: any): string;
export function formatNumberToChineseSimple(value: any): string;
export function formatMinutesFlexible(seconds: any): string;发行库类型
用于作者后端和构建工具,不是插件沙箱服务。
@shiqianjiang/ceru-plugin-issuer
ts
import { type Writable } from 'node:stream';
import type { JsonObject, JsonValue, PluginManifest } from '@shiqianjiang/ceru-plugin-sdk/manifest';
export declare const FORMAT_VERSION = 2;
export declare const LIMITS: {
file: number;
header: number;
delivery: number;
resources: number;
nodes: number;
};
export type Resource = {
type: 'json';
value: JsonValue;
} | {
type: 'text';
value: string;
mime?: string;
} | {
type: 'base64';
value: string;
mime: string;
};
export interface Proof {
mode: 'single@1' | 'template@1' | 'delivery@1';
algorithm: 'ed25519';
keyId: string;
publicKey: string;
value: string;
}
export interface TemplateDeclaration {
codeDigest: string;
personalizationSchema: JsonObject;
issuerKeys: string[];
}
export interface Delivery {
payload: JsonObject;
signature: Proof;
}
export interface ArtifactHeader {
formatVersion: 2;
syntax: 'js';
manifest: PluginManifest;
signature: Proof | null;
template?: TemplateDeclaration;
delivery?: Delivery;
}
export interface Artifact {
header: ArtifactHeader;
body: string;
modules: Record<string, string>;
resources: Record<string, Resource>;
codeDigest: string;
templateDigest?: string;
signatureStatus: 'unsigned' | 'verified-untrusted' | 'verified-trusted';
sourceFormat: 'exports-v2' | 'ceru-plugin-define-v2';
migrationWarnings: string[];
}
export interface ValidationOptions {
trustedPublicKeys?: string[];
now?: Date;
requireUnexpiredActivation?: boolean;
}
export declare function canonical(value: unknown): string;
export declare function parseJsonStrict(text: string): any;
export declare const MANIFEST_SCHEMA: {
type: string;
properties: Record<string, unknown>;
required: string[];
additionalProperties: boolean;
};
export declare function validateManifest(value: unknown): asserts value is PluginManifest;
export declare function digest(input: string | Uint8Array): string;
export declare function generateSigningKeys(): {
publicKey: string;
privateKey: string;
};
export declare function encodeHeader(header: ArtifactHeader): Buffer;
export declare function encodeArtifact(header: ArtifactHeader, body: string): Buffer;
/** Resolves signed delivery configuration over the defaults embedded at build time. */
export declare function resolveArtifactConfig(header: ArtifactHeader): JsonObject;
/** Returns customer-facing metadata without changing plugin identity or permissions. */
export declare function resolveArtifactDisplay(header: ArtifactHeader): {
name: string;
description?: string;
author?: string;
};
export declare const DEFAULT_PERSONALIZATION_SCHEMA: JsonObject;
/** Creates the default delivery policy from a plugin's build-time config shape. */
export declare function createPersonalizationSchema(config?: JsonObject): JsonObject;
export declare function readArtifact(input: Uint8Array | string, options?: ValidationOptions): Artifact;
export declare function signArtifact(input: Uint8Array, privateKey: string): Buffer;
export declare function createTemplate(input: Uint8Array, options: {
privateKey: string;
issuerPublicKeys: string[];
personalizationSchema?: JsonObject;
}): Buffer;
/** Prepare once per core/key; issue() copies a file, writeTo() streams the shared core without rehashing it. */
export declare class PreparedIssuer {
#private;
readonly templateDigest: string;
constructor(input: Uint8Array, options: {
issuerPrivateKey: string;
trustedPublicKeys?: string[];
});
issue(personalization: JsonObject, options?: {
deliveryId?: string;
now?: Date;
}): Buffer;
writeTo(destination: Writable, personalization: JsonObject, options?: {
deliveryId?: string;
now?: Date;
}): Promise<void>;
}