@lucaperret/tidal-cli 1.1.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,7 @@
8
8
 
9
9
  [![npm](https://img.shields.io/npm/v/@lucaperret/tidal-cli)](https://www.npmjs.com/package/@lucaperret/tidal-cli)
10
10
  [![CI](https://img.shields.io/github/actions/workflow/status/lucaperret/tidal-cli/ci.yml?label=tests)](https://github.com/lucaperret/tidal-cli/actions)
11
+ [![smithery badge](https://smithery.ai/badge/lucaperret/tidal)](https://smithery.ai/servers/lucaperret/tidal)
11
12
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
12
13
  [![Node](https://img.shields.io/badge/node-%3E%3D20-green.svg)](https://nodejs.org)
13
14
 
@@ -24,8 +25,10 @@ tidal-cli wraps the [Tidal API v2](https://developer.tidal.com) into a single co
24
25
  - **Playlists** — full CRUD, add/remove tracks, reorder, add entire albums
25
26
  - **Library** — favorites for artists, albums, tracks, videos, playlists
26
27
  - **Playback** — stream info, direct URLs, local playback via DASH
27
- - **Recommendations** — personalized mixes (My Mix, Discovery, New Arrivals)
28
- - **History** — recently added tracks, albums, artists
28
+ - **Recommendations** — personalized mixes (Daily, Discovery, New Release, Offline) with drill-down into mix items
29
+ - **History** — recently added tracks, albums, artists; search history (list, delete, clear)
30
+ - **Save for Later** — bookmark items in a separate queue from your main library
31
+ - **Sharing** — generate public share links for tracks and albums
29
32
  - **JSON output** on every command for scripting and agent use
30
33
 
31
34
  ## Installation
@@ -120,13 +123,28 @@ tidal-cli library remove-playlist --playlist-id <id>
120
123
  ### Discovery & History
121
124
 
122
125
  ```bash
123
- tidal-cli recommend
126
+ tidal-cli recommend # all mix categories
127
+ tidal-cli recommend --type daily # daily | discovery | new-release | offline
128
+ tidal-cli mix items <mix-id> --type daily # tracks inside a specific mix
124
129
  tidal-cli history tracks
125
130
  tidal-cli history albums
126
131
  tidal-cli history artists
132
+ tidal-cli search history # your recent searches
133
+ tidal-cli search history-delete <entry-id>
134
+ tidal-cli search history-clear
127
135
  tidal-cli user profile
128
136
  ```
129
137
 
138
+ ### Save for Later & Sharing
139
+
140
+ ```bash
141
+ tidal-cli saved list
142
+ tidal-cli saved add --type tracks --id <id> # tracks | albums | artists | playlists | videos
143
+ tidal-cli saved remove --type albums --id <id>
144
+ tidal-cli share track <id> # creates a public share link
145
+ tidal-cli share album <id>
146
+ ```
147
+
130
148
  ### Playback
131
149
 
132
150
  ```bash
@@ -148,9 +166,20 @@ tidal-cli --json playlist list
148
166
  tidal-cli --json artist similar 8992
149
167
  ```
150
168
 
169
+ ## MCP Server (Claude Integration)
170
+
171
+ tidal-cli is available as a remote MCP server for [Claude Desktop](https://claude.ai), [Smithery](https://smithery.ai/servers/lucaperret/tidal), and any MCP-compatible client.
172
+
173
+ **Connect in Claude Desktop:**
174
+ 1. Settings → Connectors → Add custom connector
175
+ 2. Enter: `https://tidal-cli.lucaperret.ch/api/mcp`
176
+ 3. Click "Connect" → log in to Tidal → done
177
+
178
+ 40 tools with OAuth authentication, safety annotations, and 3 prompt templates.
179
+
151
180
  ## Agent Automation
152
181
 
153
- tidal-cli is available as an [OpenClaw](https://openclaw.ai) skill on [ClawHub](https://clawhub.ai/lucaperret/tidal-cli). Install it for your AI agent:
182
+ tidal-cli is also available as an [OpenClaw](https://openclaw.ai) skill on [ClawHub](https://clawhub.ai/lucaperret/tidal-cli). Install it for your AI agent:
154
183
 
155
184
  ```bash
156
185
  clawhub install tidal-cli
@@ -197,7 +226,7 @@ npm test # run once
197
226
  npm run test:watch # watch mode
198
227
  ```
199
228
 
200
- 111 tests covering search, playlists, artists, tracks, albums, library, auth, and session.
229
+ 143 tests covering search, playlists, artists, tracks, albums, library, recommendations, mixes, save-for-later, sharing, search history, auth, and session.
201
230
 
202
231
  ## License
203
232
 
package/dist/album.d.ts CHANGED
@@ -1,2 +1,6 @@
1
+ import type { AlbumInfo, AlbumResult } from './types';
2
+ export type { AlbumInfo, AlbumResult };
3
+ export declare function getAlbumInfoData(albumId: string, client: any, countryCode: string): Promise<AlbumInfo>;
1
4
  export declare function getAlbumInfo(albumId: string, json: boolean): Promise<void>;
5
+ export declare function getAlbumByBarcodeData(barcode: string, client: any, countryCode: string): Promise<AlbumResult[]>;
2
6
  export declare function getAlbumByBarcode(barcode: string, json: boolean): Promise<void>;
package/dist/album.js CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAlbumInfoData = getAlbumInfoData;
3
4
  exports.getAlbumInfo = getAlbumInfo;
5
+ exports.getAlbumByBarcodeData = getAlbumByBarcodeData;
4
6
  exports.getAlbumByBarcode = getAlbumByBarcode;
5
7
  const auth_1 = require("./auth");
6
8
  function formatDuration(isoDuration) {
@@ -14,32 +16,29 @@ function formatDuration(isoDuration) {
14
16
  const s = (match[3] ?? '0').padStart(2, '0');
15
17
  return `${h}${m}:${s}`;
16
18
  }
17
- async function getAlbumInfo(albumId, json) {
18
- const client = await (0, auth_1.getApiClient)();
19
+ async function getAlbumInfoData(albumId, client, countryCode) {
19
20
  const { data, error } = await client.GET('/albums/{id}', {
20
21
  params: {
21
22
  path: { id: albumId },
22
23
  query: {
23
- countryCode: await (0, auth_1.getCountryCode)(),
24
+ countryCode,
24
25
  include: ['artists', 'coverArt'],
25
26
  },
26
27
  },
27
28
  });
28
29
  if (error || !data) {
29
- console.error(`Error: Failed to get album info — ${JSON.stringify(error)}`);
30
- process.exit(1);
30
+ throw new Error(`Failed to get album info — ${JSON.stringify(error)}`);
31
31
  }
32
32
  const attrs = data.data?.attributes ?? {};
33
33
  const included = data.included ?? [];
34
34
  const artists = included
35
35
  .filter((item) => item.type === 'artists')
36
36
  .map((item) => item.attributes?.name ?? item.id);
37
- // Get cover art from included artworks
38
37
  const artwork = included.find((item) => item.type === 'artworks');
39
38
  const files = artwork?.attributes?.files ?? [];
40
39
  const preferred = files.find((f) => f.meta?.width === 640) ?? files[0];
41
40
  const coverUrl = preferred?.href;
42
- const result = {
41
+ return {
43
42
  id: albumId,
44
43
  title: attrs.title ?? 'Unknown',
45
44
  artists,
@@ -52,47 +51,56 @@ async function getAlbumInfo(albumId, json) {
52
51
  barcodeId: attrs.barcodeId,
53
52
  coverUrl,
54
53
  };
55
- if (json) {
56
- console.log(JSON.stringify(result, null, 2));
57
- return;
58
- }
59
- console.log(`\nAlbum: [${result.id}] ${result.title}`);
60
- if (result.artists.length > 0)
61
- console.log(` Artists: ${result.artists.join(', ')}`);
62
- if (result.albumType)
63
- console.log(` Type: ${result.albumType}`);
64
- if (result.releaseDate)
65
- console.log(` Release Date: ${result.releaseDate}`);
66
- if (result.numberOfItems !== undefined)
67
- console.log(` Tracks: ${result.numberOfItems}`);
68
- if (result.duration)
69
- console.log(` Duration: ${result.duration}`);
70
- if (result.popularity !== undefined)
71
- console.log(` Popularity: ${result.popularity}`);
72
- if (result.explicit !== undefined)
73
- console.log(` Explicit: ${result.explicit}`);
74
- if (result.barcodeId)
75
- console.log(` Barcode: ${result.barcodeId}`);
76
- if (result.coverUrl)
77
- console.log(` Cover: ${result.coverUrl}`);
78
- console.log();
79
54
  }
80
- async function getAlbumByBarcode(barcode, json) {
55
+ async function getAlbumInfo(albumId, json) {
81
56
  const client = await (0, auth_1.getApiClient)();
57
+ const countryCode = await (0, auth_1.getCountryCode)();
58
+ try {
59
+ const result = await getAlbumInfoData(albumId, client, countryCode);
60
+ if (json) {
61
+ console.log(JSON.stringify(result, null, 2));
62
+ return;
63
+ }
64
+ console.log(`\nAlbum: [${result.id}] ${result.title}`);
65
+ if (result.artists.length > 0)
66
+ console.log(` Artists: ${result.artists.join(', ')}`);
67
+ if (result.albumType)
68
+ console.log(` Type: ${result.albumType}`);
69
+ if (result.releaseDate)
70
+ console.log(` Release Date: ${result.releaseDate}`);
71
+ if (result.numberOfItems !== undefined)
72
+ console.log(` Tracks: ${result.numberOfItems}`);
73
+ if (result.duration)
74
+ console.log(` Duration: ${result.duration}`);
75
+ if (result.popularity !== undefined)
76
+ console.log(` Popularity: ${result.popularity}`);
77
+ if (result.explicit !== undefined)
78
+ console.log(` Explicit: ${result.explicit}`);
79
+ if (result.barcodeId)
80
+ console.log(` Barcode: ${result.barcodeId}`);
81
+ if (result.coverUrl)
82
+ console.log(` Cover: ${result.coverUrl}`);
83
+ console.log();
84
+ }
85
+ catch (err) {
86
+ console.error(`Error: ${err.message}`);
87
+ process.exit(1);
88
+ }
89
+ }
90
+ async function getAlbumByBarcodeData(barcode, client, countryCode) {
82
91
  const { data, error } = await client.GET('/albums', {
83
92
  params: {
84
93
  query: {
85
- countryCode: await (0, auth_1.getCountryCode)(),
94
+ countryCode,
86
95
  'filter[barcodeId]': [barcode],
87
96
  },
88
97
  },
89
98
  });
90
99
  if (error || !data) {
91
- console.error(`Error: Failed to get album by barcode — ${JSON.stringify(error)}`);
92
- process.exit(1);
100
+ throw new Error(`Failed to get album by barcode — ${JSON.stringify(error)}`);
93
101
  }
94
102
  const items = data.data ?? [];
95
- const albums = items.map((item) => {
103
+ return items.map((item) => {
96
104
  const attrs = item.attributes;
97
105
  return {
98
106
  id: item.id,
@@ -104,21 +112,32 @@ async function getAlbumByBarcode(barcode, json) {
104
112
  barcodeId: attrs?.barcodeId,
105
113
  };
106
114
  });
107
- if (json) {
108
- console.log(JSON.stringify(albums, null, 2));
109
- return;
110
- }
111
- if (albums.length === 0) {
112
- console.log(`No albums found for barcode ${barcode}.`);
113
- return;
115
+ }
116
+ async function getAlbumByBarcode(barcode, json) {
117
+ const client = await (0, auth_1.getApiClient)();
118
+ const countryCode = await (0, auth_1.getCountryCode)();
119
+ try {
120
+ const albums = await getAlbumByBarcodeData(barcode, client, countryCode);
121
+ if (json) {
122
+ console.log(JSON.stringify(albums, null, 2));
123
+ return;
124
+ }
125
+ if (albums.length === 0) {
126
+ console.log(`No albums found for barcode ${barcode}.`);
127
+ return;
128
+ }
129
+ console.log(`\nAlbums matching barcode ${barcode}:\n`);
130
+ for (const a of albums) {
131
+ const extras = [a.albumType, a.releaseDate, a.numberOfItems !== undefined ? `${a.numberOfItems} tracks` : undefined]
132
+ .filter(Boolean)
133
+ .join(', ');
134
+ console.log(` [${a.id}] ${a.title}${extras ? ` (${extras})` : ''}`);
135
+ }
136
+ console.log();
114
137
  }
115
- console.log(`\nAlbums matching barcode ${barcode}:\n`);
116
- for (const a of albums) {
117
- const extras = [a.albumType, a.releaseDate, a.numberOfItems !== undefined ? `${a.numberOfItems} tracks` : undefined]
118
- .filter(Boolean)
119
- .join(', ');
120
- console.log(` [${a.id}] ${a.title}${extras ? ` (${extras})` : ''}`);
138
+ catch (err) {
139
+ console.error(`Error: ${err.message}`);
140
+ process.exit(1);
121
141
  }
122
- console.log();
123
142
  }
124
143
  //# sourceMappingURL=album.js.map
package/dist/artist.d.ts CHANGED
@@ -1,5 +1,12 @@
1
+ import type { ArtistInfo, ArtistTrack, ArtistAlbum, SimilarArtist, RadioPlaylist } from './types';
2
+ export type { ArtistInfo, ArtistTrack, ArtistAlbum, SimilarArtist };
3
+ export declare function getArtistInfoData(artistId: string, client: any, countryCode: string): Promise<ArtistInfo>;
1
4
  export declare function getArtistInfo(artistId: string, json: boolean): Promise<void>;
5
+ export declare function getArtistRadioData(artistId: string, client: any, countryCode: string): Promise<RadioPlaylist[]>;
2
6
  export declare function getArtistRadio(artistId: string, json: boolean): Promise<void>;
7
+ export declare function getArtistTracksData(artistId: string, client: any, countryCode: string): Promise<ArtistTrack[]>;
3
8
  export declare function getArtistTracks(artistId: string, json: boolean): Promise<void>;
9
+ export declare function getArtistAlbumsData(artistId: string, client: any, countryCode: string): Promise<ArtistAlbum[]>;
4
10
  export declare function getArtistAlbums(artistId: string, json: boolean): Promise<void>;
11
+ export declare function getSimilarArtistsData(artistId: string, client: any, countryCode: string): Promise<SimilarArtist[]>;
5
12
  export declare function getSimilarArtists(artistId: string, json: boolean): Promise<void>;
package/dist/artist.js CHANGED
@@ -1,9 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getArtistInfoData = getArtistInfoData;
3
4
  exports.getArtistInfo = getArtistInfo;
5
+ exports.getArtistRadioData = getArtistRadioData;
4
6
  exports.getArtistRadio = getArtistRadio;
7
+ exports.getArtistTracksData = getArtistTracksData;
5
8
  exports.getArtistTracks = getArtistTracks;
9
+ exports.getArtistAlbumsData = getArtistAlbumsData;
6
10
  exports.getArtistAlbums = getArtistAlbums;
11
+ exports.getSimilarArtistsData = getSimilarArtistsData;
7
12
  exports.getSimilarArtists = getSimilarArtists;
8
13
  const auth_1 = require("./auth");
9
14
  function formatDuration(isoDuration) {
@@ -17,64 +22,70 @@ function formatDuration(isoDuration) {
17
22
  const s = (match[3] ?? '0').padStart(2, '0');
18
23
  return `${h}${m}:${s}`;
19
24
  }
20
- async function getArtistInfo(artistId, json) {
21
- const client = await (0, auth_1.getApiClient)();
25
+ async function getArtistInfoData(artistId, client, countryCode) {
22
26
  const { data, error } = await client.GET('/artists/{id}', {
23
27
  params: {
24
28
  path: { id: artistId },
25
29
  query: {
26
- countryCode: await (0, auth_1.getCountryCode)(),
30
+ countryCode,
27
31
  include: ['biography'],
28
32
  },
29
33
  },
30
34
  });
31
35
  if (error || !data) {
32
- console.error(`Error: Failed to get artist info — ${JSON.stringify(error)}`);
33
- process.exit(1);
36
+ throw new Error(`Failed to get artist info — ${JSON.stringify(error)}`);
34
37
  }
35
38
  const attrs = data.data?.attributes ?? {};
36
39
  const included = data.included ?? [];
37
40
  const biographyItem = included.find((item) => item.type === 'artistBiographies');
38
41
  const biographyText = biographyItem?.attributes?.text ?? attrs.biography?.text ?? attrs.biography;
39
- const result = {
42
+ return {
40
43
  id: artistId,
41
44
  name: attrs.name ?? 'Unknown',
42
45
  popularity: attrs.popularity,
43
46
  handle: attrs.handle,
44
47
  biography: biographyText,
45
48
  };
46
- if (json) {
47
- console.log(JSON.stringify(result, null, 2));
48
- return;
49
- }
50
- console.log(`\nArtist: [${result.id}] ${result.name}`);
51
- if (result.handle)
52
- console.log(` Handle: ${result.handle}`);
53
- if (result.popularity !== undefined)
54
- console.log(` Popularity: ${result.popularity}`);
55
- if (result.biography)
56
- console.log(` Biography: ${result.biography}`);
57
- console.log();
58
49
  }
59
- async function getArtistRadio(artistId, json) {
50
+ async function getArtistInfo(artistId, json) {
60
51
  const client = await (0, auth_1.getApiClient)();
52
+ const countryCode = await (0, auth_1.getCountryCode)();
53
+ try {
54
+ const result = await getArtistInfoData(artistId, client, countryCode);
55
+ if (json) {
56
+ console.log(JSON.stringify(result, null, 2));
57
+ return;
58
+ }
59
+ console.log(`\nArtist: [${result.id}] ${result.name}`);
60
+ if (result.handle)
61
+ console.log(` Handle: ${result.handle}`);
62
+ if (result.popularity !== undefined)
63
+ console.log(` Popularity: ${result.popularity}`);
64
+ if (result.biography)
65
+ console.log(` Biography: ${result.biography}`);
66
+ console.log();
67
+ }
68
+ catch (err) {
69
+ console.error(`Error: ${err.message}`);
70
+ process.exit(1);
71
+ }
72
+ }
73
+ async function getArtistRadioData(artistId, client, countryCode) {
61
74
  const { data, error } = await client.GET('/artists/{id}/relationships/radio', {
62
75
  params: {
63
76
  path: { id: artistId },
64
77
  query: {
65
- countryCode: await (0, auth_1.getCountryCode)(),
78
+ countryCode,
66
79
  include: ['radio'],
67
80
  },
68
81
  },
69
82
  });
70
83
  if (error || !data) {
71
- console.error(`Error: Failed to get artist radio — ${JSON.stringify(error)}`);
72
- process.exit(1);
84
+ throw new Error(`Failed to get artist radio — ${JSON.stringify(error)}`);
73
85
  }
74
- // Radio returns playlists (mix playlists), not individual tracks
75
86
  const radioData = data.data ?? [];
76
87
  const included = data.included ?? [];
77
- const playlists = radioData.map((item) => {
88
+ return radioData.map((item) => {
78
89
  const incl = included.find((i) => i.id === item.id && i.type === 'playlists');
79
90
  const attrs = incl?.attributes ?? {};
80
91
  return {
@@ -82,83 +93,100 @@ async function getArtistRadio(artistId, json) {
82
93
  type: item.type,
83
94
  name: attrs.name,
84
95
  numberOfItems: attrs.numberOfItems,
85
- description: attrs.description,
86
96
  };
87
97
  });
88
- if (json) {
89
- console.log(JSON.stringify(playlists, null, 2));
90
- return;
91
- }
92
- if (playlists.length === 0) {
93
- console.log(`No radio found for artist ${artistId}.`);
94
- return;
98
+ }
99
+ async function getArtistRadio(artistId, json) {
100
+ const client = await (0, auth_1.getApiClient)();
101
+ const countryCode = await (0, auth_1.getCountryCode)();
102
+ try {
103
+ const playlists = await getArtistRadioData(artistId, client, countryCode);
104
+ if (json) {
105
+ console.log(JSON.stringify(playlists, null, 2));
106
+ return;
107
+ }
108
+ if (playlists.length === 0) {
109
+ console.log(`No radio found for artist ${artistId}.`);
110
+ return;
111
+ }
112
+ console.log(`\nRadio for artist ${artistId}:\n`);
113
+ for (const p of playlists) {
114
+ console.log(` [${p.id}] ${p.name ?? 'Radio Mix'}${p.numberOfItems ? ` (${p.numberOfItems} tracks)` : ''}`);
115
+ }
116
+ console.log();
95
117
  }
96
- console.log(`\nRadio for artist ${artistId}:\n`);
97
- for (const p of playlists) {
98
- console.log(` [${p.id}] ${p.name ?? 'Radio Mix'}${p.numberOfItems ? ` (${p.numberOfItems} tracks)` : ''}`);
118
+ catch (err) {
119
+ console.error(`Error: ${err.message}`);
120
+ process.exit(1);
99
121
  }
100
- console.log();
101
122
  }
102
- async function getArtistTracks(artistId, json) {
103
- const client = await (0, auth_1.getApiClient)();
123
+ async function getArtistTracksData(artistId, client, countryCode) {
104
124
  const { data, error } = await client.GET('/artists/{id}/relationships/tracks', {
105
125
  params: {
106
126
  path: { id: artistId },
107
127
  query: {
108
- countryCode: await (0, auth_1.getCountryCode)(),
128
+ countryCode,
109
129
  'collapseBy': 'FINGERPRINT',
110
130
  include: ['tracks'],
111
131
  },
112
132
  },
113
133
  });
114
134
  if (error || !data) {
115
- console.error(`Error: Failed to get artist tracks — ${JSON.stringify(error)}`);
116
- process.exit(1);
135
+ throw new Error(`Failed to get artist tracks — ${JSON.stringify(error)}`);
117
136
  }
118
137
  const included = data.included ?? [];
119
- const tracks = included
138
+ return included
120
139
  .filter((item) => item.type === 'tracks')
121
140
  .map((item) => {
122
141
  const attrs = item.attributes;
123
142
  return {
124
143
  id: item.id,
125
144
  title: attrs?.title ?? 'Unknown',
126
- duration: attrs?.duration,
145
+ duration: formatDuration(attrs?.duration),
127
146
  isrc: attrs?.isrc,
128
147
  popularity: attrs?.popularity,
129
148
  };
130
149
  });
131
- if (json) {
132
- console.log(JSON.stringify(tracks, null, 2));
133
- return;
134
- }
135
- if (tracks.length === 0) {
136
- console.log(`No tracks found for artist ${artistId}.`);
137
- return;
150
+ }
151
+ async function getArtistTracks(artistId, json) {
152
+ const client = await (0, auth_1.getApiClient)();
153
+ const countryCode = await (0, auth_1.getCountryCode)();
154
+ try {
155
+ const tracks = await getArtistTracksData(artistId, client, countryCode);
156
+ if (json) {
157
+ console.log(JSON.stringify(tracks, null, 2));
158
+ return;
159
+ }
160
+ if (tracks.length === 0) {
161
+ console.log(`No tracks found for artist ${artistId}.`);
162
+ return;
163
+ }
164
+ console.log(`\nTracks for artist ${artistId}:\n`);
165
+ for (const t of tracks) {
166
+ console.log(` [${t.id}] ${t.title}${t.popularity !== undefined ? ` (popularity: ${t.popularity})` : ''}`);
167
+ }
168
+ console.log();
138
169
  }
139
- console.log(`\nTracks for artist ${artistId}:\n`);
140
- for (const t of tracks) {
141
- console.log(` [${t.id}] ${t.title}${t.popularity !== undefined ? ` (popularity: ${t.popularity})` : ''}`);
170
+ catch (err) {
171
+ console.error(`Error: ${err.message}`);
172
+ process.exit(1);
142
173
  }
143
- console.log();
144
174
  }
145
- async function getArtistAlbums(artistId, json) {
146
- const client = await (0, auth_1.getApiClient)();
175
+ async function getArtistAlbumsData(artistId, client, countryCode) {
147
176
  const { data, error } = await client.GET('/artists/{id}/relationships/albums', {
148
177
  params: {
149
178
  path: { id: artistId },
150
179
  query: {
151
- countryCode: await (0, auth_1.getCountryCode)(),
180
+ countryCode,
152
181
  include: ['albums'],
153
182
  },
154
183
  },
155
184
  });
156
185
  if (error || !data) {
157
- console.error(`Error: Failed to get artist albums — ${JSON.stringify(error)}`);
158
- process.exit(1);
186
+ throw new Error(`Failed to get artist albums — ${JSON.stringify(error)}`);
159
187
  }
160
188
  const included = data.included ?? [];
161
- const albums = included
189
+ return included
162
190
  .filter((item) => item.type === 'albums')
163
191
  .map((item) => {
164
192
  const attrs = item.attributes;
@@ -170,40 +198,49 @@ async function getArtistAlbums(artistId, json) {
170
198
  numberOfItems: attrs?.numberOfItems,
171
199
  };
172
200
  });
173
- if (json) {
174
- console.log(JSON.stringify(albums, null, 2));
175
- return;
176
- }
177
- if (albums.length === 0) {
178
- console.log(`No albums found for artist ${artistId}.`);
179
- return;
201
+ }
202
+ async function getArtistAlbums(artistId, json) {
203
+ const client = await (0, auth_1.getApiClient)();
204
+ const countryCode = await (0, auth_1.getCountryCode)();
205
+ try {
206
+ const albums = await getArtistAlbumsData(artistId, client, countryCode);
207
+ if (json) {
208
+ console.log(JSON.stringify(albums, null, 2));
209
+ return;
210
+ }
211
+ if (albums.length === 0) {
212
+ console.log(`No albums found for artist ${artistId}.`);
213
+ return;
214
+ }
215
+ console.log(`\nAlbums for artist ${artistId}:\n`);
216
+ for (const a of albums) {
217
+ const extras = [a.albumType, a.releaseDate, a.numberOfItems !== undefined ? `${a.numberOfItems} tracks` : undefined]
218
+ .filter(Boolean)
219
+ .join(', ');
220
+ console.log(` [${a.id}] ${a.title}${extras ? ` (${extras})` : ''}`);
221
+ }
222
+ console.log();
180
223
  }
181
- console.log(`\nAlbums for artist ${artistId}:\n`);
182
- for (const a of albums) {
183
- const extras = [a.albumType, a.releaseDate, a.numberOfItems !== undefined ? `${a.numberOfItems} tracks` : undefined]
184
- .filter(Boolean)
185
- .join(', ');
186
- console.log(` [${a.id}] ${a.title}${extras ? ` (${extras})` : ''}`);
224
+ catch (err) {
225
+ console.error(`Error: ${err.message}`);
226
+ process.exit(1);
187
227
  }
188
- console.log();
189
228
  }
190
- async function getSimilarArtists(artistId, json) {
191
- const client = await (0, auth_1.getApiClient)();
229
+ async function getSimilarArtistsData(artistId, client, countryCode) {
192
230
  const { data, error } = await client.GET('/artists/{id}/relationships/similarArtists', {
193
231
  params: {
194
232
  path: { id: artistId },
195
233
  query: {
196
- countryCode: await (0, auth_1.getCountryCode)(),
234
+ countryCode,
197
235
  include: ['similarArtists'],
198
236
  },
199
237
  },
200
238
  });
201
239
  if (error || !data) {
202
- console.error(`Error: Failed to get similar artists — ${JSON.stringify(error)}`);
203
- process.exit(1);
240
+ throw new Error(`Failed to get similar artists — ${JSON.stringify(error)}`);
204
241
  }
205
242
  const included = data.included ?? [];
206
- const artists = included
243
+ return included
207
244
  .filter((item) => item.type === 'artists')
208
245
  .map((item) => {
209
246
  const attrs = item.attributes;
@@ -213,18 +250,29 @@ async function getSimilarArtists(artistId, json) {
213
250
  popularity: attrs?.popularity,
214
251
  };
215
252
  });
216
- if (json) {
217
- console.log(JSON.stringify(artists, null, 2));
218
- return;
219
- }
220
- if (artists.length === 0) {
221
- console.log(`No similar artists found for artist ${artistId}.`);
222
- return;
253
+ }
254
+ async function getSimilarArtists(artistId, json) {
255
+ const client = await (0, auth_1.getApiClient)();
256
+ const countryCode = await (0, auth_1.getCountryCode)();
257
+ try {
258
+ const artists = await getSimilarArtistsData(artistId, client, countryCode);
259
+ if (json) {
260
+ console.log(JSON.stringify(artists, null, 2));
261
+ return;
262
+ }
263
+ if (artists.length === 0) {
264
+ console.log(`No similar artists found for artist ${artistId}.`);
265
+ return;
266
+ }
267
+ console.log(`\nSimilar artists to ${artistId}:\n`);
268
+ for (const a of artists) {
269
+ console.log(` [${a.id}] ${a.name}${a.popularity !== undefined ? ` (popularity: ${a.popularity})` : ''}`);
270
+ }
271
+ console.log();
223
272
  }
224
- console.log(`\nSimilar artists to ${artistId}:\n`);
225
- for (const a of artists) {
226
- console.log(` [${a.id}] ${a.name}${a.popularity !== undefined ? ` (popularity: ${a.popularity})` : ''}`);
273
+ catch (err) {
274
+ console.error(`Error: ${err.message}`);
275
+ process.exit(1);
227
276
  }
228
- console.log();
229
277
  }
230
278
  //# sourceMappingURL=artist.js.map
package/dist/history.d.ts CHANGED
@@ -1,3 +1,4 @@
1
- type RecentType = 'tracks' | 'albums' | 'artists';
1
+ import type { RecentItem, RecentType } from './types';
2
+ export type { RecentItem, RecentType };
3
+ export declare function getRecentlyAddedData(type: RecentType, client: any, countryCode: string): Promise<RecentItem[]>;
2
4
  export declare function getRecentlyAdded(type: RecentType, json: boolean): Promise<void>;
3
- export {};