@nuclearplayer/plugin-sdk 0.0.9 → 0.0.11
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 +92 -83
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +76 -48
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -1,53 +1,59 @@
|
|
|
1
1
|
# Nuclear Plugin SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Build plugins for Nuclear music player.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
Plugins are JavaScript/TypeScript modules that extend Nuclear's functionality. Write lifecycle hooks, register providers, and ship it as an npm package or local bundle.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
7
8
|
|
|
8
|
-
## 2. Quick Start
|
|
9
9
|
```bash
|
|
10
10
|
mkdir my-plugin && cd my-plugin
|
|
11
11
|
pnpm init -y
|
|
12
12
|
pnpm add @nuclearplayer/plugin-sdk
|
|
13
|
-
# add dev tooling of your choice (vite, tsup, esbuild, rollup)
|
|
14
13
|
```
|
|
15
14
|
|
|
16
15
|
Create `src/index.ts`:
|
|
16
|
+
|
|
17
17
|
```ts
|
|
18
18
|
import { NuclearPluginAPI } from '@nuclearplayer/plugin-sdk';
|
|
19
19
|
|
|
20
20
|
export default {
|
|
21
21
|
async onLoad(api: NuclearPluginAPI) {
|
|
22
|
+
console.log('Plugin loaded');
|
|
22
23
|
},
|
|
23
24
|
async onEnable(api: NuclearPluginAPI) {
|
|
25
|
+
console.log('Plugin enabled');
|
|
24
26
|
},
|
|
25
27
|
async onDisable() {
|
|
28
|
+
console.log('Plugin disabled');
|
|
26
29
|
},
|
|
27
30
|
async onUnload() {
|
|
31
|
+
console.log('Plugin unloaded');
|
|
28
32
|
},
|
|
29
33
|
};
|
|
30
34
|
```
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
Build it to `dist/index.js` as a CommonJS bundle.
|
|
37
|
+
|
|
38
|
+
## Manifest (package.json)
|
|
33
39
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- `
|
|
37
|
-
- `
|
|
38
|
-
- `
|
|
39
|
-
- `author`: Plain string.
|
|
40
|
+
### Required fields
|
|
41
|
+
- `name` - Unique plugin ID (scoped names allowed)
|
|
42
|
+
- `version` - Semver version
|
|
43
|
+
- `description` - One-line summary
|
|
44
|
+
- `author` - Your name
|
|
40
45
|
|
|
41
|
-
Optional
|
|
42
|
-
- `main
|
|
46
|
+
### Optional fields
|
|
47
|
+
- `main` - Entry file path (defaults to `index.js` or `dist/index.js`)
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- `
|
|
48
|
-
- `
|
|
49
|
+
### Nuclear-specific config
|
|
50
|
+
Add a `nuclear` object for extra metadata:
|
|
51
|
+
|
|
52
|
+
- `displayName` - Friendly name (defaults to `name`)
|
|
53
|
+
- `category` - Arbitrary grouping (e.g., `source`, `integration`, `lyrics`)
|
|
54
|
+
- `icon` - See below
|
|
55
|
+
- `permissions` - Capabilities your plugin uses (informational only for now)
|
|
49
56
|
|
|
50
|
-
Example:
|
|
51
57
|
```json
|
|
52
58
|
{
|
|
53
59
|
"name": "@nuclear-plugin/lastfm",
|
|
@@ -64,20 +70,23 @@ Example:
|
|
|
64
70
|
}
|
|
65
71
|
```
|
|
66
72
|
|
|
67
|
-
##
|
|
73
|
+
## Icons
|
|
74
|
+
|
|
68
75
|
```ts
|
|
69
76
|
type PluginIcon = { type: 'link'; link: string };
|
|
70
77
|
```
|
|
78
|
+
|
|
71
79
|
Link icons should point to a local file path or remote URL; keep them small (<= 64x64, optimized).
|
|
72
80
|
|
|
73
|
-
##
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- `
|
|
78
|
-
- `
|
|
81
|
+
## Lifecycle Hooks
|
|
82
|
+
|
|
83
|
+
All hooks are optional. Export a default object with any of:
|
|
84
|
+
|
|
85
|
+
- `onLoad(api)` - Runs after plugin code loads and manifest is parsed
|
|
86
|
+
- `onEnable(api)` - Runs when user enables the plugin
|
|
87
|
+
- `onDisable()` - Runs when user disables it
|
|
88
|
+
- `onUnload()` - Runs before plugin is removed from memory
|
|
79
89
|
|
|
80
|
-
Typical pattern:
|
|
81
90
|
```ts
|
|
82
91
|
export default {
|
|
83
92
|
async onLoad(api) {
|
|
@@ -91,74 +100,74 @@ export default {
|
|
|
91
100
|
};
|
|
92
101
|
```
|
|
93
102
|
|
|
94
|
-
##
|
|
95
|
-
|
|
103
|
+
## Permissions
|
|
104
|
+
|
|
105
|
+
Declare what your plugin does in the `permissions` array. Permissions are currently informational. Future versions might show UI for this.
|
|
106
|
+
|
|
107
|
+
Examples: `network`, `scrobble`, `playback-control`, `lyrics`, `search`, `storage`
|
|
108
|
+
|
|
109
|
+
## File Structure
|
|
96
110
|
|
|
97
|
-
## 7. File Structure Example
|
|
98
111
|
```text
|
|
99
112
|
my-plugin/
|
|
100
113
|
package.json
|
|
101
|
-
README.md
|
|
102
114
|
src/
|
|
103
115
|
index.ts
|
|
104
116
|
dist/
|
|
105
|
-
index.js
|
|
106
|
-
node_modules/
|
|
117
|
+
index.js
|
|
107
118
|
```
|
|
108
119
|
|
|
109
|
-
##
|
|
110
|
-
You can use any bundler that outputs a single JS file that the loader can evaluate in a CommonJS style environment.
|
|
120
|
+
## Building
|
|
111
121
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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';
|
|
122
|
+
You can use any bundler that outputs a single JS file. Your bundle needs to work in a CommonJS environment (`module.exports` or `exports.default`).
|
|
123
|
+
|
|
124
|
+
Example with tsup:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"devDependencies": { "tsup": "^8" },
|
|
129
|
+
"scripts": { "build": "tsup src/index.ts --dts --format cjs --minify --out-dir dist" }
|
|
130
|
+
}
|
|
145
131
|
```
|
|
146
132
|
|
|
147
|
-
|
|
148
|
-
```ts
|
|
149
|
-
import { NuclearPluginAPI } from '@nuclearplayer/plugin-sdk';
|
|
133
|
+
Run `pnpm build` and you'll get `dist/index.js`.
|
|
150
134
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
135
|
+
## Development
|
|
136
|
+
|
|
137
|
+
1. Create your plugin folder
|
|
138
|
+
2. Build to produce the entry file
|
|
139
|
+
3. Load it in Nuclear
|
|
140
|
+
4. Rebuild after changes; you'll need to reload the plugin
|
|
141
|
+
|
|
142
|
+
## Tips
|
|
143
|
+
|
|
144
|
+
- Keep startup fast, defer heavy work to `onEnable`
|
|
145
|
+
- Validate network responses
|
|
146
|
+
- Minimize dependencies, smaller = faster
|
|
147
|
+
|
|
148
|
+
## Troubleshooting
|
|
149
|
+
|
|
150
|
+
| Problem | Solution |
|
|
151
|
+
|---------|----------|
|
|
152
|
+
| Can't find entry file | Check `main` in package.json or make sure `index.js` or `dist/index.js` exists |
|
|
153
|
+
| Missing fields error | Add all required fields: name, version, description, author |
|
|
154
|
+
| Hooks don't fire | Export a default object, not a function or class |
|
|
155
|
+
|
|
156
|
+
## Types
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import type {
|
|
160
|
+
NuclearPlugin,
|
|
161
|
+
PluginManifest,
|
|
162
|
+
PluginIcon,
|
|
163
|
+
// Model types (re-exported from @nuclearplayer/model)
|
|
164
|
+
Artist,
|
|
165
|
+
Album,
|
|
166
|
+
Track,
|
|
167
|
+
// ... and many more
|
|
168
|
+
} from '@nuclearplayer/plugin-sdk';
|
|
161
169
|
```
|
|
162
170
|
|
|
163
|
-
##
|
|
171
|
+
## License
|
|
172
|
+
|
|
164
173
|
AGPL-3.0-only
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +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"}
|
|
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;AAChD,cAAc,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,92 +1,120 @@
|
|
|
1
|
-
import { useState as
|
|
2
|
-
class
|
|
3
|
-
#
|
|
1
|
+
import { useState as g, useEffect as f, useMemo as b } from "react";
|
|
2
|
+
class p {
|
|
3
|
+
#e;
|
|
4
4
|
constructor(t) {
|
|
5
|
-
this.#
|
|
5
|
+
this.#e = t;
|
|
6
6
|
}
|
|
7
7
|
#t(t) {
|
|
8
|
-
const
|
|
9
|
-
if (!
|
|
8
|
+
const e = this.#e;
|
|
9
|
+
if (!e)
|
|
10
10
|
throw new Error("Providers host not available");
|
|
11
|
-
return t(
|
|
11
|
+
return t(e);
|
|
12
12
|
}
|
|
13
13
|
register(t) {
|
|
14
|
-
return this.#t((
|
|
14
|
+
return this.#t((e) => e.register(t));
|
|
15
15
|
}
|
|
16
16
|
unregister(t) {
|
|
17
|
-
return this.#t((
|
|
17
|
+
return this.#t((e) => e.unregister(t));
|
|
18
18
|
}
|
|
19
19
|
list(t) {
|
|
20
|
-
return this.#t((
|
|
20
|
+
return this.#t((e) => e.list(t));
|
|
21
21
|
}
|
|
22
22
|
get(t) {
|
|
23
|
-
return this.#t((
|
|
23
|
+
return this.#t((e) => e.get(t));
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
class
|
|
27
|
-
#
|
|
26
|
+
class d {
|
|
27
|
+
#e;
|
|
28
28
|
constructor(t) {
|
|
29
|
-
this.#
|
|
29
|
+
this.#e = t;
|
|
30
30
|
}
|
|
31
31
|
#t(t) {
|
|
32
|
-
const
|
|
33
|
-
if (!
|
|
32
|
+
const e = this.#e;
|
|
33
|
+
if (!e)
|
|
34
34
|
throw new Error("Settings host not available");
|
|
35
|
-
return t(
|
|
35
|
+
return t(e);
|
|
36
36
|
}
|
|
37
|
-
register(t,
|
|
38
|
-
return this.#t((
|
|
37
|
+
register(t, e) {
|
|
38
|
+
return this.#t((i) => i.register(t, e));
|
|
39
39
|
}
|
|
40
40
|
get(t) {
|
|
41
|
-
return this.#t((
|
|
41
|
+
return this.#t((e) => e.get(t));
|
|
42
42
|
}
|
|
43
|
-
set(t,
|
|
44
|
-
return this.#t((
|
|
43
|
+
set(t, e) {
|
|
44
|
+
return this.#t((i) => i.set(t, e));
|
|
45
45
|
}
|
|
46
|
-
subscribe(t,
|
|
47
|
-
return this.#t((
|
|
46
|
+
subscribe(t, e) {
|
|
47
|
+
return this.#t((i) => i.subscribe(t, e));
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
-
class
|
|
50
|
+
class v {
|
|
51
51
|
Settings;
|
|
52
52
|
Providers;
|
|
53
53
|
constructor(t) {
|
|
54
|
-
this.Settings = new
|
|
54
|
+
this.Settings = new d(t?.settingsHost), this.Providers = new p(t?.providersHost);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
class
|
|
57
|
+
class w extends v {
|
|
58
58
|
}
|
|
59
|
-
class
|
|
59
|
+
class M extends Error {
|
|
60
60
|
constructor(t) {
|
|
61
61
|
super(`Missing capability: ${t}`), this.name = "MissingCapabilityError";
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
const
|
|
65
|
-
const [
|
|
66
|
-
|
|
67
|
-
if (!
|
|
64
|
+
const A = (s, t) => {
|
|
65
|
+
const [e, i] = g(void 0);
|
|
66
|
+
f(() => {
|
|
67
|
+
if (!s)
|
|
68
68
|
return;
|
|
69
|
-
let
|
|
70
|
-
const
|
|
71
|
-
|
|
69
|
+
let u = !0, c = !1;
|
|
70
|
+
const r = s.subscribe(t, (n) => {
|
|
71
|
+
u && (c = !0, i(n));
|
|
72
72
|
});
|
|
73
|
-
return
|
|
74
|
-
|
|
73
|
+
return s.get(t).then((n) => {
|
|
74
|
+
u && (c || i(n));
|
|
75
75
|
}), () => {
|
|
76
|
-
|
|
76
|
+
u = !1, r && r();
|
|
77
77
|
};
|
|
78
|
-
}, [t,
|
|
79
|
-
const
|
|
80
|
-
() => (
|
|
81
|
-
|
|
78
|
+
}, [t, s]);
|
|
79
|
+
const o = b(
|
|
80
|
+
() => (u) => {
|
|
81
|
+
s && s.set(t, u);
|
|
82
82
|
},
|
|
83
|
-
[t,
|
|
83
|
+
[t, s]
|
|
84
84
|
);
|
|
85
|
-
return [
|
|
85
|
+
return [e, o];
|
|
86
86
|
};
|
|
87
|
+
function E(s, t, e) {
|
|
88
|
+
if (!s?.items?.length)
|
|
89
|
+
return;
|
|
90
|
+
const i = s.items.filter((r) => !(r.purpose && r.purpose !== t || !r.url));
|
|
91
|
+
if (!i.length)
|
|
92
|
+
return s.items[0];
|
|
93
|
+
const o = (r) => !r.width || !r.height ? 1 : r.width / r.height, c = ((r) => {
|
|
94
|
+
switch (r) {
|
|
95
|
+
case "avatar":
|
|
96
|
+
case "thumbnail":
|
|
97
|
+
return 1;
|
|
98
|
+
case "cover":
|
|
99
|
+
return 1;
|
|
100
|
+
case "background":
|
|
101
|
+
return 16 / 9;
|
|
102
|
+
default:
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
})(t);
|
|
106
|
+
return i.map((r) => {
|
|
107
|
+
const n = Math.min(r.width || 0, r.height || 0), a = Math.abs(o(r) - c), h = Math.abs(n - e), l = n < e ? e / n : 1;
|
|
108
|
+
return {
|
|
109
|
+
artwork: r,
|
|
110
|
+
score: (l > 1.5 ? -1e3 : 0) + -a * 50 + -h * 0.1
|
|
111
|
+
};
|
|
112
|
+
}).sort((r, n) => n.score - r.score)[0]?.artwork;
|
|
113
|
+
}
|
|
87
114
|
export {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
115
|
+
M as MissingCapabilityError,
|
|
116
|
+
v as NuclearAPI,
|
|
117
|
+
w as NuclearPluginAPI,
|
|
118
|
+
E as pickArtwork,
|
|
119
|
+
A as useSetting
|
|
92
120
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuclearplayer/plugin-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Plugin SDK for Nuclear music player",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,8 +21,7 @@
|
|
|
21
21
|
"clsx": "^2.1.1",
|
|
22
22
|
"lucide-react": "^0.542.0",
|
|
23
23
|
"react": "^18.3.1",
|
|
24
|
-
"tailwind-merge": "^3.3.1"
|
|
25
|
-
"@nuclearplayer/model": "0.0.9"
|
|
24
|
+
"tailwind-merge": "^3.3.1"
|
|
26
25
|
},
|
|
27
26
|
"devDependencies": {
|
|
28
27
|
"@tailwindcss/vite": "^4.1.11",
|