@nuclearplayer/plugin-sdk 0.0.9

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.
package/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # Nuclear Plugin SDK
2
+
3
+ Official toolkit for building Nuclear plugins.
4
+
5
+ ## 1. What Is A Nuclear Plugin?
6
+ A small JavaScript/TypeScript bundle that exports lifecycle hooks and ships with a `package.json` describing metadata (display name, icon, permissions, etc.). The app reads the manifest for metadata, then loads and executes your exported hooks in-process.
7
+
8
+ ## 2. Quick Start
9
+ ```bash
10
+ mkdir my-plugin && cd my-plugin
11
+ pnpm init -y
12
+ pnpm add @nuclearplayer/plugin-sdk
13
+ # add dev tooling of your choice (vite, tsup, esbuild, rollup)
14
+ ```
15
+
16
+ Create `src/index.ts`:
17
+ ```ts
18
+ import { NuclearPluginAPI } from '@nuclearplayer/plugin-sdk';
19
+
20
+ export default {
21
+ async onLoad(api: NuclearPluginAPI) {
22
+ },
23
+ async onEnable(api: NuclearPluginAPI) {
24
+ },
25
+ async onDisable() {
26
+ },
27
+ async onUnload() {
28
+ },
29
+ };
30
+ ```
31
+
32
+ Bundle to `dist/index.js` (or set a custom `main`). Ensure the output is a CommonJS style bundle (assigns to `module.exports` or `exports.default`).
33
+
34
+ ## 3. Manifest (package.json) Specification
35
+ Required top-level fields:
36
+ - `name`: Unique plugin id (used internally). Scoped names allowed.
37
+ - `version`: Semver string.
38
+ - `description`: One line summary shown to users.
39
+ - `author`: Plain string.
40
+
41
+ Optional standard fields:
42
+ - `main`: Entry file path relative to package root. If omitted the loader tries `index.js` then `dist/index.js`.
43
+
44
+ Optional Nuclear namespace (`nuclear`):
45
+ - `displayName`: Friendly name (falls back to `name`).
46
+ - `category`: Arbitrary grouping (examples: `source`, `integration`, `lyrics`, `utility`).
47
+ - `icon`: See Icon spec below.
48
+ - `permissions`: String array of declared capabilities (informational only right now).
49
+
50
+ Example:
51
+ ```json
52
+ {
53
+ "name": "@nuclear-plugin/lastfm",
54
+ "version": "0.1.0",
55
+ "description": "Scrobble tracks to Last.fm",
56
+ "author": "Nuclear Team",
57
+ "main": "dist/index.js",
58
+ "nuclear": {
59
+ "displayName": "Last.fm Scrobbler",
60
+ "category": "integration",
61
+ "icon": { "type": "link", "link": "https://example.com/icon.png" },
62
+ "permissions": ["scrobble", "network"]
63
+ }
64
+ }
65
+ ```
66
+
67
+ ## 4. Icon Specification
68
+ ```ts
69
+ type PluginIcon = { type: 'link'; link: string };
70
+ ```
71
+ Link icons should point to a local file path or remote URL; keep them small (<= 64x64, optimized).
72
+
73
+ ## 5. Lifecycle Hooks
74
+ All hooks are optional. Export a default object containing any of:
75
+ - `onLoad(api)`: Runs after the plugin code is first evaluated and manifest metadata processed.
76
+ - `onEnable(api)`: Runs when user enables the plugin (may happen multiple times across sessions).
77
+ - `onDisable()`: Runs when disabled.
78
+ - `onUnload()`: Runs before the plugin is fully removed from memory.
79
+
80
+ Typical pattern:
81
+ ```ts
82
+ export default {
83
+ async onLoad(api) {
84
+ },
85
+ async onEnable(api) {
86
+ },
87
+ async onDisable() {
88
+ },
89
+ async onUnload() {
90
+ },
91
+ };
92
+ ```
93
+
94
+ ## 6. Permissions
95
+ `permissions` is currently informational. Declare high-level capabilities your plugin intends to use (network, scrobble, playback-control, lyrics, search, storage, etc.). Future versions may expose UI around this.
96
+
97
+ ## 7. File Structure Example
98
+ ```text
99
+ my-plugin/
100
+ package.json
101
+ README.md
102
+ src/
103
+ index.ts
104
+ dist/
105
+ index.js (built output)
106
+ node_modules/
107
+ ```
108
+
109
+ ## 8. Building Your Plugin
110
+ You can use any bundler that outputs a single JS file that the loader can evaluate in a CommonJS style environment.
111
+
112
+ Example minimal `tsup` config (optional):
113
+ ```jsonc
114
+ // package.json excerpt
115
+ "devDependencies": { "tsup": "^8" },
116
+ "scripts": { "build": "tsup src/index.ts --dts --format cjs --minify --out-dir dist" }
117
+ ```
118
+ Run `pnpm build` to produce `dist/index.js`.
119
+
120
+ Ensure the final bundle sets `module.exports = { ... }` or `exports.default = { ... }`. Default ESM output alone will not be picked up unless your bundler transpiles it to a CommonJS wrapper.
121
+
122
+ ## 9. Local Development Workflow
123
+ 1. Create your plugin folder somewhere accessible.
124
+ 2. Build to produce entry file.
125
+ 3. (Future) Place or symlink the folder into the Nuclear plugins directory once auto-discovery is implemented. For now loading is manual (loader API expects a path).
126
+ 4. Rebuild after changes; the app will need a reload or unload+load cycle when hot-reload support is added.
127
+
128
+ ## 10. Best Practices
129
+ - Keep startup fast; defer heavy work until `onEnable`.
130
+ - Avoid global state leakage; store state on a module-local object.
131
+ - Validate network responses defensively.
132
+ - Use permissions array to communicate scope clearly.
133
+ - Keep dependencies minimal; smaller bundles load faster.
134
+
135
+ ## 11. Troubleshooting
136
+ | Issue | Check |
137
+ |-------|-------|
138
+ | Loader cannot resolve entry | Is `main` correct or is there a built `index.js` / `dist/index.js`? |
139
+ | Missing fields error | Confirm all required manifest fields: name, version, description, author. |
140
+ | Hooks not firing | Ensure default export is an object, not a function or class. |
141
+
142
+ ## 12. Type Exports
143
+ ```ts
144
+ import type { NuclearPlugin, PluginManifest, PluginIcon } from '@nuclearplayer/plugin-sdk';
145
+ ```
146
+
147
+ ## 13. Example Complete Minimal Plugin (TypeScript)
148
+ ```ts
149
+ import { NuclearPluginAPI } from '@nuclearplayer/plugin-sdk';
150
+
151
+ export default {
152
+ async onLoad(api: NuclearPluginAPI) {
153
+ },
154
+ async onEnable(api: NuclearPluginAPI) {
155
+ },
156
+ async onDisable() {
157
+ },
158
+ async onUnload() {
159
+ },
160
+ };
161
+ ```
162
+
163
+ ## 14. License
164
+ AGPL-3.0-only
@@ -0,0 +1,15 @@
1
+ import { ProvidersHost } from '../types/providers';
2
+ import { SettingsHost } from '../types/settings';
3
+ import { Providers } from './providers';
4
+ import { Settings } from './settings';
5
+ export declare class NuclearAPI {
6
+ readonly Settings: Settings;
7
+ readonly Providers: Providers;
8
+ constructor(opts?: {
9
+ settingsHost?: SettingsHost;
10
+ providersHost?: ProvidersHost;
11
+ });
12
+ }
13
+ export declare class NuclearPluginAPI extends NuclearAPI {
14
+ }
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,qBAAa,UAAU;IACrB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;gBAElB,IAAI,CAAC,EAAE;QACjB,YAAY,CAAC,EAAE,YAAY,CAAC;QAC5B,aAAa,CAAC,EAAE,aAAa,CAAC;KAC/B;CAIF;AAED,qBAAa,gBAAiB,SAAQ,UAAU;CAAG"}
@@ -0,0 +1,11 @@
1
+ import { ProvidersHost } from '../types/providers';
2
+ import { ProviderDescriptor, ProviderKind } from '../types/search';
3
+ export declare class Providers {
4
+ #private;
5
+ constructor(host?: ProvidersHost);
6
+ register<T extends ProviderDescriptor>(p: T): string;
7
+ unregister(id: string): boolean;
8
+ list<K extends ProviderKind = ProviderKind>(kind?: K): ProviderDescriptor<K>[];
9
+ get<T extends ProviderDescriptor>(id: string): T | undefined;
10
+ }
11
+ //# sourceMappingURL=providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/api/providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAExE,qBAAa,SAAS;;gBAGR,IAAI,CAAC,EAAE,aAAa;IAYhC,QAAQ,CAAC,CAAC,SAAS,kBAAkB,EAAE,CAAC,EAAE,CAAC;IAI3C,UAAU,CAAC,EAAE,EAAE,MAAM;IAIrB,IAAI,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,IAAI,CAAC,EAAE,CAAC;IAIpD,GAAG,CAAC,CAAC,SAAS,kBAAkB,EAAE,EAAE,EAAE,MAAM;CAG7C"}
@@ -0,0 +1,10 @@
1
+ import { SettingDefinition, SettingsHost, SettingSource, SettingValue } from '../types/settings';
2
+ export declare class Settings {
3
+ #private;
4
+ constructor(host?: SettingsHost);
5
+ register(defs: SettingDefinition[], source: SettingSource): Promise<import('..').SettingsRegistrationResult>;
6
+ get<T extends SettingValue = SettingValue>(id: string): Promise<T | undefined>;
7
+ set<T extends SettingValue = SettingValue>(id: string, value: T): Promise<void>;
8
+ subscribe<T extends SettingValue = SettingValue>(id: string, listener: (value: T | undefined) => void): () => void;
9
+ }
10
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/api/settings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,YAAY,EACb,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,QAAQ;;gBAGP,IAAI,CAAC,EAAE,YAAY;IAY/B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,EAAE,EAAE,MAAM,EAAE,aAAa;IAIzD,GAAG,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAAE,EAAE,MAAM;IAIrD,GAAG,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAI/D,SAAS,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAC7C,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,KAAK,IAAI;CAI3C"}
@@ -0,0 +1,7 @@
1
+ export { NuclearPluginAPI, NuclearAPI } from './api';
2
+ export * from './types';
3
+ export * from './types/settings';
4
+ export * from './types/search';
5
+ export type { ProvidersHost } from './types/providers';
6
+ export { useSetting } from './react/useSetting';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACrD,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,92 @@
1
+ import { useState as a, useEffect as l, useMemo as g } from "react";
2
+ class h {
3
+ #r;
4
+ constructor(t) {
5
+ this.#r = t;
6
+ }
7
+ #t(t) {
8
+ const r = this.#r;
9
+ if (!r)
10
+ throw new Error("Providers host not available");
11
+ return t(r);
12
+ }
13
+ register(t) {
14
+ return this.#t((r) => r.register(t));
15
+ }
16
+ unregister(t) {
17
+ return this.#t((r) => r.unregister(t));
18
+ }
19
+ list(t) {
20
+ return this.#t((r) => r.list(t));
21
+ }
22
+ get(t) {
23
+ return this.#t((r) => r.get(t));
24
+ }
25
+ }
26
+ class b {
27
+ #r;
28
+ constructor(t) {
29
+ this.#r = t;
30
+ }
31
+ #t(t) {
32
+ const r = this.#r;
33
+ if (!r)
34
+ throw new Error("Settings host not available");
35
+ return t(r);
36
+ }
37
+ register(t, r) {
38
+ return this.#t((s) => s.register(t, r));
39
+ }
40
+ get(t) {
41
+ return this.#t((r) => r.get(t));
42
+ }
43
+ set(t, r) {
44
+ return this.#t((s) => s.set(t, r));
45
+ }
46
+ subscribe(t, r) {
47
+ return this.#t((s) => s.subscribe(t, r));
48
+ }
49
+ }
50
+ class f {
51
+ Settings;
52
+ Providers;
53
+ constructor(t) {
54
+ this.Settings = new b(t?.settingsHost), this.Providers = new h(t?.providersHost);
55
+ }
56
+ }
57
+ class d extends f {
58
+ }
59
+ class w extends Error {
60
+ constructor(t) {
61
+ super(`Missing capability: ${t}`), this.name = "MissingCapabilityError";
62
+ }
63
+ }
64
+ const p = (e, t) => {
65
+ const [r, s] = a(void 0);
66
+ l(() => {
67
+ if (!e)
68
+ return;
69
+ let i = !0, u = !1;
70
+ const o = e.subscribe(t, (n) => {
71
+ i && (u = !0, s(n));
72
+ });
73
+ return e.get(t).then((n) => {
74
+ i && (u || s(n));
75
+ }), () => {
76
+ i = !1, o && o();
77
+ };
78
+ }, [t, e]);
79
+ const c = g(
80
+ () => (i) => {
81
+ e && e.set(t, i);
82
+ },
83
+ [t, e]
84
+ );
85
+ return [r, c];
86
+ };
87
+ export {
88
+ w as MissingCapabilityError,
89
+ f as NuclearAPI,
90
+ d as NuclearPluginAPI,
91
+ p as useSetting
92
+ };
@@ -0,0 +1,3 @@
1
+ import { SettingsHost, SettingValue } from '../types/settings';
2
+ export declare const useSetting: <T extends SettingValue = SettingValue>(host: SettingsHost | undefined, id: string) => readonly [T | undefined, (nextValue: T) => void];
3
+ //# sourceMappingURL=useSetting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSetting.d.ts","sourceRoot":"","sources":["../../src/react/useSetting.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEpE,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,YAAY,GAAG,YAAY,EAC9D,MAAM,YAAY,GAAG,SAAS,EAC9B,IAAI,MAAM,0CAkCU,CAAC,UAUtB,CAAC"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/test/setup.ts"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { SettingDefinition, SettingsHost, SettingSource, SettingValue } from '../../types/settings';
2
+ export declare class InMemorySettingsHost implements SettingsHost {
3
+ private definitions;
4
+ private values;
5
+ private listeners;
6
+ private readonly source;
7
+ constructor(source: SettingSource);
8
+ private fullyQualified;
9
+ register(definitions: SettingDefinition[], _source: SettingSource): Promise<{
10
+ registered: string[];
11
+ }>;
12
+ get<T extends SettingValue = SettingValue>(id: string): Promise<T | undefined>;
13
+ set<T extends SettingValue = SettingValue>(id: string, value: T): Promise<void>;
14
+ subscribe<T extends SettingValue = SettingValue>(id: string, listener: (value: T | undefined) => void): () => void;
15
+ }
16
+ //# sourceMappingURL=inMemorySettingsHost.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inMemorySettingsHost.d.ts","sourceRoot":"","sources":["../../../src/test/utils/inMemorySettingsHost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,YAAY,EACb,MAAM,sBAAsB,CAAC;AAE9B,qBAAa,oBAAqB,YAAW,YAAY;IACvD,OAAO,CAAC,WAAW,CAAwC;IAC3D,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,SAAS,CAGb;IACJ,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;gBAE3B,MAAM,EAAE,aAAa;IAIjC,OAAO,CAAC,cAAc;IAOhB,QAAQ,CAAC,WAAW,EAAE,iBAAiB,EAAE,EAAE,OAAO,EAAE,aAAa;;;IAsBjE,GAAG,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAAE,EAAE,MAAM;IAKrD,GAAG,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAWrE,SAAS,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAC7C,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,KAAK,IAAI;CAmB3C"}
@@ -0,0 +1,9 @@
1
+ import { ProviderDescriptor, ProviderKind } from './search';
2
+ export type ProvidersHost = {
3
+ register<T extends ProviderDescriptor>(provider: T): string;
4
+ unregister(providerId: string): boolean;
5
+ list<K extends ProviderKind = ProviderKind>(kind?: K): ProviderDescriptor<K>[];
6
+ get<T extends ProviderDescriptor>(providerId: string): T | undefined;
7
+ clear(): void;
8
+ };
9
+ //# sourceMappingURL=providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/types/providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEjE,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,CAAC,SAAS,kBAAkB,EAAE,QAAQ,EAAE,CAAC,GAAG,MAAM,CAAC;IAC5D,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;IACxC,IAAI,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EACxC,IAAI,CAAC,EAAE,CAAC,GACP,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3B,GAAG,CAAC,CAAC,SAAS,kBAAkB,EAAE,UAAU,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;IACrE,KAAK,IAAI,IAAI,CAAC;CACf,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { Album, AlbumRef, Artist, ArtistRef, PlaylistRef, Track, TrackRef } from '@nuclearplayer/model';
2
+ export type SearchCategory = 'artists' | 'albums' | 'tracks' | 'playlists';
3
+ export type SearchCapability = SearchCategory | 'unified';
4
+ export type ArtistMetadataCapability = 'artistDetails' | 'artistAlbums' | 'artistTopTracks' | 'artistRelatedArtists';
5
+ export type SearchParams = {
6
+ query: string;
7
+ types?: SearchCategory[];
8
+ limit?: number;
9
+ };
10
+ export type SearchResults = {
11
+ artists?: ArtistRef[];
12
+ albums?: AlbumRef[];
13
+ tracks?: Track[];
14
+ playlists?: PlaylistRef[];
15
+ };
16
+ export type ProviderKind = 'metadata' | 'streaming' | 'lyrics' | (string & {});
17
+ export type ProviderDescriptor<K extends ProviderKind = ProviderKind> = {
18
+ id: string;
19
+ kind: K;
20
+ name: string;
21
+ pluginId?: string;
22
+ };
23
+ export type MetadataProvider = ProviderDescriptor<'metadata'> & {
24
+ searchCapabilities?: SearchCapability[];
25
+ artistMetadataCapabilities?: ArtistMetadataCapability[];
26
+ search?: (params: SearchParams) => Promise<SearchResults>;
27
+ searchArtists?: (params: Omit<SearchParams, 'types'>) => Promise<ArtistRef[]>;
28
+ searchAlbums?: (params: Omit<SearchParams, 'types'>) => Promise<AlbumRef[]>;
29
+ searchTracks?: (params: Omit<SearchParams, 'types'>) => Promise<Track[]>;
30
+ searchPlaylists?: (params: Omit<SearchParams, 'types'>) => Promise<PlaylistRef[]>;
31
+ fetchArtistDetails?: (query: string) => Promise<Artist>;
32
+ fetchAlbumDetails?: (query: string) => Promise<Album>;
33
+ fetchArtistAlbums?: (artistId: string) => Promise<AlbumRef[]>;
34
+ fetchArtistTopTracks?: (artistId: string) => Promise<TrackRef[]>;
35
+ fetchArtistRelatedArtists?: (artistId: string) => Promise<ArtistRef[]>;
36
+ };
37
+ export declare class MissingCapabilityError extends Error {
38
+ constructor(capability: string);
39
+ }
40
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/types/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,QAAQ,EACR,MAAM,EACN,SAAS,EACT,WAAW,EACX,KAAK,EACL,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAC3E,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,SAAS,CAAC;AAE1D,MAAM,MAAM,wBAAwB,GAChC,eAAe,GACf,cAAc,GACd,iBAAiB,GACjB,sBAAsB,CAAC;AAE3B,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,WAAW,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAE/E,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,IAAI;IACtE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,CAAC,CAAC;IACR,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,CAAC,UAAU,CAAC,GAAG;IAC9D,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACxC,0BAA0B,CAAC,EAAE,wBAAwB,EAAE,CAAC;IACxD,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1D,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IAC9E,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC5E,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACzE,eAAe,CAAC,EAAE,CAChB,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,KAChC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAE5B,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACxD,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;IACtD,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC9D,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IACjE,yBAAyB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;CACxE,CAAC;AAEF,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,UAAU,EAAE,MAAM;CAI/B"}
@@ -0,0 +1,113 @@
1
+ export type SettingSource = {
2
+ type: 'core';
3
+ } | {
4
+ type: 'plugin';
5
+ pluginId: string;
6
+ pluginName?: string;
7
+ };
8
+ export type SettingCategory = string;
9
+ export type BooleanWidget = {
10
+ type: 'toggle';
11
+ };
12
+ export type NumberWidget = {
13
+ type: 'slider';
14
+ min?: number;
15
+ max?: number;
16
+ step?: number;
17
+ unit?: string;
18
+ } | {
19
+ type: 'number-input';
20
+ min?: number;
21
+ max?: number;
22
+ step?: number;
23
+ unit?: string;
24
+ };
25
+ export type StringWidget = {
26
+ type: 'text';
27
+ placeholder?: string;
28
+ } | {
29
+ type: 'password';
30
+ placeholder?: string;
31
+ } | {
32
+ type: 'textarea';
33
+ placeholder?: string;
34
+ rows?: number;
35
+ };
36
+ export type EnumWidget = {
37
+ type: 'select';
38
+ } | {
39
+ type: 'radio';
40
+ };
41
+ export type BooleanSettingDefinition = {
42
+ id: string;
43
+ title: string;
44
+ description?: string;
45
+ category: SettingCategory;
46
+ kind: 'boolean';
47
+ default?: boolean;
48
+ hidden?: boolean;
49
+ source?: SettingSource;
50
+ widget?: BooleanWidget;
51
+ };
52
+ export type NumberSettingDefinition = {
53
+ id: string;
54
+ title: string;
55
+ description?: string;
56
+ category: SettingCategory;
57
+ kind: 'number';
58
+ default?: number;
59
+ hidden?: boolean;
60
+ source?: SettingSource;
61
+ widget?: NumberWidget;
62
+ min?: number;
63
+ max?: number;
64
+ step?: number;
65
+ unit?: string;
66
+ };
67
+ export type StringFormat = 'text' | 'url' | 'path' | 'token' | 'language';
68
+ export type StringSettingDefinition = {
69
+ id: string;
70
+ title: string;
71
+ description?: string;
72
+ category: SettingCategory;
73
+ kind: 'string';
74
+ default?: string;
75
+ hidden?: boolean;
76
+ source?: SettingSource;
77
+ widget?: StringWidget;
78
+ format?: StringFormat;
79
+ pattern?: string;
80
+ minLength?: number;
81
+ maxLength?: number;
82
+ };
83
+ export type EnumOption = {
84
+ value: string;
85
+ label: string;
86
+ };
87
+ export type EnumSettingDefinition = {
88
+ id: string;
89
+ title: string;
90
+ description?: string;
91
+ category: SettingCategory;
92
+ kind: 'enum';
93
+ options: EnumOption[];
94
+ default?: string;
95
+ hidden?: boolean;
96
+ source?: SettingSource;
97
+ widget?: EnumWidget;
98
+ };
99
+ export type SettingDefinition = BooleanSettingDefinition | NumberSettingDefinition | StringSettingDefinition | EnumSettingDefinition;
100
+ export type SettingValue = boolean | number | string | undefined;
101
+ export type SettingsRegistration = {
102
+ settings: SettingDefinition[];
103
+ };
104
+ export type SettingsRegistrationResult = {
105
+ registered: string[];
106
+ };
107
+ export type SettingsHost = {
108
+ register(defs: SettingDefinition[], source: SettingSource): Promise<SettingsRegistrationResult>;
109
+ get<T extends SettingValue = SettingValue>(id: string): Promise<T | undefined>;
110
+ set<T extends SettingValue = SettingValue>(id: string, value: T): Promise<void>;
111
+ subscribe<T extends SettingValue = SettingValue>(id: string, listener: (value: T | undefined) => void): () => void;
112
+ };
113
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/types/settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import { NuclearPluginAPI } from './api';
2
+ export type PluginIcon = {
3
+ type: 'link';
4
+ link: string;
5
+ };
6
+ export type PluginManifest = {
7
+ name: string;
8
+ version: string;
9
+ description: string;
10
+ author: string;
11
+ main?: string;
12
+ nuclear?: {
13
+ displayName?: string;
14
+ category?: string;
15
+ icon?: PluginIcon;
16
+ permissions?: string[];
17
+ };
18
+ };
19
+ export type NuclearPlugin = {
20
+ onLoad?(api: NuclearPluginAPI): void | Promise<void>;
21
+ onUnload?(): void | Promise<void>;
22
+ onEnable?(api: NuclearPluginAPI): void | Promise<void>;
23
+ onDisable?(): void | Promise<void>;
24
+ };
25
+ export type PluginMetadata = {
26
+ id: string;
27
+ name: string;
28
+ displayName: string;
29
+ version: string;
30
+ description: string;
31
+ author: string;
32
+ category?: string;
33
+ icon?: PluginIcon;
34
+ permissions: string[];
35
+ };
36
+ export type LoadedPlugin = {
37
+ metadata: PluginMetadata;
38
+ instance: NuclearPlugin;
39
+ path: string;
40
+ };
41
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAE9C,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,UAAU,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;KACxB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,QAAQ,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,QAAQ,CAAC,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,SAAS,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,cAAc,CAAC;IACzB,QAAQ,EAAE,aAAa,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC"}
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@nuclearplayer/plugin-sdk",
3
+ "version": "0.0.9",
4
+ "description": "Plugin SDK for Nuclear music player",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "dependencies": {
20
+ "class-variance-authority": "^0.7.1",
21
+ "clsx": "^2.1.1",
22
+ "lucide-react": "^0.542.0",
23
+ "react": "^18.3.1",
24
+ "tailwind-merge": "^3.3.1",
25
+ "@nuclearplayer/model": "0.0.9"
26
+ },
27
+ "devDependencies": {
28
+ "@tailwindcss/vite": "^4.1.11",
29
+ "@testing-library/jest-dom": "^6.6.4",
30
+ "@testing-library/react": "^16.3.0",
31
+ "@types/react": "^18.3.23",
32
+ "@vitejs/plugin-react": "^5.0.1",
33
+ "@vitest/coverage-v8": "^3.2.4",
34
+ "jsdom": "^26.1.0",
35
+ "react-dom": "^18.3.1",
36
+ "tailwindcss": "^4.1.11",
37
+ "typescript": "^5.9.2",
38
+ "vite": "^7.1.3",
39
+ "vite-plugin-dts": "^4.5.4",
40
+ "vitest": "^3.2.4",
41
+ "@nuclearplayer/eslint-config": "0.0.9",
42
+ "@nuclearplayer/tailwind-config": "0.0.9"
43
+ },
44
+ "peerDependencies": {
45
+ "react": "^18.3.1"
46
+ },
47
+ "keywords": [
48
+ "nuclear",
49
+ "music",
50
+ "player",
51
+ "plugin",
52
+ "sdk",
53
+ "react"
54
+ ],
55
+ "author": {
56
+ "name": "nukeop",
57
+ "email": "12746779+nukeop@users.noreply.github.com"
58
+ },
59
+ "license": "AGPL-3.0-only",
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "https://github.com/NuclearPlayer/nuclear-xrd.git",
63
+ "directory": "packages/plugin-sdk"
64
+ },
65
+ "homepage": "https://github.com/NuclearPlayer/nuclear-xrd#readme",
66
+ "bugs": {
67
+ "url": "https://github.com/NuclearPlayer/nuclear-xrd/issues"
68
+ },
69
+ "scripts": {
70
+ "dev": "vite",
71
+ "build": "tsc && vite build",
72
+ "build:npm": "vite build --mode npm",
73
+ "test": "vitest --run",
74
+ "test:watch": "vitest",
75
+ "test:coverage": "vitest --run --coverage",
76
+ "lint": "eslint .",
77
+ "lint:fix": "eslint . --fix",
78
+ "type-check": "tsc --noEmit",
79
+ "clean": "rm -rf dist build .turbo node_modules/.vite"
80
+ }
81
+ }