@raphiiko/wavelink-cli 0.0.5 → 0.0.7

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/package.json CHANGED
@@ -1,58 +1,58 @@
1
- {
2
- "name": "@raphiiko/wavelink-cli",
3
- "version": "0.0.5",
4
- "description": "Command line interface for Elgato Wave Link 3.0",
5
- "author": "Raphiiko",
6
- "license": "MIT",
7
- "homepage": "https://github.com/Raphiiko/wavelink-cli",
8
- "repository": {
9
- "type": "git",
10
- "url": "https://github.com/Raphiiko/wavelink-cli.git"
11
- },
12
- "bugs": {
13
- "url": "https://github.com/Raphiiko/wavelink-cli/issues"
14
- },
15
- "keywords": [
16
- "wavelink",
17
- "cli",
18
- "typescript"
19
- ],
20
- "main": "dist/index.js",
21
- "module": "dist/index.js",
22
- "type": "module",
23
- "bin": {
24
- "wavelink-cli": "dist/index.js"
25
- },
26
- "files": [
27
- "dist",
28
- "src"
29
- ],
30
- "scripts": {
31
- "build": "bun build ./src/index.ts --target=node --outfile dist/index.js",
32
- "start": "bun run src/index.ts",
33
- "format": "prettier --write \"src/**/*.ts\"",
34
- "format:check": "prettier --check \"src/**/*.ts\"",
35
- "lint": "eslint src/**/*.ts",
36
- "lint:fix": "eslint src/**/*.ts --fix",
37
- "typecheck": "tsc --noEmit",
38
- "check": "bun run format:check && bun run lint && bun run typecheck",
39
- "prepublishOnly": "bun run build && bun run check"
40
- },
41
- "devDependencies": {
42
- "@types/bun": "latest",
43
- "@typescript-eslint/eslint-plugin": "^8.52.0",
44
- "@typescript-eslint/parser": "^8.52.0",
45
- "eslint": "^9.39.2",
46
- "eslint-config-prettier": "^10.1.8",
47
- "eslint-plugin-prettier": "^5.5.4",
48
- "prettier": "^3.7.4",
49
- "typescript": "^5"
50
- },
51
- "peerDependencies": {
52
- "typescript": "^5"
53
- },
54
- "dependencies": {
55
- "@raphiiko/wavelink-ts": "^1.1.0",
56
- "commander": "^14.0.2"
57
- }
1
+ {
2
+ "name": "@raphiiko/wavelink-cli",
3
+ "version": "0.0.7",
4
+ "description": "Command line interface for Elgato Wave Link 3.0",
5
+ "author": "Raphiiko",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/Raphiiko/wavelink-cli",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/Raphiiko/wavelink-cli.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/Raphiiko/wavelink-cli/issues"
14
+ },
15
+ "keywords": [
16
+ "wavelink",
17
+ "cli",
18
+ "typescript"
19
+ ],
20
+ "main": "dist/index.js",
21
+ "module": "dist/index.js",
22
+ "type": "module",
23
+ "bin": {
24
+ "wavelink-cli": "dist/index.js"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "src"
29
+ ],
30
+ "scripts": {
31
+ "build": "bun run scripts/build.js",
32
+ "start": "bun run src/index.ts",
33
+ "format": "prettier --write \"src/**/*.ts\"",
34
+ "format:check": "prettier --check \"src/**/*.ts\"",
35
+ "lint": "eslint src/**/*.ts",
36
+ "lint:fix": "eslint src/**/*.ts --fix",
37
+ "typecheck": "tsc --noEmit",
38
+ "check": "bun run format:check && bun run lint && bun run typecheck",
39
+ "prepublishOnly": "bun run build && bun run check"
40
+ },
41
+ "devDependencies": {
42
+ "@types/bun": "latest",
43
+ "@typescript-eslint/eslint-plugin": "^8.52.0",
44
+ "@typescript-eslint/parser": "^8.52.0",
45
+ "eslint": "^9.39.2",
46
+ "eslint-config-prettier": "^10.1.8",
47
+ "eslint-plugin-prettier": "^5.5.4",
48
+ "prettier": "^3.7.4",
49
+ "typescript": "^5"
50
+ },
51
+ "peerDependencies": {
52
+ "typescript": "^5"
53
+ },
54
+ "dependencies": {
55
+ "@raphiiko/wavelink-ts": "^1.3.0",
56
+ "commander": "^14.0.2"
57
+ }
58
58
  }
@@ -0,0 +1,243 @@
1
+ import { WaveLinkClient } from "@raphiiko/wavelink-ts";
2
+ import { Argument, Command } from "commander";
3
+ import { withClient } from "../services/client.js";
4
+ import { requireChannel, requireMix } from "../services/finders.js";
5
+ import { formatPercent, formatMuted, getChannelName } from "../utils/format.js";
6
+ import { parsePercent } from "../utils/validation.js";
7
+ import { exitWithError } from "../utils/error.js";
8
+
9
+ export async function listChannels(client: WaveLinkClient): Promise<void> {
10
+ const { channels } = await client.getChannels();
11
+
12
+ console.log("\n=== Channels ===\n");
13
+
14
+ if (channels.length === 0) {
15
+ console.log("No channels found.");
16
+ return;
17
+ }
18
+
19
+ for (const channel of channels) {
20
+ const channelName = getChannelName(channel);
21
+ console.log(`Channel: ${channelName}`);
22
+ console.log(` ID: ${channel.id}`);
23
+ console.log(` Type: ${channel.type}`);
24
+ console.log(` Level: ${formatPercent(channel.level)}`);
25
+ console.log(` Muted: ${formatMuted(channel.isMuted)}`);
26
+
27
+ if (channel.apps?.length) {
28
+ console.log(` Apps: ${channel.apps.map((a) => a.name || a.id).join(", ")}`);
29
+ }
30
+
31
+ if (channel.mixes?.length) {
32
+ console.log(" Mix Assignments:");
33
+ for (const mix of channel.mixes) {
34
+ console.log(
35
+ ` ${mix.id}: Level ${formatPercent(mix.level)}, Muted: ${formatMuted(mix.isMuted)}`
36
+ );
37
+ }
38
+ }
39
+ console.log();
40
+ }
41
+ }
42
+
43
+ export function registerChannelCommands(program: Command): void {
44
+ const channelCmd = program.command("channel").description("Manage channels");
45
+
46
+ channelCmd
47
+ .command("list")
48
+ .description("List all channels with their IDs and names")
49
+ .action(() => withClient(listChannels));
50
+
51
+ channelCmd
52
+ .command("set-volume")
53
+ .description("Set channel master volume")
54
+ .addArgument(
55
+ new Argument("<channel-id-or-name>", "ID or name of the channel (case-insensitive)")
56
+ )
57
+ .addArgument(new Argument("<volume>", "Volume level (0-100)"))
58
+ .action((channelId: string, volume: string) => {
59
+ const volumePercent = parsePercent(volume, "Volume");
60
+ return withClient(async (client) => {
61
+ const channel = await requireChannel(client, channelId);
62
+ await client.setChannelVolume(channel.id, volumePercent / 100);
63
+ console.log(`Successfully set channel '${channel.name}' volume to ${volumePercent}%`);
64
+ });
65
+ });
66
+
67
+ channelCmd
68
+ .command("mute")
69
+ .description("Mute a channel")
70
+ .addArgument(
71
+ new Argument("<channel-id-or-name>", "ID or name of the channel (case-insensitive)")
72
+ )
73
+ .action((channelId: string) =>
74
+ withClient(async (client) => {
75
+ const channel = await requireChannel(client, channelId);
76
+ await client.setChannelMute(channel.id, true);
77
+ console.log(`Successfully muted channel '${channel.name}'`);
78
+ })
79
+ );
80
+
81
+ channelCmd
82
+ .command("unmute")
83
+ .description("Unmute a channel")
84
+ .addArgument(
85
+ new Argument("<channel-id-or-name>", "ID or name of the channel (case-insensitive)")
86
+ )
87
+ .action((channelId: string) =>
88
+ withClient(async (client) => {
89
+ const channel = await requireChannel(client, channelId);
90
+ await client.setChannelMute(channel.id, false);
91
+ console.log(`Successfully unmuted channel '${channel.name}'`);
92
+ })
93
+ );
94
+
95
+ channelCmd
96
+ .command("toggle-mute")
97
+ .description("Toggle channel mute state")
98
+ .addArgument(
99
+ new Argument("<channel-id-or-name>", "ID or name of the channel (case-insensitive)")
100
+ )
101
+ .action((channelId: string) =>
102
+ withClient(async (client) => {
103
+ const channel = await requireChannel(client, channelId);
104
+ await client.toggleChannelMute(channel.id);
105
+ console.log(`Successfully toggled mute for channel '${channel.name}'`);
106
+ })
107
+ );
108
+
109
+ channelCmd
110
+ .command("set-mix-volume")
111
+ .description("Set channel volume in a specific mix")
112
+ .addArgument(
113
+ new Argument("<channel-id-or-name>", "ID or name of the channel (case-insensitive)")
114
+ )
115
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
116
+ .addArgument(new Argument("<volume>", "Volume level (0-100)"))
117
+ .action((channelId: string, mixId: string, volume: string) => {
118
+ const volumePercent = parsePercent(volume, "Volume");
119
+ return withClient(async (client) => {
120
+ const channel = await requireChannel(client, channelId);
121
+ const mix = await requireMix(client, mixId);
122
+ await client.setChannelMixVolume(channel.id, mix.id, volumePercent / 100);
123
+ console.log(
124
+ `Successfully set channel '${channel.name}' volume to ${volumePercent}% in mix '${mix.name}'`
125
+ );
126
+ });
127
+ });
128
+
129
+ channelCmd
130
+ .command("mute-in-mix")
131
+ .description("Mute a channel in a specific mix")
132
+ .addArgument(
133
+ new Argument("<channel-id-or-name>", "ID or name of the channel (case-insensitive)")
134
+ )
135
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
136
+ .action((channelId: string, mixId: string) =>
137
+ withClient(async (client) => {
138
+ const channel = await requireChannel(client, channelId);
139
+ const mix = await requireMix(client, mixId);
140
+ await client.setChannelMixMute(channel.id, mix.id, true);
141
+ console.log(`Successfully muted channel '${channel.name}' in mix '${mix.name}'`);
142
+ })
143
+ );
144
+
145
+ channelCmd
146
+ .command("unmute-in-mix")
147
+ .description("Unmute a channel in a specific mix")
148
+ .addArgument(
149
+ new Argument("<channel-id-or-name>", "ID or name of the channel (case-insensitive)")
150
+ )
151
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
152
+ .action((channelId: string, mixId: string) =>
153
+ withClient(async (client) => {
154
+ const channel = await requireChannel(client, channelId);
155
+ const mix = await requireMix(client, mixId);
156
+ await client.setChannelMixMute(channel.id, mix.id, false);
157
+ console.log(`Successfully unmuted channel '${channel.name}' in mix '${mix.name}'`);
158
+ })
159
+ );
160
+
161
+ channelCmd
162
+ .command("toggle-mute-in-mix")
163
+ .description("Toggle channel mute state in a specific mix")
164
+ .addArgument(
165
+ new Argument("<channel-id-or-name>", "ID or name of the channel (case-insensitive)")
166
+ )
167
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
168
+ .action((channelId: string, mixId: string) =>
169
+ withClient(async (client) => {
170
+ const channel = await requireChannel(client, channelId);
171
+ const mix = await requireMix(client, mixId);
172
+
173
+ // Get full channel info to check mix assignment
174
+ const { channels } = await client.getChannels();
175
+ const fullChannel = channels.find((c) => c.id === channel.id);
176
+
177
+ if (!fullChannel) exitWithError(`Channel '${channelId}' not found`);
178
+
179
+ const mixAssignment = fullChannel.mixes?.find((m) => m.id === mix.id);
180
+
181
+ if (!mixAssignment) {
182
+ exitWithError(`Channel '${channel.name}' is not available in mix '${mix.name}'`);
183
+ }
184
+
185
+ const newMuted = !mixAssignment.isMuted;
186
+ await client.setChannelMixMute(channel.id, mix.id, newMuted);
187
+ console.log(`Successfully toggled mute for channel '${channel.name}' in mix '${mix.name}'`);
188
+ })
189
+ );
190
+
191
+ channelCmd
192
+ .command("isolate")
193
+ .description("Mute all channels in a mix except for the specified one")
194
+ .addArgument(
195
+ new Argument(
196
+ "<channel-id-or-name>",
197
+ "ID or name of the channel to isolate (case-insensitive)"
198
+ )
199
+ )
200
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
201
+ .action((channelId: string, mixId: string) =>
202
+ withClient(async (client) => {
203
+ const mix = await requireMix(client, mixId);
204
+ const targetChannel = await requireChannel(client, channelId);
205
+ const { channels } = await client.getChannels();
206
+
207
+ let mutedCount = 0;
208
+ let alreadyMutedCount = 0;
209
+
210
+ for (const channel of channels) {
211
+ // Find the channel's assignment for this mix
212
+ const assignment = channel.mixes?.find((m) => m.id === mix.id);
213
+
214
+ // If the channel isn't in this mix (unexpected for Wave Link, but possible in API types), skip
215
+ if (!assignment) continue;
216
+
217
+ if (channel.id === targetChannel.id) {
218
+ // This is the chosen channel: Unmute it
219
+ if (assignment.isMuted) {
220
+ await client.setChannelMixMute(channel.id, mix.id, false);
221
+ console.log(`Unmuted target channel '${getChannelName(channel)}'`);
222
+ } else {
223
+ console.log(`Target channel '${getChannelName(channel)}' is already unmuted`);
224
+ }
225
+ } else {
226
+ // This is NOT the chosen channel: Mute it
227
+ if (!assignment.isMuted) {
228
+ await client.setChannelMixMute(channel.id, mix.id, true);
229
+ mutedCount++;
230
+ } else {
231
+ alreadyMutedCount++;
232
+ }
233
+ }
234
+ }
235
+
236
+ console.log(
237
+ `SUCCESS: Isolated '${targetChannel.name}' in mix '${mix.name}'.\n` +
238
+ ` - Muted ${mutedCount} other channels.\n` +
239
+ ` - ${alreadyMutedCount} channels were already muted.`
240
+ );
241
+ })
242
+ );
243
+ }
@@ -0,0 +1,19 @@
1
+ import { WaveLinkClient } from "@raphiiko/wavelink-ts";
2
+ import { Command } from "commander";
3
+ import { withClient } from "../services/client.js";
4
+
5
+ export async function showApplicationInfo(client: WaveLinkClient): Promise<void> {
6
+ const info = await client.getApplicationInfo();
7
+ console.log("\n=== Wave Link Application Info ===\n");
8
+ console.log(`Application ID: ${info.appID}`);
9
+ console.log(`Name: ${info.name}`);
10
+ console.log(`Interface Revision: ${info.interfaceRevision}`);
11
+ console.log();
12
+ }
13
+
14
+ export function registerInfoCommand(program: Command): void {
15
+ program
16
+ .command("info")
17
+ .description("Show Wave Link application information")
18
+ .action(() => withClient(showApplicationInfo));
19
+ }
@@ -0,0 +1,128 @@
1
+ import { WaveLinkClient } from "@raphiiko/wavelink-ts";
2
+ import { Argument, Command } from "commander";
3
+ import { withClient } from "../services/client.js";
4
+ import { requireInput } from "../services/finders.js";
5
+ import { formatPercent, formatMuted } from "../utils/format.js";
6
+ import { parsePercent } from "../utils/validation.js";
7
+
8
+ export async function setInputGain(
9
+ client: WaveLinkClient,
10
+ inputId: string,
11
+ gainPercent: number
12
+ ): Promise<void> {
13
+ const input = await requireInput(client, inputId);
14
+ await client.setInputGain(input.deviceId, input.inputId, gainPercent / 100);
15
+ console.log(`Successfully set input '${input.inputName}' gain to ${gainPercent}%`);
16
+ }
17
+
18
+ export async function setInputMute(
19
+ client: WaveLinkClient,
20
+ inputId: string,
21
+ isMuted: boolean
22
+ ): Promise<void> {
23
+ const input = await requireInput(client, inputId);
24
+ await client.setInputMute(input.deviceId, input.inputId, isMuted);
25
+ console.log(`Successfully ${isMuted ? "muted" : "unmuted"} input '${input.inputName}'`);
26
+ }
27
+
28
+ export async function listInputs(client: WaveLinkClient): Promise<void> {
29
+ const { inputDevices } = await client.getInputDevices();
30
+
31
+ console.log("\n=== Input Devices ===\n");
32
+
33
+ if (inputDevices.length === 0) {
34
+ console.log("No input devices found.");
35
+ return;
36
+ }
37
+
38
+ for (const device of inputDevices) {
39
+ console.log(`Device: ${device.name || device.id}`);
40
+ console.log(` Device ID: ${device.id}`);
41
+ console.log(` Wave Device: ${formatMuted(device.isWaveDevice)}`);
42
+
43
+ if (device.inputs.length === 0) {
44
+ console.log(" No inputs available");
45
+ } else {
46
+ for (const input of device.inputs) {
47
+ console.log(` Input: ${input.name || input.id}`);
48
+ console.log(` Input ID: ${input.id}`);
49
+ const gainMin = input.gain.min !== undefined ? formatPercent(input.gain.min) : "unknown";
50
+ const gainMax =
51
+ input.gain.max !== undefined
52
+ ? formatPercent(input.gain.max)
53
+ : input.gain.maxRange !== undefined
54
+ ? formatPercent(input.gain.maxRange)
55
+ : "unknown";
56
+ console.log(
57
+ ` Gain: ${formatPercent(input.gain.value)} (min: ${gainMin}, max: ${gainMax})`
58
+ );
59
+ if (input.isGainLockOn !== undefined) {
60
+ console.log(` Gain Lock: ${formatMuted(input.isGainLockOn)}`);
61
+ }
62
+ console.log(` Muted: ${formatMuted(input.isMuted)}`);
63
+ if (input.micPcMix) {
64
+ console.log(
65
+ ` Mic/PC Mix: ${formatPercent(input.micPcMix.value)}${input.micPcMix.isInverted ? " (inverted)" : ""}`
66
+ );
67
+ }
68
+ if (input.effects?.length) {
69
+ const effectsList = input.effects
70
+ .map((e) => `${e.name || e.id} (${e.isEnabled ? "ON" : "OFF"})`)
71
+ .join(", ");
72
+ console.log(` Effects: ${effectsList}`);
73
+ }
74
+ }
75
+ }
76
+ console.log();
77
+ }
78
+ }
79
+
80
+ export function registerInputCommands(program: Command): void {
81
+ const inputCmd = program.command("input").description("Manage input devices");
82
+
83
+ inputCmd
84
+ .command("list")
85
+ .description("List all input devices with their IDs")
86
+ .action(() => withClient(listInputs));
87
+
88
+ inputCmd
89
+ .command("set-gain")
90
+ .description("Set input device gain")
91
+ .addArgument(
92
+ new Argument("<input-id-or-name>", "ID or name of the input device (case-insensitive)")
93
+ )
94
+ .addArgument(new Argument("<gain>", "Gain level (0-100)"))
95
+ .action((inputId: string, gain: string) => {
96
+ const gainPercent = parsePercent(gain, "Gain");
97
+ return withClient((client) => setInputGain(client, inputId, gainPercent));
98
+ });
99
+
100
+ inputCmd
101
+ .command("mute")
102
+ .description("Mute an input device")
103
+ .addArgument(
104
+ new Argument("<input-id-or-name>", "ID or name of the input device (case-insensitive)")
105
+ )
106
+ .action((inputId: string) => withClient((client) => setInputMute(client, inputId, true)));
107
+
108
+ inputCmd
109
+ .command("unmute")
110
+ .description("Unmute an input device")
111
+ .addArgument(
112
+ new Argument("<input-id-or-name>", "ID or name of the input device (case-insensitive)")
113
+ )
114
+ .action((inputId: string) => withClient((client) => setInputMute(client, inputId, false)));
115
+
116
+ inputCmd
117
+ .command("toggle-mute")
118
+ .description("Toggle input device mute state")
119
+ .addArgument(
120
+ new Argument("<input-id-or-name>", "ID or name of the input device (case-insensitive)")
121
+ )
122
+ .action((inputId: string) =>
123
+ withClient(async (client) => {
124
+ const input = await requireInput(client, inputId);
125
+ await setInputMute(client, inputId, !input.isMuted);
126
+ })
127
+ );
128
+ }
@@ -0,0 +1,98 @@
1
+ import { WaveLinkClient } from "@raphiiko/wavelink-ts";
2
+ import { Argument, Command } from "commander";
3
+ import { withClient } from "../services/client.js";
4
+ import { requireMix } from "../services/finders.js";
5
+ import { formatPercent, formatMuted } from "../utils/format.js";
6
+ import { parsePercent } from "../utils/validation.js";
7
+ import { setSingleOutputForMix } from "./output.js";
8
+
9
+ export async function listMixes(client: WaveLinkClient): Promise<void> {
10
+ const { mixes } = await client.getMixes();
11
+
12
+ console.log("\n=== Mixes ===\n");
13
+
14
+ if (mixes.length === 0) {
15
+ console.log("No mixes found.");
16
+ return;
17
+ }
18
+
19
+ for (const mix of mixes) {
20
+ console.log(`Mix: ${mix.name}`);
21
+ console.log(` ID: ${mix.id}`);
22
+ console.log(` Level: ${formatPercent(mix.level)}`);
23
+ console.log(` Muted: ${formatMuted(mix.isMuted)}`);
24
+ console.log();
25
+ }
26
+ }
27
+
28
+ export function registerMixCommands(program: Command): void {
29
+ const mixCmd = program.command("mix").description("Manage mixes");
30
+
31
+ mixCmd
32
+ .command("list")
33
+ .description("List all mixes with their IDs and names")
34
+ .action(() => withClient(listMixes));
35
+
36
+ mixCmd
37
+ .command("set-output")
38
+ .description(
39
+ "Set a device as the ONLY output for a mix (removes all other outputs from that mix)"
40
+ )
41
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
42
+ .addArgument(
43
+ new Argument("<output-id-or-name>", "ID or name of the output device (case-insensitive)")
44
+ )
45
+ .action((mixId: string, outputId: string) =>
46
+ withClient((client) => setSingleOutputForMix(client, outputId, mixId))
47
+ );
48
+
49
+ mixCmd
50
+ .command("set-volume")
51
+ .description("Set mix master volume")
52
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
53
+ .addArgument(new Argument("<volume>", "Volume level (0-100)"))
54
+ .action((mixId: string, volume: string) => {
55
+ const volumePercent = parsePercent(volume, "Volume");
56
+ return withClient(async (client) => {
57
+ const mix = await requireMix(client, mixId);
58
+ await client.setMixVolume(mix.id, volumePercent / 100);
59
+ console.log(`Successfully set mix '${mix.name}' volume to ${volumePercent}%`);
60
+ });
61
+ });
62
+
63
+ mixCmd
64
+ .command("mute")
65
+ .description("Mute a mix")
66
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
67
+ .action((mixId: string) =>
68
+ withClient(async (client) => {
69
+ const mix = await requireMix(client, mixId);
70
+ await client.setMixMute(mix.id, true);
71
+ console.log(`Successfully muted mix '${mix.name}'`);
72
+ })
73
+ );
74
+
75
+ mixCmd
76
+ .command("unmute")
77
+ .description("Unmute a mix")
78
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
79
+ .action((mixId: string) =>
80
+ withClient(async (client) => {
81
+ const mix = await requireMix(client, mixId);
82
+ await client.setMixMute(mix.id, false);
83
+ console.log(`Successfully unmuted mix '${mix.name}'`);
84
+ })
85
+ );
86
+
87
+ mixCmd
88
+ .command("toggle-mute")
89
+ .description("Toggle mix mute state")
90
+ .addArgument(new Argument("<mix-id-or-name>", "ID or name of the mix (case-insensitive)"))
91
+ .action((mixId: string) =>
92
+ withClient(async (client) => {
93
+ const mix = await requireMix(client, mixId);
94
+ await client.toggleMixMute(mix.id);
95
+ console.log(`Successfully toggled mute for mix '${mix.name}'`);
96
+ })
97
+ );
98
+ }