roon-mcp 0.1.0

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.
Files changed (67) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +161 -0
  4. package/dist/BrowseSessionManager.d.ts +34 -0
  5. package/dist/BrowseSessionManager.js +97 -0
  6. package/dist/BrowseSessionManager.js.map +1 -0
  7. package/dist/BrowseSessionManager.test.d.ts +1 -0
  8. package/dist/BrowseSessionManager.test.js +83 -0
  9. package/dist/BrowseSessionManager.test.js.map +1 -0
  10. package/dist/GenreService.d.ts +22 -0
  11. package/dist/GenreService.js +177 -0
  12. package/dist/GenreService.js.map +1 -0
  13. package/dist/GenreService.test.d.ts +1 -0
  14. package/dist/GenreService.test.js +147 -0
  15. package/dist/GenreService.test.js.map +1 -0
  16. package/dist/PlaybackService.d.ts +43 -0
  17. package/dist/PlaybackService.js +291 -0
  18. package/dist/PlaybackService.js.map +1 -0
  19. package/dist/PlaybackService.test.d.ts +1 -0
  20. package/dist/PlaybackService.test.js +281 -0
  21. package/dist/PlaybackService.test.js.map +1 -0
  22. package/dist/RoonClient.d.ts +37 -0
  23. package/dist/RoonClient.js +105 -0
  24. package/dist/RoonClient.js.map +1 -0
  25. package/dist/RoonMcpServer.d.ts +21 -0
  26. package/dist/RoonMcpServer.js +218 -0
  27. package/dist/RoonMcpServer.js.map +1 -0
  28. package/dist/SearchNavigator.d.ts +38 -0
  29. package/dist/SearchNavigator.js +104 -0
  30. package/dist/SearchNavigator.js.map +1 -0
  31. package/dist/SearchService.d.ts +29 -0
  32. package/dist/SearchService.js +236 -0
  33. package/dist/SearchService.js.map +1 -0
  34. package/dist/SearchService.test.d.ts +1 -0
  35. package/dist/SearchService.test.js +197 -0
  36. package/dist/SearchService.test.js.map +1 -0
  37. package/dist/TrackExpansionService.d.ts +75 -0
  38. package/dist/TrackExpansionService.js +337 -0
  39. package/dist/TrackExpansionService.js.map +1 -0
  40. package/dist/TrackExpansionService.test.d.ts +1 -0
  41. package/dist/TrackExpansionService.test.js +382 -0
  42. package/dist/TrackExpansionService.test.js.map +1 -0
  43. package/dist/ZoneService.d.ts +52 -0
  44. package/dist/ZoneService.js +139 -0
  45. package/dist/ZoneService.js.map +1 -0
  46. package/dist/ZoneService.test.d.ts +1 -0
  47. package/dist/ZoneService.test.js +111 -0
  48. package/dist/ZoneService.test.js.map +1 -0
  49. package/dist/index.d.ts +2 -0
  50. package/dist/index.js +44 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/locator.d.ts +58 -0
  53. package/dist/locator.js +88 -0
  54. package/dist/locator.js.map +1 -0
  55. package/dist/locator.test.d.ts +1 -0
  56. package/dist/locator.test.js +37 -0
  57. package/dist/locator.test.js.map +1 -0
  58. package/dist/logger.d.ts +23 -0
  59. package/dist/logger.js +56 -0
  60. package/dist/logger.js.map +1 -0
  61. package/dist/logger.test.d.ts +1 -0
  62. package/dist/logger.test.js +53 -0
  63. package/dist/logger.test.js.map +1 -0
  64. package/dist/types.d.ts +93 -0
  65. package/dist/types.js +13 -0
  66. package/dist/types.js.map +1 -0
  67. package/package.json +60 -0
@@ -0,0 +1,382 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { BrowseSessionManager } from "./BrowseSessionManager.js";
4
+ import { decodeLocator, encodeGenreLocator, encodeLocator, isGenreLocator, } from "./locator.js";
5
+ import { TrackExpansionService } from "./TrackExpansionService.js";
6
+ /** Locator for a single-group/single-item search keyed by `query`. */
7
+ const loc = (query) => encodeLocator({ q: query, g: 0, i: 0 });
8
+ // Synthetic keys for the canned search tree the navigator walks:
9
+ // search(input) → [group] → [item] → opened node (nodes[query]).
10
+ const GROUP_KEY = "__grp";
11
+ const ITEM_KEY = "__itm";
12
+ /**
13
+ * Minimal stateful model of the search hierarchy the expander navigates. The
14
+ * opened item for a query is `nodes[query]`; secondary containers (e.g. a
15
+ * "Tracks" sub-list) are keyed by their own item_key, also in `nodes`.
16
+ */
17
+ class FakeBrowse {
18
+ nodes;
19
+ stack = [];
20
+ query = "";
21
+ constructor(nodes) {
22
+ this.nodes = nodes;
23
+ }
24
+ browse(o, cb) {
25
+ if (o.pop_all) {
26
+ this.stack = [];
27
+ if (o.input !== undefined) {
28
+ this.query = o.input;
29
+ this.stack = [
30
+ { items: [{ title: "Group", item_key: GROUP_KEY, hint: "list" }], hint: null, title: "Search" },
31
+ ];
32
+ }
33
+ return cb(false, { action: "list" });
34
+ }
35
+ if (o.pop_levels) {
36
+ for (let i = 0; i < o.pop_levels; i++)
37
+ this.stack.pop();
38
+ return cb(false, { action: "list" });
39
+ }
40
+ if (o.item_key !== undefined) {
41
+ if (o.item_key === GROUP_KEY) {
42
+ this.stack.push({ items: [{ title: "Item", item_key: ITEM_KEY, hint: "list" }], hint: null, title: "Group" });
43
+ return cb(false, { action: "list" });
44
+ }
45
+ const node = o.item_key === ITEM_KEY ? this.nodes[this.query] : this.nodes[o.item_key];
46
+ if (node === undefined) {
47
+ return cb("InvalidItemKey", undefined);
48
+ }
49
+ if ("message" in node) {
50
+ return cb(false, { action: "message", message: node.message });
51
+ }
52
+ this.stack.push({ items: node.items, hint: node.listHint, title: node.title });
53
+ return cb(false, {
54
+ action: "list",
55
+ item: { title: node.title },
56
+ list: { title: node.title, count: node.items.length, level: this.stack.length - 1, hint: node.listHint ?? null },
57
+ });
58
+ }
59
+ return cb(false, { action: "none" });
60
+ }
61
+ load(o, cb) {
62
+ const top = this.stack[this.stack.length - 1] ?? { items: [], hint: null, title: "" };
63
+ const offset = o.offset ?? 0;
64
+ const items = top.items.slice(offset, offset + (o.count ?? 100));
65
+ cb(false, {
66
+ items,
67
+ offset,
68
+ list: { title: top.title, count: top.items.length, level: this.stack.length - 1, hint: top.hint ?? null },
69
+ });
70
+ }
71
+ }
72
+ function buildService(nodes) {
73
+ const fake = new FakeBrowse(nodes);
74
+ const stub = { waitForCore: async () => undefined, getBrowse: () => fake };
75
+ return new TrackExpansionService(new BrowseSessionManager(stub));
76
+ }
77
+ function track(title, key, subtitle) {
78
+ return { title, item_key: key, subtitle };
79
+ }
80
+ function nav(title, key) {
81
+ return { title, item_key: key, hint: "list" };
82
+ }
83
+ function header(title) {
84
+ return { title, hint: "header" };
85
+ }
86
+ function action(title, key) {
87
+ return { title, item_key: key, hint: "action" };
88
+ }
89
+ // A real Roon track row opens its Play/Queue menu when selected, so it carries
90
+ // hint "action_list" (unlike the bare-leaf `track()` model above). The leading
91
+ // "Play Album"/"Play Artist" shortcut shares that hint but is not a track.
92
+ function actionTrack(title, key, subtitle) {
93
+ return { title, item_key: key, subtitle, hint: "action_list" };
94
+ }
95
+ function playShortcut(title, key) {
96
+ return { title, item_key: key, hint: "action_list" };
97
+ }
98
+ test("an album expands directly into its track list", async () => {
99
+ const svc = buildService({
100
+ album1: { title: "Dive", items: [track("Awake", "t:1", "Tycho"), track("Daydream", "t:2", "Tycho")] },
101
+ });
102
+ const out = await svc.getTracksFor({ itemKey: loc("album1") });
103
+ assert.equal(out.sourceItemKey, loc("album1"));
104
+ assert.deepEqual(out.skipped, []);
105
+ assert.equal(out.tracks.length, 2);
106
+ // Each track is keyed by a locator extending the source with its index.
107
+ const first = decodeLocator(out.tracks[0]?.itemKey ?? "");
108
+ assert.ok(first && !isGenreLocator(first));
109
+ assert.equal(first.q, "album1");
110
+ assert.equal(first.t, 0);
111
+ assert.equal(out.tracks[0]?.title, "Awake");
112
+ assert.equal(out.tracks[0]?.artist, "Tycho");
113
+ assert.equal(decodeLocator(out.tracks[1]?.itemKey ?? "")?.t, 1);
114
+ });
115
+ test("a single track (action menu) returns itself as one track", async () => {
116
+ const svc = buildService({
117
+ track1: {
118
+ title: "Awake",
119
+ items: [action("Play Now", "a:p"), action("Add to Queue", "a:q")],
120
+ listHint: "action_list",
121
+ },
122
+ });
123
+ const out = await svc.getTracksFor({ itemKey: loc("track1") });
124
+ assert.equal(out.tracks.length, 1);
125
+ // The candidate is itself the track, so it keeps its own (t-less) locator.
126
+ assert.equal(out.tracks[0]?.itemKey, loc("track1"));
127
+ assert.equal(out.tracks[0]?.title, "Awake");
128
+ });
129
+ test("an artist page with a 'Top Tracks' header returns those tracks, not albums", async () => {
130
+ const svc = buildService({
131
+ artist1: {
132
+ title: "Tycho",
133
+ items: [
134
+ header("Top Tracks"),
135
+ track("Awake", "t:1", "Tycho"),
136
+ track("Montana", "t:2", "Tycho"),
137
+ header("Top Albums"),
138
+ nav("Dive", "al:1"),
139
+ ],
140
+ },
141
+ });
142
+ const out = await svc.getTracksFor({ itemKey: loc("artist1") });
143
+ assert.equal(out.tracks.length, 2);
144
+ assert.deepEqual(out.tracks.map((t) => t.title).sort(), ["Awake", "Montana"]);
145
+ assert.ok(out.tracks.every((t) => t.title !== "Dive"));
146
+ });
147
+ test("a local artist with no track section drills its top album into real tracks", async () => {
148
+ // Purely-local artist page: a flat list of album containers, no streaming
149
+ // "Top Tracks"/"Popular" section. The album must not be returned as a track;
150
+ // we drill it into its own track list. (Reproduces the live "Tycho" bug.)
151
+ const svc = buildService({
152
+ artist2: { title: "Tycho", items: [nav("Dive (Deluxe Version)", "al:1")] },
153
+ "al:1": {
154
+ title: "Dive (Deluxe Version)",
155
+ items: [track("Awake", "t:1", "Tycho"), track("Daydream", "t:2", "Tycho")],
156
+ },
157
+ });
158
+ const out = await svc.getTracksFor({ itemKey: loc("artist2") });
159
+ assert.deepEqual(out.skipped, []);
160
+ assert.deepEqual(out.tracks.map((t) => t.title), ["Awake", "Daydream"]);
161
+ assert.ok(out.tracks.every((t) => t.title !== "Dive (Deluxe Version)"));
162
+ // Tracks are keyed by the artist locator extended with their drilled index.
163
+ assert.deepEqual(out.tracks.map((t) => decodeLocator(t.itemKey)?.t), [0, 1]);
164
+ assert.equal(out.tracks[0]?.artist, "Tycho");
165
+ });
166
+ test("a local artist drills its album past the leading 'Play' shortcuts (live shape)", async () => {
167
+ // Mirrors the real Tycho Core: the artist page leads with a "Play Artist"
168
+ // action_list shortcut + an album (hint "list"); the album leads with a
169
+ // "Play Album" shortcut + action_list track rows. Only the real track must
170
+ // come back — neither the album nor the "Play …" shortcuts.
171
+ const svc = buildService({
172
+ artist4: {
173
+ title: "Tycho",
174
+ items: [playShortcut("Play Artist", "pa:0"), nav("Dive (Deluxe Version)", "al:1")],
175
+ },
176
+ "al:1": {
177
+ title: "Dive (Deluxe Version)",
178
+ items: [playShortcut("Play Album", "pl:0"), actionTrack("Hours", "tk:1", "Tycho, Zac Brown")],
179
+ },
180
+ });
181
+ const out = await svc.getTracksFor({ itemKey: loc("artist4") });
182
+ assert.deepEqual(out.skipped, []);
183
+ assert.deepEqual(out.tracks.map((t) => t.title), ["Hours"]);
184
+ assert.ok(out.tracks.every((t) => !/^play /i.test(t.title) && t.title !== "Dive (Deluxe Version)"));
185
+ assert.equal(out.tracks[0]?.artist, "Tycho, Zac Brown");
186
+ assert.equal(decodeLocator(out.tracks[0]?.itemKey ?? "")?.t, 0);
187
+ });
188
+ test("an artist page with album sections (headers, no track label) drills the top album", async () => {
189
+ const svc = buildService({
190
+ artist3: {
191
+ title: "Tycho",
192
+ items: [header("Main Albums"), nav("Dive", "al:1"), nav("Awake", "al:2")],
193
+ },
194
+ "al:1": { title: "Dive", items: [track("A1", "t:1"), track("A2", "t:2")] },
195
+ "al:2": { title: "Awake", items: [track("B1", "t:3")] },
196
+ });
197
+ const out = await svc.getTracksFor({ itemKey: loc("artist3") });
198
+ // The first (top) album is drilled; its tracks come back, not the album names.
199
+ assert.deepEqual(out.tracks.map((t) => t.title), ["A1", "A2"]);
200
+ assert.deepEqual(out.skipped, []);
201
+ });
202
+ test("a located artist track re-resolves to a live key for playback", async () => {
203
+ const svc = buildService({
204
+ artist2: { title: "Tycho", items: [nav("Dive (Deluxe Version)", "al:1")] },
205
+ "al:1": { title: "Dive (Deluxe Version)", items: [track("Awake", "t:1"), track("Daydream", "t:2")] },
206
+ // Browsing into a track yields its action menu (the "self" shape).
207
+ "t:1": { title: "Awake", items: [action("Play Now", "a:p")], listHint: "action_list" },
208
+ "t:2": { title: "Daydream", items: [action("Play Now", "a:p")], listHint: "action_list" },
209
+ });
210
+ const out = await svc.getTracksFor({ itemKey: loc("artist2") });
211
+ const second = decodeLocator(out.tracks[1]?.itemKey ?? "");
212
+ assert.equal(second?.t, 1);
213
+ // PlaybackService re-navigates a track locator through openTrackForPlayback;
214
+ // it must re-drill the album and open entry t, landing on that track.
215
+ const opened = await svc.openTrackForPlayback(second);
216
+ assert.equal(opened.action, "list");
217
+ assert.equal(opened.item?.title, "Daydream");
218
+ });
219
+ test("when no direct tracks exist, it drills into a 'Tracks' container", async () => {
220
+ const svc = buildService({
221
+ mix1: {
222
+ title: "Daily Mix",
223
+ items: [header("Featured"), nav("Daily Mix", "dm:1"), header("More"), nav("Tracks", "tc:1")],
224
+ },
225
+ "tc:1": { title: "Tracks", items: [track("Song A", "s:1", "VA"), track("Song B", "s:2", "VA")] },
226
+ });
227
+ const out = await svc.getTracksFor({ itemKey: loc("mix1") });
228
+ assert.deepEqual(out.tracks.map((t) => t.title), ["Song A", "Song B"]);
229
+ assert.deepEqual(out.tracks.map((t) => decodeLocator(t.itemKey)?.t), [0, 1]);
230
+ assert.deepEqual(out.skipped, []);
231
+ });
232
+ test("a non-expandable (message) item returns empty tracks with a skipped reason", async () => {
233
+ const svc = buildService({ bad1: { message: "This item is not available." } });
234
+ const out = await svc.getTracksFor({ itemKey: loc("bad1") });
235
+ assert.deepEqual(out.tracks, []);
236
+ assert.equal(out.skipped.length, 1);
237
+ assert.match(out.skipped[0]?.reason ?? "", /NOT_EXPANDABLE/);
238
+ assert.match(out.skipped[0]?.reason ?? "", /not available/i);
239
+ });
240
+ test("a stale/invalid locator is reported as skipped, not thrown", async () => {
241
+ const svc = buildService({ album1: { title: "Dive", items: [track("Awake", "t:1")] } });
242
+ // A valid locator that no longer resolves (no such query in the tree).
243
+ const out = await svc.getTracksFor({ itemKey: loc("ghost") });
244
+ assert.deepEqual(out.tracks, []);
245
+ assert.equal(out.skipped.length, 1);
246
+ assert.equal(out.skipped[0]?.itemKey, loc("ghost"));
247
+ });
248
+ /** Stateful model of the `genres` hierarchy: a keyed tree with a level stack. */
249
+ class FakeGenres {
250
+ root;
251
+ stack = [];
252
+ byKey = new Map();
253
+ constructor(root) {
254
+ this.root = root;
255
+ const index = (nodes) => {
256
+ for (const n of nodes) {
257
+ this.byKey.set(n.key, n);
258
+ if (n.children)
259
+ index(n.children);
260
+ }
261
+ };
262
+ index(root);
263
+ }
264
+ browse(o, cb) {
265
+ if (o.pop_levels) {
266
+ for (let i = 0; i < o.pop_levels; i++)
267
+ this.stack.pop();
268
+ return cb(false, { action: "list" });
269
+ }
270
+ if (o.pop_all) {
271
+ this.stack = [this.root];
272
+ return cb(false, { action: "list" });
273
+ }
274
+ if (o.item_key !== undefined) {
275
+ const node = this.byKey.get(o.item_key);
276
+ if (!node)
277
+ return cb("InvalidItemKey", undefined);
278
+ this.stack.push(node.children ?? []);
279
+ return cb(false, {
280
+ action: "list",
281
+ item: { title: node.title },
282
+ list: { title: node.title, count: node.children?.length ?? 0, level: this.stack.length - 1 },
283
+ });
284
+ }
285
+ return cb(false, { action: "none" });
286
+ }
287
+ load(o, cb) {
288
+ const top = this.stack[this.stack.length - 1] ?? [];
289
+ const items = top.map((n) => ({
290
+ title: n.title,
291
+ item_key: n.key,
292
+ subtitle: n.subtitle,
293
+ hint: n.hint,
294
+ }));
295
+ cb(false, { items, offset: o.offset ?? 0, list: { title: "", count: items.length, level: 0 } });
296
+ }
297
+ }
298
+ /** A track leaf in the genre tree (bare leaf: no hint, so it's a track row). */
299
+ function gtrack(title, key, subtitle) {
300
+ return { title, key, subtitle };
301
+ }
302
+ function buildGenreService(root) {
303
+ const fake = new FakeGenres(root);
304
+ const stub = { waitForCore: async () => undefined, getBrowse: () => fake };
305
+ return new TrackExpansionService(new BrowseSessionManager(stub));
306
+ }
307
+ // A genre page with an "Albums" container holding three albums of varying size.
308
+ const PSYTRANCE_TREE = [
309
+ {
310
+ title: "Psytrance",
311
+ key: "g:psy",
312
+ subtitle: "0 Artists, 3 Albums",
313
+ hint: "list",
314
+ children: [
315
+ { title: "Play Genre", key: "a:pg", hint: "action_list" },
316
+ { title: "Artists", key: "c:art", hint: "list" },
317
+ {
318
+ title: "Albums",
319
+ key: "c:alb",
320
+ hint: "list",
321
+ children: [
322
+ {
323
+ title: "Album One",
324
+ key: "al:1",
325
+ hint: "list",
326
+ children: [gtrack("A1", "t:a1", "DJ One"), gtrack("A2", "t:a2", "DJ One"), gtrack("A3", "t:a3", "DJ One")],
327
+ },
328
+ {
329
+ title: "Album Two",
330
+ key: "al:2",
331
+ hint: "list",
332
+ children: [gtrack("B1", "t:b1", "DJ Two"), gtrack("B2", "t:b2", "DJ Two")],
333
+ },
334
+ { title: "Album Three", key: "al:3", hint: "list", children: [gtrack("C1", "t:c1", "DJ Three")] },
335
+ ],
336
+ },
337
+ ],
338
+ },
339
+ ];
340
+ const genreLoc = encodeGenreLocator(["Psytrance"]);
341
+ test("a genre expands across its albums, not just the first", async () => {
342
+ const svc = buildGenreService(PSYTRANCE_TREE);
343
+ const out = await svc.getTracksFor({ itemKey: genreLoc, limit: 10 });
344
+ assert.deepEqual(out.skipped, []);
345
+ // Tracks come from all three albums (the old behavior returned only Album One).
346
+ assert.deepEqual(out.tracks.map((t) => t.title), ["A1", "A2", "A3", "B1", "B2", "C1"]);
347
+ assert.equal(out.tracks[0]?.artist, "DJ One");
348
+ // Each is keyed by a genre locator with (album, track) coordinates.
349
+ const coords = out.tracks.map((t) => {
350
+ const loc = decodeLocator(t.itemKey);
351
+ assert.ok(isGenreLocator(loc));
352
+ return [loc.a, loc.t];
353
+ });
354
+ assert.deepEqual(coords, [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [2, 0]]);
355
+ });
356
+ test("a small limit spreads the budget across albums", async () => {
357
+ const svc = buildGenreService(PSYTRANCE_TREE);
358
+ const out = await svc.getTracksFor({ itemKey: genreLoc, limit: 3 });
359
+ // One track from each album rather than three from Album One.
360
+ assert.deepEqual(out.tracks.map((t) => t.title), ["A1", "B1", "C1"]);
361
+ });
362
+ test("a genre track re-resolves to a live key for playback (album→track)", async () => {
363
+ const svc = buildGenreService(PSYTRANCE_TREE);
364
+ const out = await svc.getTracksFor({ itemKey: genreLoc, limit: 10 });
365
+ // "B2" is album index 1, track index 1.
366
+ const b2 = out.tracks.find((t) => t.title === "B2");
367
+ const loc = decodeLocator(b2.itemKey);
368
+ assert.ok(isGenreLocator(loc) && loc.a === 1 && loc.t === 1);
369
+ const opened = await svc.openTrackForPlayback(loc);
370
+ assert.equal(opened.item?.title, "B2");
371
+ });
372
+ test("limit caps the number of returned tracks", async () => {
373
+ const svc = buildService({
374
+ albumbig: {
375
+ title: "Big",
376
+ items: Array.from({ length: 20 }, (_, i) => track(`Track ${i}`, `t:${i}`)),
377
+ },
378
+ });
379
+ const out = await svc.getTracksFor({ itemKey: loc("albumbig"), limit: 5 });
380
+ assert.equal(out.tracks.length, 5);
381
+ });
382
+ //# sourceMappingURL=TrackExpansionService.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TrackExpansionService.test.js","sourceRoot":"","sources":["../src/TrackExpansionService.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import { RoonClient } from "./RoonClient.js";
2
+ import { type RoonCallLogger } from "./logger.js";
3
+ import { type RoonZone } from "./types.js";
4
+ export interface ZoneResolutionAmbiguous {
5
+ ambiguous: true;
6
+ candidates: RoonZone[];
7
+ }
8
+ export interface ResolvedZoneTarget {
9
+ /** Id to pass to Roon browse actions — the caller's id (which may be an
10
+ * output id) when explicit, else the resolved zone's id. */
11
+ targetId: string;
12
+ zone: RoonZone;
13
+ }
14
+ /** Reads and ranks Roon zones via the Transport service. */
15
+ export declare class ZoneService {
16
+ private readonly roon;
17
+ private readonly logger;
18
+ /** Optional configured default (zone/output id or display-name substring),
19
+ * used when a playback call omits its zoneId. */
20
+ private readonly defaultZone?;
21
+ constructor(roon: RoonClient, logger?: RoonCallLogger,
22
+ /** Optional configured default (zone/output id or display-name substring),
23
+ * used when a playback call omits its zoneId. */
24
+ defaultZone?: string | undefined);
25
+ /** List all playable zones exposed by the paired Core. */
26
+ listZones(): Promise<RoonZone[]>;
27
+ /**
28
+ * Find a zone by its zone id or by any of its output ids. Roon playback
29
+ * actions accept either, so callers can pass whichever `list_zones` exposed.
30
+ */
31
+ findZone(idOrOutput: string): Promise<RoonZone | undefined>;
32
+ /**
33
+ * Resolve the zone a playback call should target.
34
+ * - Explicit id (zone or output): used as-is; unknown id → `ZONE_NOT_FOUND`.
35
+ * - Omitted: fall back to the configured default (matched as an id first,
36
+ * then as a display name), else the single/Office/playing heuristics.
37
+ * Returns `ZONE_AMBIGUOUS` when no single zone can be chosen.
38
+ */
39
+ resolveTarget(explicitId?: string): Promise<ResolvedZoneTarget>;
40
+ /** Best-effort "now playing" one-liner for a zone or output id. */
41
+ nowPlayingFor(idOrOutput: string): Promise<string | undefined>;
42
+ private findRawZone;
43
+ private getZonesBody;
44
+ /**
45
+ * Resolve a single zone, applying the plan's resolution rules:
46
+ * 1. Exact, then fuzzy, name match when a preferred name is given.
47
+ * 2. Prefer a zone named "Office".
48
+ * 3. Prefer a currently playing zone.
49
+ * Returns ambiguity when several plausible zones remain.
50
+ */
51
+ resolveZone(preferredName?: string): Promise<RoonZone | ZoneResolutionAmbiguous>;
52
+ }
@@ -0,0 +1,139 @@
1
+ import { silentLogger } from "./logger.js";
2
+ import { RoonMcpError } from "./types.js";
3
+ function mapState(state) {
4
+ switch (state) {
5
+ case "playing":
6
+ case "paused":
7
+ case "loading":
8
+ case "stopped":
9
+ return state;
10
+ default:
11
+ return "unknown";
12
+ }
13
+ }
14
+ function toRoonZone(zone) {
15
+ return {
16
+ zoneId: zone.zone_id,
17
+ displayName: zone.display_name,
18
+ state: mapState(zone.state),
19
+ outputIds: (zone.outputs ?? []).map((o) => o.output_id),
20
+ // A zone currently playing is the most natural default target.
21
+ isDefaultCandidate: zone.state === "playing",
22
+ };
23
+ }
24
+ /** Reads and ranks Roon zones via the Transport service. */
25
+ export class ZoneService {
26
+ roon;
27
+ logger;
28
+ defaultZone;
29
+ constructor(roon, logger = silentLogger,
30
+ /** Optional configured default (zone/output id or display-name substring),
31
+ * used when a playback call omits its zoneId. */
32
+ defaultZone) {
33
+ this.roon = roon;
34
+ this.logger = logger;
35
+ this.defaultZone = defaultZone;
36
+ }
37
+ /** List all playable zones exposed by the paired Core. */
38
+ async listZones() {
39
+ const body = await this.getZonesBody();
40
+ return (body.zones ?? []).map(toRoonZone);
41
+ }
42
+ /**
43
+ * Find a zone by its zone id or by any of its output ids. Roon playback
44
+ * actions accept either, so callers can pass whichever `list_zones` exposed.
45
+ */
46
+ async findZone(idOrOutput) {
47
+ const raw = await this.findRawZone(idOrOutput);
48
+ return raw ? toRoonZone(raw) : undefined;
49
+ }
50
+ /**
51
+ * Resolve the zone a playback call should target.
52
+ * - Explicit id (zone or output): used as-is; unknown id → `ZONE_NOT_FOUND`.
53
+ * - Omitted: fall back to the configured default (matched as an id first,
54
+ * then as a display name), else the single/Office/playing heuristics.
55
+ * Returns `ZONE_AMBIGUOUS` when no single zone can be chosen.
56
+ */
57
+ async resolveTarget(explicitId) {
58
+ if (explicitId) {
59
+ const zone = await this.findZone(explicitId);
60
+ if (!zone) {
61
+ throw new RoonMcpError("ZONE_NOT_FOUND", `No zone or output matches id "${explicitId}". Call list_zones for current ids.`);
62
+ }
63
+ // Preserve the caller's id (it may be an output id) as the action target.
64
+ return { targetId: explicitId, zone };
65
+ }
66
+ // A configured default may be a zone/output id…
67
+ if (this.defaultZone) {
68
+ const byId = await this.findZone(this.defaultZone);
69
+ if (byId)
70
+ return { targetId: byId.zoneId, zone: byId };
71
+ }
72
+ // …or a display name; otherwise apply the resolution heuristics.
73
+ const resolved = await this.resolveZone(this.defaultZone);
74
+ if ("ambiguous" in resolved) {
75
+ throw new RoonMcpError("ZONE_AMBIGUOUS", this.defaultZone
76
+ ? `Default zone "${this.defaultZone}" matches multiple zones; pass an explicit zoneId.`
77
+ : "Multiple zones available; set ROON_DEFAULT_ZONE or pass an explicit zoneId.", { candidates: resolved.candidates.map((z) => ({ zoneId: z.zoneId, displayName: z.displayName })) });
78
+ }
79
+ return { targetId: resolved.zoneId, zone: resolved };
80
+ }
81
+ /** Best-effort "now playing" one-liner for a zone or output id. */
82
+ async nowPlayingFor(idOrOutput) {
83
+ const np = (await this.findRawZone(idOrOutput))?.now_playing;
84
+ return (np?.two_line?.line1 ?? np?.one_line?.line1 ?? np?.three_line?.line1 ?? undefined);
85
+ }
86
+ async findRawZone(idOrOutput) {
87
+ const body = await this.getZonesBody();
88
+ return (body.zones ?? []).find((z) => z.zone_id === idOrOutput ||
89
+ (z.outputs ?? []).some((o) => o.output_id === idOrOutput));
90
+ }
91
+ async getZonesBody() {
92
+ await this.roon.waitForCore();
93
+ const transport = this.roon.getTransport();
94
+ return this.logger.call("get_zones", {}, () => new Promise((resolve, reject) => {
95
+ transport.get_zones((error, result) => {
96
+ if (error) {
97
+ reject(new RoonMcpError("BROWSE_FAILED", `get_zones failed: ${error}`));
98
+ return;
99
+ }
100
+ resolve(result);
101
+ });
102
+ }), (body) => ({ zones: body.zones?.length ?? 0 }));
103
+ }
104
+ /**
105
+ * Resolve a single zone, applying the plan's resolution rules:
106
+ * 1. Exact, then fuzzy, name match when a preferred name is given.
107
+ * 2. Prefer a zone named "Office".
108
+ * 3. Prefer a currently playing zone.
109
+ * Returns ambiguity when several plausible zones remain.
110
+ */
111
+ async resolveZone(preferredName) {
112
+ const zones = await this.listZones();
113
+ if (zones.length === 0) {
114
+ throw new RoonMcpError("ZONE_NOT_FOUND", "No zones are available on the Core.");
115
+ }
116
+ if (zones.length === 1)
117
+ return zones[0];
118
+ if (preferredName) {
119
+ const needle = preferredName.trim().toLowerCase();
120
+ const exact = zones.filter((z) => z.displayName.toLowerCase() === needle);
121
+ if (exact.length === 1)
122
+ return exact[0];
123
+ const fuzzy = zones.filter((z) => z.displayName.toLowerCase().includes(needle));
124
+ if (fuzzy.length === 1)
125
+ return fuzzy[0];
126
+ if (fuzzy.length > 1)
127
+ return { ambiguous: true, candidates: fuzzy };
128
+ throw new RoonMcpError("ZONE_NOT_FOUND", `No zone matches "${preferredName}".`, { available: zones.map((z) => z.displayName) });
129
+ }
130
+ const office = zones.filter((z) => z.displayName.toLowerCase().includes("office"));
131
+ if (office.length === 1)
132
+ return office[0];
133
+ const playing = zones.filter((z) => z.state === "playing");
134
+ if (playing.length === 1)
135
+ return playing[0];
136
+ return { ambiguous: true, candidates: zones };
137
+ }
138
+ }
139
+ //# sourceMappingURL=ZoneService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZoneService.js","sourceRoot":"","sources":["../src/ZoneService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};