@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 CHANGED
@@ -1,53 +1,59 @@
1
1
  # Nuclear Plugin SDK
2
2
 
3
- Official toolkit for building Nuclear plugins.
3
+ Build plugins for Nuclear music player.
4
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.
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
- 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`).
36
+ Build it to `dist/index.js` as a CommonJS bundle.
37
+
38
+ ## Manifest (package.json)
33
39
 
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
+ ### 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 standard fields:
42
- - `main`: Entry file path relative to package root. If omitted the loader tries `index.js` then `dist/index.js`.
46
+ ### Optional fields
47
+ - `main` - Entry file path (defaults to `index.js` or `dist/index.js`)
43
48
 
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
+ ### 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
- ## 4. Icon Specification
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
- ## 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.
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
- ## 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.
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 (built output)
106
- node_modules/
117
+ index.js
107
118
  ```
108
119
 
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.
120
+ ## Building
111
121
 
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';
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
- ## 13. Example Complete Minimal Plugin (TypeScript)
148
- ```ts
149
- import { NuclearPluginAPI } from '@nuclearplayer/plugin-sdk';
133
+ Run `pnpm build` and you'll get `dist/index.js`.
150
134
 
151
- export default {
152
- async onLoad(api: NuclearPluginAPI) {
153
- },
154
- async onEnable(api: NuclearPluginAPI) {
155
- },
156
- async onDisable() {
157
- },
158
- async onUnload() {
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
- ## 14. License
171
+ ## License
172
+
164
173
  AGPL-3.0-only
package/dist/index.d.ts CHANGED
@@ -4,4 +4,5 @@ export * from './types/settings';
4
4
  export * from './types/search';
5
5
  export type { ProvidersHost } from './types/providers';
6
6
  export { useSetting } from './react/useSetting';
7
+ export * from '@nuclearplayer/model';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -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 a, useEffect as l, useMemo as g } from "react";
2
- class h {
3
- #r;
1
+ import { useState as g, useEffect as f, useMemo as b } from "react";
2
+ class p {
3
+ #e;
4
4
  constructor(t) {
5
- this.#r = t;
5
+ this.#e = t;
6
6
  }
7
7
  #t(t) {
8
- const r = this.#r;
9
- if (!r)
8
+ const e = this.#e;
9
+ if (!e)
10
10
  throw new Error("Providers host not available");
11
- return t(r);
11
+ return t(e);
12
12
  }
13
13
  register(t) {
14
- return this.#t((r) => r.register(t));
14
+ return this.#t((e) => e.register(t));
15
15
  }
16
16
  unregister(t) {
17
- return this.#t((r) => r.unregister(t));
17
+ return this.#t((e) => e.unregister(t));
18
18
  }
19
19
  list(t) {
20
- return this.#t((r) => r.list(t));
20
+ return this.#t((e) => e.list(t));
21
21
  }
22
22
  get(t) {
23
- return this.#t((r) => r.get(t));
23
+ return this.#t((e) => e.get(t));
24
24
  }
25
25
  }
26
- class b {
27
- #r;
26
+ class d {
27
+ #e;
28
28
  constructor(t) {
29
- this.#r = t;
29
+ this.#e = t;
30
30
  }
31
31
  #t(t) {
32
- const r = this.#r;
33
- if (!r)
32
+ const e = this.#e;
33
+ if (!e)
34
34
  throw new Error("Settings host not available");
35
- return t(r);
35
+ return t(e);
36
36
  }
37
- register(t, r) {
38
- return this.#t((s) => s.register(t, r));
37
+ register(t, e) {
38
+ return this.#t((i) => i.register(t, e));
39
39
  }
40
40
  get(t) {
41
- return this.#t((r) => r.get(t));
41
+ return this.#t((e) => e.get(t));
42
42
  }
43
- set(t, r) {
44
- return this.#t((s) => s.set(t, r));
43
+ set(t, e) {
44
+ return this.#t((i) => i.set(t, e));
45
45
  }
46
- subscribe(t, r) {
47
- return this.#t((s) => s.subscribe(t, r));
46
+ subscribe(t, e) {
47
+ return this.#t((i) => i.subscribe(t, e));
48
48
  }
49
49
  }
50
- class f {
50
+ class v {
51
51
  Settings;
52
52
  Providers;
53
53
  constructor(t) {
54
- this.Settings = new b(t?.settingsHost), this.Providers = new h(t?.providersHost);
54
+ this.Settings = new d(t?.settingsHost), this.Providers = new p(t?.providersHost);
55
55
  }
56
56
  }
57
- class d extends f {
57
+ class w extends v {
58
58
  }
59
- class w extends Error {
59
+ class M extends Error {
60
60
  constructor(t) {
61
61
  super(`Missing capability: ${t}`), this.name = "MissingCapabilityError";
62
62
  }
63
63
  }
64
- const p = (e, t) => {
65
- const [r, s] = a(void 0);
66
- l(() => {
67
- if (!e)
64
+ const A = (s, t) => {
65
+ const [e, i] = g(void 0);
66
+ f(() => {
67
+ if (!s)
68
68
  return;
69
- let i = !0, u = !1;
70
- const o = e.subscribe(t, (n) => {
71
- i && (u = !0, s(n));
69
+ let u = !0, c = !1;
70
+ const r = s.subscribe(t, (n) => {
71
+ u && (c = !0, i(n));
72
72
  });
73
- return e.get(t).then((n) => {
74
- i && (u || s(n));
73
+ return s.get(t).then((n) => {
74
+ u && (c || i(n));
75
75
  }), () => {
76
- i = !1, o && o();
76
+ u = !1, r && r();
77
77
  };
78
- }, [t, e]);
79
- const c = g(
80
- () => (i) => {
81
- e && e.set(t, i);
78
+ }, [t, s]);
79
+ const o = b(
80
+ () => (u) => {
81
+ s && s.set(t, u);
82
82
  },
83
- [t, e]
83
+ [t, s]
84
84
  );
85
- return [r, c];
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
- w as MissingCapabilityError,
89
- f as NuclearAPI,
90
- d as NuclearPluginAPI,
91
- p as useSetting
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.9",
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",