@involvex/youtube-music-cli 0.0.18 → 0.0.20

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 (36) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/source/cli.js +123 -8
  3. package/dist/source/components/import/ImportLayout.d.ts +1 -0
  4. package/dist/source/components/import/ImportLayout.js +119 -0
  5. package/dist/source/components/import/ImportProgress.d.ts +6 -0
  6. package/dist/source/components/import/ImportProgress.js +73 -0
  7. package/dist/source/components/layouts/MainLayout.js +15 -2
  8. package/dist/source/components/settings/Settings.js +6 -2
  9. package/dist/source/services/config/config.service.js +10 -0
  10. package/dist/source/services/import/import.service.d.ts +44 -0
  11. package/dist/source/services/import/import.service.js +272 -0
  12. package/dist/source/services/import/spotify.service.d.ts +40 -0
  13. package/dist/source/services/import/spotify.service.js +171 -0
  14. package/dist/source/services/import/track-matcher.service.d.ts +60 -0
  15. package/dist/source/services/import/track-matcher.service.js +271 -0
  16. package/dist/source/services/import/youtube-import.service.d.ts +17 -0
  17. package/dist/source/services/import/youtube-import.service.js +84 -0
  18. package/dist/source/services/web/static-file.service.d.ts +31 -0
  19. package/dist/source/services/web/static-file.service.js +163 -0
  20. package/dist/source/services/web/web-server-manager.d.ts +88 -0
  21. package/dist/source/services/web/web-server-manager.js +502 -0
  22. package/dist/source/services/web/web-streaming.service.d.ts +88 -0
  23. package/dist/source/services/web/web-streaming.service.js +290 -0
  24. package/dist/source/services/web/websocket.server.d.ts +63 -0
  25. package/dist/source/services/web/websocket.server.js +267 -0
  26. package/dist/source/stores/player.store.js +24 -0
  27. package/dist/source/types/cli.types.d.ts +8 -0
  28. package/dist/source/types/config.types.d.ts +2 -0
  29. package/dist/source/types/import.types.d.ts +72 -0
  30. package/dist/source/types/import.types.js +2 -0
  31. package/dist/source/types/web.types.d.ts +127 -0
  32. package/dist/source/types/web.types.js +2 -0
  33. package/dist/source/utils/constants.d.ts +1 -0
  34. package/dist/source/utils/constants.js +1 -0
  35. package/dist/youtube-music-cli.exe +0 -0
  36. package/package.json +8 -3
@@ -0,0 +1,290 @@
1
+ import { logger } from "../logger/logger.service.js";
2
+ class WebStreamingService {
3
+ clients = new Map();
4
+ clientInfo = new Map();
5
+ messageHandlers = new Set();
6
+ prevState = null;
7
+ UPDATE_THROTTLE_MS = 250; // Throttle updates to 4/sec
8
+ lastUpdateTime = 0;
9
+ pendingUpdate = null;
10
+ updateTimer = null;
11
+ /**
12
+ * Add a client connection
13
+ */
14
+ addClient(clientId, ws, authenticated = true) {
15
+ this.clients.set(clientId, ws);
16
+ this.clientInfo.set(clientId, {
17
+ id: clientId,
18
+ authenticated,
19
+ connectedAt: Date.now(),
20
+ lastHeartbeat: Date.now(),
21
+ });
22
+ logger.info('WebStreamingService', 'Client connected', {
23
+ clientId,
24
+ authenticated,
25
+ });
26
+ // Send initial state if available
27
+ if (this.prevState) {
28
+ this.sendToClient(clientId, {
29
+ type: 'state-update',
30
+ state: this.prevState,
31
+ });
32
+ }
33
+ // Send connection event
34
+ this.broadcast({
35
+ type: 'event',
36
+ event: 'client-connected',
37
+ data: { clientId, clientCount: this.clients.size },
38
+ });
39
+ }
40
+ /**
41
+ * Remove a client connection
42
+ */
43
+ removeClient(clientId) {
44
+ const hadClient = this.clients.has(clientId);
45
+ this.clients.delete(clientId);
46
+ this.clientInfo.delete(clientId);
47
+ if (hadClient) {
48
+ logger.info('WebStreamingService', 'Client disconnected', {
49
+ clientId,
50
+ remainingClients: this.clients.size,
51
+ });
52
+ this.broadcast({
53
+ type: 'event',
54
+ event: 'client-disconnected',
55
+ data: { clientId, clientCount: this.clients.size },
56
+ });
57
+ }
58
+ }
59
+ /**
60
+ * Check if a client exists
61
+ */
62
+ hasClient(clientId) {
63
+ return this.clients.has(clientId);
64
+ }
65
+ /**
66
+ * Get client count
67
+ */
68
+ getClientCount() {
69
+ return this.clients.size;
70
+ }
71
+ /**
72
+ * Get all connected clients
73
+ */
74
+ getClients() {
75
+ return Array.from(this.clients.keys());
76
+ }
77
+ /**
78
+ * Handle incoming message from a client
79
+ */
80
+ handleClientMessage(clientId, message) {
81
+ try {
82
+ const msg = message;
83
+ // Update client heartbeat
84
+ const info = this.clientInfo.get(clientId);
85
+ if (info) {
86
+ info.lastHeartbeat = Date.now();
87
+ this.clientInfo.set(clientId, info);
88
+ }
89
+ // Emit to all handlers
90
+ for (const handler of this.messageHandlers) {
91
+ handler(msg);
92
+ }
93
+ }
94
+ catch (error) {
95
+ logger.error('WebStreamingService', 'Failed to handle client message', {
96
+ clientId,
97
+ error: error instanceof Error ? error.message : String(error),
98
+ });
99
+ }
100
+ }
101
+ /**
102
+ * Register a handler for incoming client messages
103
+ */
104
+ onMessage(handler) {
105
+ this.messageHandlers.add(handler);
106
+ return () => {
107
+ this.messageHandlers.delete(handler);
108
+ };
109
+ }
110
+ /**
111
+ * Send a message to a specific client
112
+ */
113
+ sendToClient(clientId, message) {
114
+ const ws = this.clients.get(clientId);
115
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
116
+ return;
117
+ }
118
+ try {
119
+ ws.send(JSON.stringify(message));
120
+ }
121
+ catch (error) {
122
+ logger.error('WebStreamingService', 'Failed to send to client', {
123
+ clientId,
124
+ error: error instanceof Error ? error.message : String(error),
125
+ });
126
+ }
127
+ }
128
+ /**
129
+ * Broadcast a message to all connected clients
130
+ */
131
+ broadcast(message) {
132
+ const data = JSON.stringify(message);
133
+ for (const [clientId, ws] of this.clients) {
134
+ if (ws.readyState === WebSocket.OPEN) {
135
+ try {
136
+ ws.send(data);
137
+ }
138
+ catch (error) {
139
+ logger.error('WebStreamingService', 'Failed to broadcast to client', {
140
+ clientId,
141
+ error: error instanceof Error ? error.message : String(error),
142
+ });
143
+ }
144
+ }
145
+ }
146
+ }
147
+ /**
148
+ * Compute partial state delta (only changed fields)
149
+ */
150
+ computeDelta(newState) {
151
+ if (!this.prevState) {
152
+ return newState;
153
+ }
154
+ const delta = {};
155
+ // Check each field for changes
156
+ const fields = [
157
+ 'currentTrack',
158
+ 'isPlaying',
159
+ 'volume',
160
+ 'speed',
161
+ 'progress',
162
+ 'duration',
163
+ 'queue',
164
+ 'queuePosition',
165
+ 'repeat',
166
+ 'shuffle',
167
+ 'isLoading',
168
+ 'error',
169
+ ];
170
+ for (const field of fields) {
171
+ const prevValue = this.prevState[field];
172
+ const newValue = newState[field];
173
+ // Deep comparison for objects/arrays
174
+ if (typeof prevValue === 'object' && prevValue !== null) {
175
+ if (JSON.stringify(prevValue) !== JSON.stringify(newValue)) {
176
+ delta[field] = newValue;
177
+ }
178
+ }
179
+ else if (prevValue !== newValue) {
180
+ delta[field] = newValue;
181
+ }
182
+ }
183
+ return delta;
184
+ }
185
+ /**
186
+ * Update and broadcast player state (throttled)
187
+ */
188
+ onStateChange(state) {
189
+ this.prevState = { ...state };
190
+ const now = Date.now();
191
+ const delta = this.computeDelta(state);
192
+ // Skip if no changes
193
+ if (Object.keys(delta).length === 0) {
194
+ return;
195
+ }
196
+ // Merge with pending update
197
+ this.pendingUpdate = { ...(this.pendingUpdate || {}), ...delta };
198
+ // Clear existing timer
199
+ if (this.updateTimer) {
200
+ clearTimeout(this.updateTimer);
201
+ }
202
+ // If throttle period passed, send immediately
203
+ if (now - this.lastUpdateTime >= this.UPDATE_THROTTLE_MS) {
204
+ this.sendPendingUpdate();
205
+ }
206
+ else {
207
+ // Otherwise, schedule update
208
+ this.updateTimer = setTimeout(() => {
209
+ this.sendPendingUpdate();
210
+ }, this.UPDATE_THROTTLE_MS - (now - this.lastUpdateTime));
211
+ }
212
+ }
213
+ /**
214
+ * Send pending state update to all clients
215
+ */
216
+ sendPendingUpdate() {
217
+ if (!this.pendingUpdate)
218
+ return;
219
+ this.broadcast({
220
+ type: 'state-update',
221
+ state: this.pendingUpdate,
222
+ });
223
+ this.pendingUpdate = null;
224
+ this.lastUpdateTime = Date.now();
225
+ }
226
+ /**
227
+ * Broadcast import progress
228
+ */
229
+ onImportProgress(progress) {
230
+ this.broadcast({
231
+ type: 'import-progress',
232
+ data: progress,
233
+ });
234
+ }
235
+ /**
236
+ * Broadcast import result
237
+ */
238
+ onImportResult(result) {
239
+ this.broadcast({
240
+ type: 'import-result',
241
+ data: result,
242
+ });
243
+ }
244
+ /**
245
+ * Broadcast error message
246
+ */
247
+ sendError(error, code) {
248
+ this.broadcast({
249
+ type: 'error',
250
+ error,
251
+ code,
252
+ });
253
+ }
254
+ /**
255
+ * Disconnect all clients
256
+ */
257
+ disconnectAll() {
258
+ for (const [, ws] of this.clients) {
259
+ try {
260
+ ws.close();
261
+ }
262
+ catch {
263
+ // Ignore
264
+ }
265
+ }
266
+ this.clients.clear();
267
+ this.clientInfo.clear();
268
+ logger.info('WebStreamingService', 'All clients disconnected');
269
+ }
270
+ /**
271
+ * Get server statistics
272
+ */
273
+ getStats() {
274
+ const now = Date.now();
275
+ const oldestClient = Array.from(this.clientInfo.values()).sort((a, b) => a.connectedAt - b.connectedAt)[0];
276
+ return {
277
+ clients: this.clients.size,
278
+ totalConnections: this.clientInfo.size,
279
+ uptime: oldestClient ? now - oldestClient.connectedAt : 0,
280
+ };
281
+ }
282
+ }
283
+ // Singleton instance
284
+ let webStreamingServiceInstance = null;
285
+ export function getWebStreamingService() {
286
+ if (!webStreamingServiceInstance) {
287
+ webStreamingServiceInstance = new WebStreamingService();
288
+ }
289
+ return webStreamingServiceInstance;
290
+ }
@@ -0,0 +1,63 @@
1
+ import type { WebServerConfig } from '../../types/web.types.ts';
2
+ import type { PlayerAction } from '../../types/player.types.ts';
3
+ interface WebSocketServerOptions {
4
+ config: WebServerConfig;
5
+ onCommand?: (action: PlayerAction) => void;
6
+ onImportRequest?: (source: 'spotify' | 'youtube', url: string, name?: string) => void;
7
+ onSearchRequest?: (query: string, searchType: 'all' | 'songs' | 'artists' | 'albums' | 'playlists') => void;
8
+ onConfigUpdate?: (config: Record<string, unknown>) => void;
9
+ }
10
+ declare class WebSocketServerClass {
11
+ private httpServer;
12
+ private wsServer;
13
+ private config;
14
+ private streamingService;
15
+ private staticFileService;
16
+ private onCommand?;
17
+ private onImportRequest?;
18
+ private onSearchRequest?;
19
+ private onConfigUpdate?;
20
+ constructor();
21
+ /**
22
+ * Start the WebSocket server
23
+ */
24
+ start(options: WebSocketServerOptions): Promise<void>;
25
+ /**
26
+ * Handle HTTP requests (for static file serving)
27
+ */
28
+ private handleHttpRequest;
29
+ /**
30
+ * Handle WebSocket connection
31
+ */
32
+ private handleWebSocketConnection;
33
+ /**
34
+ * Handle commands from clients
35
+ */
36
+ private handleClientCommand;
37
+ /**
38
+ * Send message to a specific WebSocket client
39
+ */
40
+ private sendToClient;
41
+ /**
42
+ * Generate a unique client ID
43
+ */
44
+ private generateClientId;
45
+ /**
46
+ * Extract auth token from request
47
+ */
48
+ private extractAuthToken;
49
+ /**
50
+ * Stop the server
51
+ */
52
+ stop(): Promise<void>;
53
+ /**
54
+ * Check if server is running
55
+ */
56
+ isRunning(): boolean;
57
+ /**
58
+ * Get server URL
59
+ */
60
+ getServerUrl(): string;
61
+ }
62
+ export declare function getWebSocketServer(): WebSocketServerClass;
63
+ export {};
@@ -0,0 +1,267 @@
1
+ // WebSocket server for web UI
2
+ import { createServer as createHttpServer, } from 'node:http';
3
+ import { WebSocketServer, WebSocket } from 'ws';
4
+ import { getWebStreamingService } from "./web-streaming.service.js";
5
+ import { getStaticFileService } from "./static-file.service.js";
6
+ import { logger } from "../logger/logger.service.js";
7
+ class WebSocketServerClass {
8
+ httpServer = null;
9
+ wsServer = null;
10
+ config;
11
+ streamingService = getWebStreamingService();
12
+ staticFileService = getStaticFileService();
13
+ onCommand;
14
+ onImportRequest;
15
+ onSearchRequest;
16
+ onConfigUpdate;
17
+ constructor() {
18
+ this.config = {
19
+ enabled: false,
20
+ host: 'localhost',
21
+ port: 8080,
22
+ enableCors: true,
23
+ allowedOrigins: ['*'],
24
+ auth: { enabled: false },
25
+ };
26
+ }
27
+ /**
28
+ * Start the WebSocket server
29
+ */
30
+ async start(options) {
31
+ this.config = options.config;
32
+ this.onCommand = options.onCommand;
33
+ this.onImportRequest = options.onImportRequest;
34
+ this.onSearchRequest = options.onSearchRequest;
35
+ this.onConfigUpdate = options.onConfigUpdate;
36
+ logger.info('WebSocketServer', 'Starting server', {
37
+ host: this.config.host,
38
+ port: this.config.port,
39
+ auth: this.config.auth.enabled,
40
+ });
41
+ // Create HTTP server
42
+ this.httpServer = createHttpServer((req, res) => {
43
+ this.handleHttpRequest(req, res);
44
+ });
45
+ // Create WebSocket server
46
+ this.wsServer = new WebSocketServer({
47
+ server: this.httpServer,
48
+ path: '/ws',
49
+ });
50
+ // Handle WebSocket connections
51
+ this.wsServer.on('connection', (ws, req) => {
52
+ this.handleWebSocketConnection(ws, req);
53
+ });
54
+ // Handle HTTP server errors
55
+ this.httpServer.on('error', error => {
56
+ logger.error('WebSocketServer', 'HTTP server error', {
57
+ error: error instanceof Error ? error.message : String(error),
58
+ });
59
+ });
60
+ // Handle WebSocket server errors
61
+ this.wsServer.on('error', error => {
62
+ logger.error('WebSocketServer', 'WebSocket server error', {
63
+ error: error instanceof Error ? error.message : String(error),
64
+ });
65
+ });
66
+ // Start listening
67
+ return new Promise((resolve, reject) => {
68
+ this.httpServer.listen({
69
+ host: this.config.host,
70
+ port: this.config.port,
71
+ }, () => {
72
+ logger.info('WebSocketServer', 'Server started', {
73
+ url: `http://${this.config.host}:${this.config.port}`,
74
+ });
75
+ resolve();
76
+ });
77
+ this.httpServer.on('error', reject);
78
+ });
79
+ }
80
+ /**
81
+ * Handle HTTP requests (for static file serving)
82
+ */
83
+ handleHttpRequest(req, res) {
84
+ // Set CORS headers if enabled
85
+ if (this.config.enableCors) {
86
+ const origin = req.headers.origin;
87
+ if (origin &&
88
+ (this.config.allowedOrigins.includes('*') ||
89
+ this.config.allowedOrigins.includes(origin))) {
90
+ res.setHeader('Access-Control-Allow-Origin', origin);
91
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
92
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
93
+ }
94
+ }
95
+ // Handle preflight requests
96
+ if (req.method === 'OPTIONS') {
97
+ res.writeHead(204);
98
+ res.end();
99
+ return;
100
+ }
101
+ // Serve static files
102
+ const url = req.url ?? '/';
103
+ this.staticFileService.serve(url, req, res);
104
+ }
105
+ /**
106
+ * Handle WebSocket connection
107
+ */
108
+ handleWebSocketConnection(ws, req) {
109
+ const clientId = this.generateClientId();
110
+ logger.info('WebSocketServer', 'New connection', {
111
+ clientId,
112
+ ip: req.socket.remoteAddress,
113
+ });
114
+ // Handle authentication if enabled
115
+ if (this.config.auth.enabled) {
116
+ const token = this.extractAuthToken(req);
117
+ if (!token || token !== this.config.auth.token) {
118
+ logger.warn('WebSocketServer', 'Authentication failed', { clientId });
119
+ ws.close(1008, 'Authentication failed');
120
+ return;
121
+ }
122
+ }
123
+ // Add client to streaming service
124
+ this.streamingService.addClient(clientId, ws, true);
125
+ // Send welcome message
126
+ this.sendToClient(ws, {
127
+ type: 'auth',
128
+ success: true,
129
+ message: 'Connected to YouTube Music CLI',
130
+ });
131
+ // Handle incoming messages
132
+ ws.on('message', (data) => {
133
+ try {
134
+ const message = JSON.parse(data.toString());
135
+ this.streamingService.handleClientMessage(clientId, message);
136
+ this.handleClientCommand(clientId, message);
137
+ }
138
+ catch (error) {
139
+ logger.error('WebSocketServer', 'Failed to parse message', {
140
+ clientId,
141
+ error: error instanceof Error ? error.message : String(error),
142
+ });
143
+ }
144
+ });
145
+ // Handle close
146
+ ws.on('close', () => {
147
+ this.streamingService.removeClient(clientId);
148
+ });
149
+ // Handle errors
150
+ ws.on('error', error => {
151
+ logger.error('WebSocketServer', 'WebSocket error', {
152
+ error: error instanceof Error ? error.message : String(error),
153
+ });
154
+ });
155
+ }
156
+ /**
157
+ * Handle commands from clients
158
+ */
159
+ handleClientCommand(_clientId, message) {
160
+ switch (message.type) {
161
+ case 'command':
162
+ if (this.onCommand) {
163
+ this.onCommand(message.action);
164
+ }
165
+ break;
166
+ case 'import-request':
167
+ if (this.onImportRequest) {
168
+ this.onImportRequest(message.source, message.url, message.name);
169
+ }
170
+ break;
171
+ case 'search-request':
172
+ if (this.onSearchRequest) {
173
+ this.onSearchRequest(message.query, message.searchType);
174
+ }
175
+ break;
176
+ case 'config-update':
177
+ if (this.onConfigUpdate) {
178
+ this.onConfigUpdate(message.config);
179
+ }
180
+ break;
181
+ case 'auth-request':
182
+ // Already handled in connection phase
183
+ break;
184
+ }
185
+ }
186
+ /**
187
+ * Send message to a specific WebSocket client
188
+ */
189
+ sendToClient(ws, message) {
190
+ if (ws.readyState === WebSocket.OPEN) {
191
+ try {
192
+ ws.send(JSON.stringify(message));
193
+ }
194
+ catch (error) {
195
+ logger.error('WebSocketServer', 'Failed to send message', {
196
+ error: error instanceof Error ? error.message : String(error),
197
+ });
198
+ }
199
+ }
200
+ }
201
+ /**
202
+ * Generate a unique client ID
203
+ */
204
+ generateClientId() {
205
+ return `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
206
+ }
207
+ /**
208
+ * Extract auth token from request
209
+ */
210
+ extractAuthToken(req) {
211
+ // Check Authorization header
212
+ const authHeader = req.headers.authorization;
213
+ if (authHeader?.startsWith('Bearer ')) {
214
+ return authHeader.substring(7);
215
+ }
216
+ // Check query parameter
217
+ const url = req.url ?? '';
218
+ const urlObj = new URL(url, `http://${req.headers.host}`);
219
+ return urlObj.searchParams.get('token');
220
+ }
221
+ /**
222
+ * Stop the server
223
+ */
224
+ async stop() {
225
+ logger.info('WebSocketServer', 'Stopping server');
226
+ // Disconnect all clients
227
+ this.streamingService.disconnectAll();
228
+ // Close WebSocket server
229
+ if (this.wsServer) {
230
+ this.wsServer.close();
231
+ this.wsServer = null;
232
+ }
233
+ // Close HTTP server
234
+ if (this.httpServer) {
235
+ return new Promise(resolve => {
236
+ this.httpServer.close(() => {
237
+ this.httpServer = null;
238
+ logger.info('WebSocketServer', 'Server stopped');
239
+ resolve();
240
+ });
241
+ });
242
+ }
243
+ }
244
+ /**
245
+ * Check if server is running
246
+ */
247
+ isRunning() {
248
+ return this.httpServer !== null;
249
+ }
250
+ /**
251
+ * Get server URL
252
+ */
253
+ getServerUrl() {
254
+ if (!this.isRunning()) {
255
+ throw new Error('Server is not running');
256
+ }
257
+ return `http://${this.config.host}:${this.config.port}`;
258
+ }
259
+ }
260
+ // Singleton instance
261
+ let webSocketServerInstance = null;
262
+ export function getWebSocketServer() {
263
+ if (!webSocketServerInstance) {
264
+ webSocketServerInstance = new WebSocketServerClass();
265
+ }
266
+ return webSocketServerInstance;
267
+ }
@@ -8,6 +8,8 @@ import { getNotificationService } from "../services/notification/notification.se
8
8
  import { getScrobblingService } from "../services/scrobbling/scrobbling.service.js";
9
9
  import { getDiscordRpcService } from "../services/discord/discord-rpc.service.js";
10
10
  import { getMprisService } from "../services/mpris/mpris.service.js";
11
+ import { getWebServerManager } from "../services/web/web-server-manager.js";
12
+ import { getWebStreamingService } from "../services/web/web-streaming.service.js";
11
13
  const initialState = {
12
14
  currentTrack: null,
13
15
  isPlaying: false,
@@ -551,6 +553,28 @@ export function PlayerProvider({ children }) {
551
553
  // Only register handlers once, update via ref
552
554
  // eslint-disable-next-line react-hooks/exhaustive-deps
553
555
  }, []);
556
+ // Web streaming: Broadcast state changes to connected clients
557
+ useEffect(() => {
558
+ // Initialize web streaming service and set up command handler
559
+ const streamingService = getWebStreamingService();
560
+ // Set up handler for incoming commands from web clients
561
+ const unsubscribe = streamingService.onMessage(message => {
562
+ if (message.type === 'command') {
563
+ dispatch(message.action);
564
+ }
565
+ });
566
+ return () => {
567
+ unsubscribe();
568
+ };
569
+ }, [dispatch]);
570
+ // Broadcast state changes to web clients
571
+ useEffect(() => {
572
+ const webServerManager = getWebServerManager();
573
+ if (webServerManager.isServerRunning()) {
574
+ const streamingService = getWebStreamingService();
575
+ streamingService.onStateChange(state);
576
+ }
577
+ }, [state]);
554
578
  const actions = useMemo(() => ({
555
579
  play: (track) => {
556
580
  logger.info('PlayerProvider', 'play() action dispatched', {
@@ -11,4 +11,12 @@ export interface Flags {
11
11
  showSuggestions?: boolean;
12
12
  headless?: boolean;
13
13
  action?: 'pause' | 'resume' | 'next' | 'previous';
14
+ importSource?: 'spotify' | 'youtube';
15
+ importUrl?: string;
16
+ importName?: string;
17
+ web?: boolean;
18
+ webHost?: string;
19
+ webPort?: number;
20
+ webOnly?: boolean;
21
+ webAuth?: string;
14
22
  }
@@ -1,5 +1,6 @@
1
1
  import type { Playlist } from './youtube-music.types.ts';
2
2
  import type { Theme } from './theme.types.ts';
3
+ import type { WebServerConfig } from './web.types.ts';
3
4
  export type RepeatMode = 'off' | 'all' | 'one';
4
5
  export type DownloadFormat = 'mp3' | 'm4a';
5
6
  export interface KeybindingConfig {
@@ -34,4 +35,5 @@ export interface Config {
34
35
  downloadsEnabled?: boolean;
35
36
  downloadDirectory?: string;
36
37
  downloadFormat?: DownloadFormat;
38
+ webServer?: WebServerConfig;
37
39
  }