@raphiiko/wavelink-cli 0.0.1

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/src/index.ts ADDED
@@ -0,0 +1,671 @@
1
+ #!/usr/bin/env node
2
+ import { WaveLinkClient } from "@raphiiko/wavelink-ts";
3
+ import { Argument, Command } from "commander";
4
+
5
+ // Type definitions for finder results
6
+ type MixInfo = { id: string; name: string };
7
+ type OutputDeviceInfo = { deviceId: string; outputId: string; currentMixId: string };
8
+ type OutputInfo = OutputDeviceInfo & { level: number; isMuted: boolean };
9
+ type InputInfo = { deviceId: string; inputId: string; gain: number; isMuted: boolean };
10
+ type ChannelInfo = { id: string; name: string };
11
+
12
+ // Utility functions
13
+ function exitWithError(message: string): never {
14
+ console.error(`Error: ${message}`);
15
+ process.exit(1);
16
+ }
17
+
18
+ function parsePercent(value: string, name: string): number {
19
+ const percent = parseInt(value, 10);
20
+ if (isNaN(percent) || percent < 0 || percent > 100) {
21
+ exitWithError(`${name} must be a number between 0 and 100`);
22
+ }
23
+ return percent;
24
+ }
25
+
26
+ function formatPercent(value: number): string {
27
+ return `${(value * 100).toFixed(0)}%`;
28
+ }
29
+
30
+ function formatMuted(isMuted: boolean): string {
31
+ return isMuted ? "Yes" : "No";
32
+ }
33
+
34
+ // Finder functions
35
+ async function findMixByIdOrName(
36
+ client: WaveLinkClient,
37
+ idOrName: string
38
+ ): Promise<MixInfo | null> {
39
+ const { mixes } = await client.getMixes();
40
+ const lowerIdOrName = idOrName.toLowerCase();
41
+ const mix = mixes.find(
42
+ (m) => m.id.toLowerCase() === lowerIdOrName || m.name.toLowerCase() === lowerIdOrName
43
+ );
44
+ return mix ? { id: mix.id, name: mix.name } : null;
45
+ }
46
+
47
+ async function findOutputDeviceById(
48
+ client: WaveLinkClient,
49
+ deviceId: string
50
+ ): Promise<OutputDeviceInfo | null> {
51
+ const { outputDevices } = await client.getOutputDevices();
52
+ const device = outputDevices.find((d) => d.id === deviceId);
53
+ const output = device?.outputs[0];
54
+ if (!output) return null;
55
+ return { deviceId: device.id, outputId: output.id, currentMixId: output.mixId };
56
+ }
57
+
58
+ async function findOutputById(
59
+ client: WaveLinkClient,
60
+ outputId: string
61
+ ): Promise<OutputInfo | null> {
62
+ const { outputDevices } = await client.getOutputDevices();
63
+ for (const device of outputDevices) {
64
+ const output = device.outputs.find((o) => o.id === outputId);
65
+ if (output) {
66
+ return {
67
+ deviceId: device.id,
68
+ outputId: output.id,
69
+ currentMixId: output.mixId,
70
+ level: output.level,
71
+ isMuted: output.isMuted,
72
+ };
73
+ }
74
+ }
75
+ return null;
76
+ }
77
+
78
+ async function findInputById(client: WaveLinkClient, inputId: string): Promise<InputInfo | null> {
79
+ const { inputDevices } = await client.getInputDevices();
80
+ for (const device of inputDevices) {
81
+ const input = device.inputs.find((i) => i.id === inputId);
82
+ if (input) {
83
+ return {
84
+ deviceId: device.id,
85
+ inputId: input.id,
86
+ gain: input.gain.value,
87
+ isMuted: input.isMuted,
88
+ };
89
+ }
90
+ }
91
+ return null;
92
+ }
93
+
94
+ async function findChannelById(
95
+ client: WaveLinkClient,
96
+ channelId: string
97
+ ): Promise<ChannelInfo | null> {
98
+ const { channels } = await client.getChannels();
99
+ const channel = channels.find((c) => c.id === channelId);
100
+ return channel ? { id: channel.id, name: channel.image?.name || channel.id } : null;
101
+ }
102
+
103
+ // Required finder wrappers that exit on not found
104
+ async function requireMix(client: WaveLinkClient, mixId: string): Promise<MixInfo> {
105
+ const mix = await findMixByIdOrName(client, mixId);
106
+ if (!mix) exitWithError(`Mix '${mixId}' not found`);
107
+ return mix;
108
+ }
109
+
110
+ async function requireOutputDevice(
111
+ client: WaveLinkClient,
112
+ deviceId: string
113
+ ): Promise<OutputDeviceInfo> {
114
+ const device = await findOutputDeviceById(client, deviceId);
115
+ if (!device) exitWithError(`Output device '${deviceId}' not found`);
116
+ return device;
117
+ }
118
+
119
+ async function requireOutput(client: WaveLinkClient, outputId: string): Promise<OutputInfo> {
120
+ const output = await findOutputById(client, outputId);
121
+ if (!output) exitWithError(`Output '${outputId}' not found`);
122
+ return output;
123
+ }
124
+
125
+ async function requireInput(client: WaveLinkClient, inputId: string): Promise<InputInfo> {
126
+ const input = await findInputById(client, inputId);
127
+ if (!input) exitWithError(`Input '${inputId}' not found`);
128
+ return input;
129
+ }
130
+
131
+ async function requireChannel(client: WaveLinkClient, channelId: string): Promise<ChannelInfo> {
132
+ const channel = await findChannelById(client, channelId);
133
+ if (!channel) exitWithError(`Channel '${channelId}' not found`);
134
+ return channel;
135
+ }
136
+
137
+ // Operation functions
138
+ async function showApplicationInfo(client: WaveLinkClient): Promise<void> {
139
+ const info = await client.getApplicationInfo();
140
+ console.log("\n=== Wave Link Application Info ===\n");
141
+ console.log(`Application ID: ${info.appID}`);
142
+ console.log(`Name: ${info.name}`);
143
+ console.log(`Interface Revision: ${info.interfaceRevision}`);
144
+ console.log();
145
+ }
146
+
147
+ async function setOutputVolume(
148
+ client: WaveLinkClient,
149
+ outputId: string,
150
+ volumePercent: number
151
+ ): Promise<void> {
152
+ const output = await requireOutput(client, outputId);
153
+ await client.setOutputVolume(output.deviceId, output.outputId, volumePercent / 100);
154
+ console.log(`Successfully set output '${output.outputId}' volume to ${volumePercent}%`);
155
+ }
156
+
157
+ async function setOutputMute(
158
+ client: WaveLinkClient,
159
+ outputId: string,
160
+ isMuted: boolean
161
+ ): Promise<void> {
162
+ const output = await requireOutput(client, outputId);
163
+ await client.setOutputDevice({
164
+ outputDevice: { id: output.deviceId, outputs: [{ id: output.outputId, isMuted }] },
165
+ });
166
+ console.log(`Successfully ${isMuted ? "muted" : "unmuted"} output '${output.outputId}'`);
167
+ }
168
+
169
+ async function setInputGain(
170
+ client: WaveLinkClient,
171
+ inputId: string,
172
+ gainPercent: number
173
+ ): Promise<void> {
174
+ const input = await requireInput(client, inputId);
175
+ await client.setInputGain(input.deviceId, input.inputId, gainPercent / 100);
176
+ console.log(`Successfully set input '${input.inputId}' gain to ${gainPercent}%`);
177
+ }
178
+
179
+ async function setInputMute(
180
+ client: WaveLinkClient,
181
+ inputId: string,
182
+ isMuted: boolean
183
+ ): Promise<void> {
184
+ const input = await requireInput(client, inputId);
185
+ await client.setInputMute(input.deviceId, input.inputId, isMuted);
186
+ console.log(`Successfully ${isMuted ? "muted" : "unmuted"} input '${input.inputId}'`);
187
+ }
188
+
189
+ async function listOutputs(client: WaveLinkClient): Promise<void> {
190
+ const { outputDevices, mainOutput } = await client.getOutputDevices();
191
+ const { mixes } = await client.getMixes();
192
+
193
+ console.log("\n=== Output Devices ===\n");
194
+
195
+ if (outputDevices.length === 0) {
196
+ console.log("No output devices found.");
197
+ return;
198
+ }
199
+
200
+ for (const device of outputDevices) {
201
+ const isMain = device.id === mainOutput ? " (MAIN OUTPUT)" : "";
202
+ console.log(`Device ID: ${device.id}${isMain}`);
203
+
204
+ if (device.outputs.length === 0) {
205
+ console.log(" No outputs available");
206
+ } else {
207
+ for (const output of device.outputs) {
208
+ const mix = mixes.find((m) => m.id === output.mixId);
209
+ console.log(` Output ID: ${output.id}`);
210
+ console.log(` Current Mix: ${mix?.name ?? output.mixId} (${output.mixId})`);
211
+ console.log(` Level: ${formatPercent(output.level)}`);
212
+ console.log(` Muted: ${formatMuted(output.isMuted)}`);
213
+ }
214
+ }
215
+ console.log();
216
+ }
217
+ }
218
+
219
+ async function listMixes(client: WaveLinkClient): Promise<void> {
220
+ const { mixes } = await client.getMixes();
221
+
222
+ console.log("\n=== Mixes ===\n");
223
+
224
+ if (mixes.length === 0) {
225
+ console.log("No mixes found.");
226
+ return;
227
+ }
228
+
229
+ for (const mix of mixes) {
230
+ console.log(`Mix: ${mix.name}`);
231
+ console.log(` ID: ${mix.id}`);
232
+ console.log(` Level: ${formatPercent(mix.level)}`);
233
+ console.log(` Muted: ${formatMuted(mix.isMuted)}`);
234
+ console.log();
235
+ }
236
+ }
237
+
238
+ async function listChannels(client: WaveLinkClient): Promise<void> {
239
+ const { channels } = await client.getChannels();
240
+
241
+ console.log("\n=== Channels ===\n");
242
+
243
+ if (channels.length === 0) {
244
+ console.log("No channels found.");
245
+ return;
246
+ }
247
+
248
+ for (const channel of channels) {
249
+ console.log(`Channel: ${channel.image?.name ?? channel.id}`);
250
+ console.log(` ID: ${channel.id}`);
251
+ console.log(` Type: ${channel.type}`);
252
+ console.log(` Level: ${formatPercent(channel.level)}`);
253
+ console.log(` Muted: ${formatMuted(channel.isMuted)}`);
254
+
255
+ if (channel.apps?.length) {
256
+ console.log(` Apps: ${channel.apps.map((a) => a.name || a.id).join(", ")}`);
257
+ }
258
+
259
+ if (channel.mixes?.length) {
260
+ console.log(" Mix Assignments:");
261
+ for (const mix of channel.mixes) {
262
+ console.log(
263
+ ` ${mix.id}: Level ${formatPercent(mix.level)}, Muted: ${formatMuted(mix.isMuted)}`
264
+ );
265
+ }
266
+ }
267
+ console.log();
268
+ }
269
+ }
270
+
271
+ async function listInputs(client: WaveLinkClient): Promise<void> {
272
+ const { inputDevices } = await client.getInputDevices();
273
+
274
+ console.log("\n=== Input Devices ===\n");
275
+
276
+ if (inputDevices.length === 0) {
277
+ console.log("No input devices found.");
278
+ return;
279
+ }
280
+
281
+ for (const device of inputDevices) {
282
+ console.log(`Device ID: ${device.id}`);
283
+ console.log(` Wave Device: ${formatMuted(device.isWaveDevice)}`);
284
+
285
+ if (device.inputs.length === 0) {
286
+ console.log(" No inputs available");
287
+ } else {
288
+ for (const input of device.inputs) {
289
+ console.log(` Input ID: ${input.id}`);
290
+ console.log(` Gain: ${formatPercent(input.gain.value)} (max: ${input.gain.maxRange})`);
291
+ console.log(` Muted: ${formatMuted(input.isMuted)}`);
292
+ if (input.micPcMix) {
293
+ console.log(` Mic/PC Mix: ${formatPercent(input.micPcMix.value)}`);
294
+ }
295
+ if (input.effects?.length) {
296
+ const effectsList = input.effects
297
+ .map((e) => `${e.id} (${e.isEnabled ? "ON" : "OFF"})`)
298
+ .join(", ");
299
+ console.log(` Effects: ${effectsList}`);
300
+ }
301
+ }
302
+ }
303
+ console.log();
304
+ }
305
+ }
306
+
307
+ async function assignOutputToMix(
308
+ client: WaveLinkClient,
309
+ deviceId: string,
310
+ mixId: string
311
+ ): Promise<void> {
312
+ const mix = await requireMix(client, mixId);
313
+ const device = await requireOutputDevice(client, deviceId);
314
+
315
+ if (device.currentMixId === mix.id) {
316
+ console.log(`Output device '${device.deviceId}' is already assigned to mix '${mix.name}'`);
317
+ return;
318
+ }
319
+
320
+ await client.switchOutputMix(device.deviceId, device.outputId, mix.id);
321
+ console.log(`Successfully assigned output device '${device.deviceId}' to mix '${mix.name}'`);
322
+ }
323
+
324
+ async function unassignOutputDevice(client: WaveLinkClient, deviceId: string): Promise<void> {
325
+ const device = await requireOutputDevice(client, deviceId);
326
+ await client.removeOutputFromMix(device.deviceId, device.outputId);
327
+ console.log(`Successfully unassigned output device '${device.deviceId}'`);
328
+ }
329
+
330
+ async function setSingleOutputForMix(
331
+ client: WaveLinkClient,
332
+ deviceId: string,
333
+ mixId: string
334
+ ): Promise<void> {
335
+ const mix = await requireMix(client, mixId);
336
+ const targetDevice = await requireOutputDevice(client, deviceId);
337
+
338
+ const { outputDevices } = await client.getOutputDevices();
339
+ const { mixes } = await client.getMixes();
340
+
341
+ const otherMix = mixes.find((m) => m.id !== mix.id);
342
+ if (!otherMix) {
343
+ exitWithError(
344
+ "Cannot use 'single' command with only one mix available. At least two mixes are required."
345
+ );
346
+ }
347
+
348
+ let devicesReassigned = 0;
349
+ let targetDeviceAssigned = false;
350
+
351
+ for (const device of outputDevices) {
352
+ for (const output of device.outputs) {
353
+ const isTargetDevice = device.id === targetDevice.deviceId;
354
+
355
+ if (isTargetDevice && output.mixId !== mix.id) {
356
+ await client.switchOutputMix(device.id, output.id, mix.id);
357
+ targetDeviceAssigned = true;
358
+ } else if (!isTargetDevice && output.mixId === mix.id) {
359
+ await client.switchOutputMix(device.id, output.id, otherMix.id);
360
+ devicesReassigned++;
361
+ }
362
+ }
363
+ }
364
+
365
+ if (targetDeviceAssigned && devicesReassigned > 0) {
366
+ console.log(
367
+ `Successfully set '${targetDevice.deviceId}' as the only output for mix '${mix.name}' ` +
368
+ `(reassigned ${devicesReassigned} other device(s) to '${otherMix.name}')`
369
+ );
370
+ } else if (targetDeviceAssigned) {
371
+ console.log(
372
+ `Successfully assigned '${targetDevice.deviceId}' to mix '${mix.name}' ` +
373
+ `(it was already the only device on this mix)`
374
+ );
375
+ } else if (devicesReassigned > 0) {
376
+ console.log(
377
+ `'${targetDevice.deviceId}' was already assigned to mix '${mix.name}'. ` +
378
+ `Reassigned ${devicesReassigned} other device(s) to '${otherMix.name}'`
379
+ );
380
+ } else {
381
+ console.log(`'${targetDevice.deviceId}' is already the only output for mix '${mix.name}'`);
382
+ }
383
+ }
384
+
385
+ async function withClient<T>(action: (client: WaveLinkClient) => Promise<T>): Promise<T> {
386
+ const client = new WaveLinkClient({
387
+ autoReconnect: false,
388
+ });
389
+
390
+ try {
391
+ console.log("Connecting to Wave Link...");
392
+ await client.connect();
393
+ console.log("Connected successfully");
394
+
395
+ return await action(client);
396
+ } catch (error) {
397
+ if (error instanceof Error) {
398
+ console.error(`Error: ${error.message}`);
399
+ } else {
400
+ console.error("An unexpected error occurred");
401
+ }
402
+ process.exit(1);
403
+ } finally {
404
+ client.disconnect();
405
+ }
406
+ }
407
+
408
+ const program = new Command();
409
+
410
+ program
411
+ .name("wavelink-cli")
412
+ .description("Manage Elgato Wave Link via command line")
413
+ .version("1.0.0");
414
+
415
+ // Info command
416
+ program
417
+ .command("info")
418
+ .description("Show Wave Link application information")
419
+ .action(() => withClient(showApplicationInfo));
420
+
421
+ // Output commands
422
+ const outputCmd = program.command("output").description("Manage output devices");
423
+
424
+ outputCmd
425
+ .command("list")
426
+ .description("List all output devices with their IDs and current mix assignments")
427
+ .action(() => withClient(listOutputs));
428
+
429
+ outputCmd
430
+ .command("assign")
431
+ .description("Assign an output device to a specific mix")
432
+ .addArgument(
433
+ new Argument("<output-id>", "ID of the output device (use 'output list' to find IDs)")
434
+ )
435
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
436
+ .action((outputId: string, mixId: string) =>
437
+ withClient((client) => assignOutputToMix(client, outputId, mixId))
438
+ );
439
+
440
+ outputCmd
441
+ .command("unassign")
442
+ .description("Unassign an output device from its current mix")
443
+ .addArgument(new Argument("<output-id>", "ID of the output device"))
444
+ .action((outputId: string) => withClient((client) => unassignOutputDevice(client, outputId)));
445
+
446
+ outputCmd
447
+ .command("set-volume")
448
+ .description("Set output device volume")
449
+ .addArgument(new Argument("<output-id>", "ID of the output device"))
450
+ .addArgument(new Argument("<volume>", "Volume level (0-100)"))
451
+ .action((outputId: string, volume: string) => {
452
+ const volumePercent = parsePercent(volume, "Volume");
453
+ return withClient((client) => setOutputVolume(client, outputId, volumePercent));
454
+ });
455
+
456
+ outputCmd
457
+ .command("mute")
458
+ .description("Mute an output device")
459
+ .addArgument(new Argument("<output-id>", "ID of the output device"))
460
+ .action((outputId: string) => withClient((client) => setOutputMute(client, outputId, true)));
461
+
462
+ outputCmd
463
+ .command("unmute")
464
+ .description("Unmute an output device")
465
+ .addArgument(new Argument("<output-id>", "ID of the output device"))
466
+ .action((outputId: string) => withClient((client) => setOutputMute(client, outputId, false)));
467
+
468
+ // Mix commands
469
+ const mixCmd = program.command("mix").description("Manage mixes");
470
+
471
+ mixCmd
472
+ .command("list")
473
+ .description("List all mixes with their IDs and names")
474
+ .action(() => withClient(listMixes));
475
+
476
+ mixCmd
477
+ .command("set-output")
478
+ .description(
479
+ "Set a device as the ONLY output for a mix (removes all other outputs from that mix)"
480
+ )
481
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
482
+ .addArgument(new Argument("<output-id>", "ID of the output device"))
483
+ .action((mixId: string, outputId: string) =>
484
+ withClient((client) => setSingleOutputForMix(client, outputId, mixId))
485
+ );
486
+
487
+ mixCmd
488
+ .command("set-volume")
489
+ .description("Set mix master volume")
490
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
491
+ .addArgument(new Argument("<volume>", "Volume level (0-100)"))
492
+ .action((mixId: string, volume: string) => {
493
+ const volumePercent = parsePercent(volume, "Volume");
494
+ return withClient(async (client) => {
495
+ const mix = await requireMix(client, mixId);
496
+ await client.setMixVolume(mix.id, volumePercent / 100);
497
+ console.log(`Successfully set mix '${mix.name}' volume to ${volumePercent}%`);
498
+ });
499
+ });
500
+
501
+ mixCmd
502
+ .command("mute")
503
+ .description("Mute a mix")
504
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
505
+ .action((mixId: string) =>
506
+ withClient(async (client) => {
507
+ const mix = await requireMix(client, mixId);
508
+ await client.setMixMute(mix.id, true);
509
+ console.log(`Successfully muted mix '${mix.name}'`);
510
+ })
511
+ );
512
+
513
+ mixCmd
514
+ .command("unmute")
515
+ .description("Unmute a mix")
516
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
517
+ .action((mixId: string) =>
518
+ withClient(async (client) => {
519
+ const mix = await requireMix(client, mixId);
520
+ await client.setMixMute(mix.id, false);
521
+ console.log(`Successfully unmuted mix '${mix.name}'`);
522
+ })
523
+ );
524
+
525
+ mixCmd
526
+ .command("toggle-mute")
527
+ .description("Toggle mix mute state")
528
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
529
+ .action((mixId: string) =>
530
+ withClient(async (client) => {
531
+ const mix = await requireMix(client, mixId);
532
+ await client.toggleMixMute(mix.id);
533
+ console.log(`Successfully toggled mute for mix '${mix.name}'`);
534
+ })
535
+ );
536
+
537
+ // Channel commands
538
+ const channelCmd = program.command("channel").description("Manage channels");
539
+
540
+ channelCmd
541
+ .command("list")
542
+ .description("List all channels with their IDs and names")
543
+ .action(() => withClient(listChannels));
544
+
545
+ channelCmd
546
+ .command("set-volume")
547
+ .description("Set channel master volume")
548
+ .addArgument(new Argument("<channel-id>", "ID of the channel"))
549
+ .addArgument(new Argument("<volume>", "Volume level (0-100)"))
550
+ .action((channelId: string, volume: string) => {
551
+ const volumePercent = parsePercent(volume, "Volume");
552
+ return withClient(async (client) => {
553
+ const channel = await requireChannel(client, channelId);
554
+ await client.setChannelVolume(channel.id, volumePercent / 100);
555
+ console.log(`Successfully set channel '${channel.name}' volume to ${volumePercent}%`);
556
+ });
557
+ });
558
+
559
+ channelCmd
560
+ .command("mute")
561
+ .description("Mute a channel")
562
+ .addArgument(new Argument("<channel-id>", "ID of the channel"))
563
+ .action((channelId: string) =>
564
+ withClient(async (client) => {
565
+ const channel = await requireChannel(client, channelId);
566
+ await client.setChannelMute(channel.id, true);
567
+ console.log(`Successfully muted channel '${channel.name}'`);
568
+ })
569
+ );
570
+
571
+ channelCmd
572
+ .command("unmute")
573
+ .description("Unmute a channel")
574
+ .addArgument(new Argument("<channel-id>", "ID of the channel"))
575
+ .action((channelId: string) =>
576
+ withClient(async (client) => {
577
+ const channel = await requireChannel(client, channelId);
578
+ await client.setChannelMute(channel.id, false);
579
+ console.log(`Successfully unmuted channel '${channel.name}'`);
580
+ })
581
+ );
582
+
583
+ channelCmd
584
+ .command("toggle-mute")
585
+ .description("Toggle channel mute state")
586
+ .addArgument(new Argument("<channel-id>", "ID of the channel"))
587
+ .action((channelId: string) =>
588
+ withClient(async (client) => {
589
+ const channel = await requireChannel(client, channelId);
590
+ await client.toggleChannelMute(channel.id);
591
+ console.log(`Successfully toggled mute for channel '${channel.name}'`);
592
+ })
593
+ );
594
+
595
+ channelCmd
596
+ .command("set-mix-volume")
597
+ .description("Set channel volume in a specific mix")
598
+ .addArgument(new Argument("<channel-id>", "ID of the channel"))
599
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
600
+ .addArgument(new Argument("<volume>", "Volume level (0-100)"))
601
+ .action((channelId: string, mixId: string, volume: string) => {
602
+ const volumePercent = parsePercent(volume, "Volume");
603
+ return withClient(async (client) => {
604
+ const channel = await requireChannel(client, channelId);
605
+ const mix = await requireMix(client, mixId);
606
+ await client.setChannelMixVolume(channel.id, mix.id, volumePercent / 100);
607
+ console.log(
608
+ `Successfully set channel '${channel.name}' volume to ${volumePercent}% in mix '${mix.name}'`
609
+ );
610
+ });
611
+ });
612
+
613
+ channelCmd
614
+ .command("mute-in-mix")
615
+ .description("Mute a channel in a specific mix")
616
+ .addArgument(new Argument("<channel-id>", "ID of the channel"))
617
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
618
+ .action((channelId: string, mixId: string) =>
619
+ withClient(async (client) => {
620
+ const channel = await requireChannel(client, channelId);
621
+ const mix = await requireMix(client, mixId);
622
+ await client.setChannelMixMute(channel.id, mix.id, true);
623
+ console.log(`Successfully muted channel '${channel.name}' in mix '${mix.name}'`);
624
+ })
625
+ );
626
+
627
+ channelCmd
628
+ .command("unmute-in-mix")
629
+ .description("Unmute a channel in a specific mix")
630
+ .addArgument(new Argument("<channel-id>", "ID of the channel"))
631
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
632
+ .action((channelId: string, mixId: string) =>
633
+ withClient(async (client) => {
634
+ const channel = await requireChannel(client, channelId);
635
+ const mix = await requireMix(client, mixId);
636
+ await client.setChannelMixMute(channel.id, mix.id, false);
637
+ console.log(`Successfully unmuted channel '${channel.name}' in mix '${mix.name}'`);
638
+ })
639
+ );
640
+
641
+ // Input commands
642
+ const inputCmd = program.command("input").description("Manage input devices");
643
+
644
+ inputCmd
645
+ .command("list")
646
+ .description("List all input devices with their IDs")
647
+ .action(() => withClient(listInputs));
648
+
649
+ inputCmd
650
+ .command("set-gain")
651
+ .description("Set input device gain")
652
+ .addArgument(new Argument("<input-id>", "ID of the input device"))
653
+ .addArgument(new Argument("<gain>", "Gain level (0-100)"))
654
+ .action((inputId: string, gain: string) => {
655
+ const gainPercent = parsePercent(gain, "Gain");
656
+ return withClient((client) => setInputGain(client, inputId, gainPercent));
657
+ });
658
+
659
+ inputCmd
660
+ .command("mute")
661
+ .description("Mute an input device")
662
+ .addArgument(new Argument("<input-id>", "ID of the input device"))
663
+ .action((inputId: string) => withClient((client) => setInputMute(client, inputId, true)));
664
+
665
+ inputCmd
666
+ .command("unmute")
667
+ .description("Unmute an input device")
668
+ .addArgument(new Argument("<input-id>", "ID of the input device"))
669
+ .action((inputId: string) => withClient((client) => setInputMute(client, inputId, false)));
670
+
671
+ await program.parseAsync();