@lucaperret/tidal-cli 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Luca Perret
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,204 @@
1
+ # tidal-cli
2
+
3
+ <p align="center">
4
+ <a href="https://tidal-cli.lucaperret.ch">
5
+ <img src="https://tidal-cli.lucaperret.ch/banner" alt="tidal-cli — Control Tidal from your terminal" width="100%" />
6
+ </a>
7
+ </p>
8
+
9
+ [![npm](https://img.shields.io/npm/v/@lucaperret/tidal-cli)](https://www.npmjs.com/package/@lucaperret/tidal-cli)
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
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
12
+ [![Node](https://img.shields.io/badge/node-%3E%3D20-green.svg)](https://nodejs.org)
13
+
14
+ ## About
15
+
16
+ tidal-cli wraps the [Tidal API v2](https://developer.tidal.com) into a single command-line tool. Search the catalog, manage playlists, explore artists, play tracks, and handle your library — all without opening a browser. Every command supports `--json` output, making it the backbone for LLM agent automation through [OpenClaw](https://openclaw.ai).
17
+
18
+ ## Features
19
+
20
+ - **Search** artists, albums, tracks, videos, playlists, and autocomplete suggestions
21
+ - **Artists** — info, top tracks, discography, similar artists, radio
22
+ - **Albums** — details, barcode lookup
23
+ - **Tracks** — info, similar tracks, ISRC lookup, radio
24
+ - **Playlists** — full CRUD, add/remove tracks, reorder, add entire albums
25
+ - **Library** — favorites for artists, albums, tracks, videos, playlists
26
+ - **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
29
+ - **JSON output** on every command for scripting and agent use
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ npm install -g @lucaperret/tidal-cli
35
+ ```
36
+
37
+ ### Requirements
38
+
39
+ - Node.js >= 20
40
+ - A [Tidal](https://tidal.com) account
41
+
42
+ ## Quick Start
43
+
44
+ ```bash
45
+ # Sign in once — opens your browser
46
+ tidal-cli auth
47
+
48
+ # Search for a track
49
+ tidal-cli search track "Around the World"
50
+
51
+ # Get artist details
52
+ tidal-cli artist info 8992
53
+
54
+ # Play a track
55
+ tidal-cli playback play 5756235
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ ### Search
61
+
62
+ ```bash
63
+ tidal-cli search artist "Gorillaz"
64
+ tidal-cli search album "Mezzanine"
65
+ tidal-cli search track "Teardrop"
66
+ tidal-cli search video "Stylo"
67
+ tidal-cli search playlist "Electronic"
68
+ tidal-cli search suggest "daft punk"
69
+ tidal-cli search editorial "indie rock"
70
+ ```
71
+
72
+ ### Artist
73
+
74
+ ```bash
75
+ tidal-cli artist info <id>
76
+ tidal-cli artist tracks <id>
77
+ tidal-cli artist albums <id>
78
+ tidal-cli artist similar <id>
79
+ tidal-cli artist radio <id>
80
+ ```
81
+
82
+ ### Album & Track
83
+
84
+ ```bash
85
+ tidal-cli album info <id>
86
+ tidal-cli album barcode <ean>
87
+ tidal-cli track info <id>
88
+ tidal-cli track similar <id>
89
+ tidal-cli track isrc <isrc>
90
+ tidal-cli track radio <id>
91
+ ```
92
+
93
+ ### Playlists
94
+
95
+ ```bash
96
+ tidal-cli playlist list
97
+ tidal-cli playlist create --name "Late Night Electronic"
98
+ tidal-cli playlist add-track --playlist-id <id> --track-id <id>
99
+ tidal-cli playlist add-album --playlist-id <id> --album-id <id>
100
+ tidal-cli playlist remove-track --playlist-id <id> --track-id <id>
101
+ tidal-cli playlist move-track --playlist-id <id> --track-id <id> --before <itemId>
102
+ tidal-cli playlist rename --playlist-id <id> --name "New Name"
103
+ tidal-cli playlist set-description --playlist-id <id> --desc "Updated description"
104
+ tidal-cli playlist delete --playlist-id <id>
105
+ ```
106
+
107
+ ### Library
108
+
109
+ ```bash
110
+ tidal-cli library add --track-id <id>
111
+ tidal-cli library add --artist-id <id>
112
+ tidal-cli library add --album-id <id>
113
+ tidal-cli library add --video-id <id>
114
+ tidal-cli library remove --track-id <id>
115
+ tidal-cli library favorite-playlists
116
+ tidal-cli library add-playlist --playlist-id <id>
117
+ tidal-cli library remove-playlist --playlist-id <id>
118
+ ```
119
+
120
+ ### Discovery & History
121
+
122
+ ```bash
123
+ tidal-cli recommend
124
+ tidal-cli history tracks
125
+ tidal-cli history albums
126
+ tidal-cli history artists
127
+ tidal-cli user profile
128
+ ```
129
+
130
+ ### Playback
131
+
132
+ ```bash
133
+ tidal-cli playback play <id>
134
+ tidal-cli playback play <id> --quality LOSSLESS
135
+ tidal-cli playback info <id>
136
+ tidal-cli playback url <id>
137
+ ```
138
+
139
+ Quality options: `LOW`, `HIGH`, `LOSSLESS`, `HI_RES`.
140
+
141
+ ## JSON Output
142
+
143
+ Add `--json` before any subcommand:
144
+
145
+ ```bash
146
+ tidal-cli --json search track "Around the World"
147
+ tidal-cli --json playlist list
148
+ tidal-cli --json artist similar 8992
149
+ ```
150
+
151
+ ## Agent Automation
152
+
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:
154
+
155
+ ```bash
156
+ clawhub install tidal-cli
157
+ ```
158
+
159
+ After `tidal-cli auth`, agents can run commands non-interactively with auto-refreshing tokens.
160
+
161
+ ### Example prompts for your AI agent
162
+
163
+ - "Create a playlist with the best tracks from Daft Punk's Discovery album"
164
+ - "Find artists similar to Massive Attack and add their top tracks to my library"
165
+ - "What are my playlists? Add the new LCD Soundsystem album to the first one"
166
+ - "Play me something by Boards of Canada"
167
+ - "Build a 2000s indie rock playlist with The Strokes, Arctic Monkeys, and Interpol"
168
+
169
+ ### Scripting patterns
170
+
171
+ ```bash
172
+ # Search then act
173
+ TRACK=$(tidal-cli --json search track "Around the World" | jq -r '.[0].id')
174
+ tidal-cli playlist add-track --playlist-id <id> --track-id "$TRACK"
175
+
176
+ # Discovery: artist → similar → top tracks → playlist
177
+ ARTIST=$(tidal-cli --json search artist "Boards of Canada" | jq -r '.[0].id')
178
+ SIMILAR=$(tidal-cli --json artist similar "$ARTIST" | jq -r '.[0].id')
179
+ TRACK=$(tidal-cli --json artist tracks "$SIMILAR" | jq -r '.[0].id')
180
+ tidal-cli playlist add-track --playlist-id <id> --track-id "$TRACK"
181
+ ```
182
+
183
+ ## Development
184
+
185
+ ```bash
186
+ git clone https://github.com/lucaperret/tidal-cli.git
187
+ cd tidal-cli
188
+ npm install
189
+ npm run build
190
+ npm test
191
+ ```
192
+
193
+ ### Running Tests
194
+
195
+ ```bash
196
+ npm test # run once
197
+ npm run test:watch # watch mode
198
+ ```
199
+
200
+ 111 tests covering search, playlists, artists, tracks, albums, library, auth, and session.
201
+
202
+ ## License
203
+
204
+ tidal-cli is licensed under the MIT License. See the [`LICENSE`](LICENSE) file for details.
@@ -0,0 +1,2 @@
1
+ export declare function getAlbumInfo(albumId: string, json: boolean): Promise<void>;
2
+ export declare function getAlbumByBarcode(barcode: string, json: boolean): Promise<void>;
package/dist/album.js ADDED
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAlbumInfo = getAlbumInfo;
4
+ exports.getAlbumByBarcode = getAlbumByBarcode;
5
+ const auth_1 = require("./auth");
6
+ function formatDuration(isoDuration) {
7
+ if (!isoDuration)
8
+ return '';
9
+ const match = isoDuration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/);
10
+ if (!match)
11
+ return isoDuration;
12
+ const h = match[1] ? `${match[1]}:` : '';
13
+ const m = (match[2] ?? '0').padStart(h ? 2 : 1, '0');
14
+ const s = (match[3] ?? '0').padStart(2, '0');
15
+ return `${h}${m}:${s}`;
16
+ }
17
+ async function getAlbumInfo(albumId, json) {
18
+ const client = await (0, auth_1.getApiClient)();
19
+ const { data, error } = await client.GET('/albums/{id}', {
20
+ params: {
21
+ path: { id: albumId },
22
+ query: {
23
+ countryCode: await (0, auth_1.getCountryCode)(),
24
+ include: ['artists', 'coverArt'],
25
+ },
26
+ },
27
+ });
28
+ if (error || !data) {
29
+ console.error(`Error: Failed to get album info — ${JSON.stringify(error)}`);
30
+ process.exit(1);
31
+ }
32
+ const attrs = data.data?.attributes ?? {};
33
+ const included = data.included ?? [];
34
+ const artists = included
35
+ .filter((item) => item.type === 'artists')
36
+ .map((item) => item.attributes?.name ?? item.id);
37
+ // Get cover art from included artworks
38
+ const artwork = included.find((item) => item.type === 'artworks');
39
+ const files = artwork?.attributes?.files ?? [];
40
+ const preferred = files.find((f) => f.meta?.width === 640) ?? files[0];
41
+ const coverUrl = preferred?.href;
42
+ const result = {
43
+ id: albumId,
44
+ title: attrs.title ?? 'Unknown',
45
+ artists,
46
+ albumType: attrs.albumType,
47
+ releaseDate: attrs.releaseDate,
48
+ numberOfItems: attrs.numberOfItems,
49
+ duration: formatDuration(attrs.duration),
50
+ popularity: attrs.popularity,
51
+ explicit: attrs.explicit,
52
+ barcodeId: attrs.barcodeId,
53
+ coverUrl,
54
+ };
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
+ }
80
+ async function getAlbumByBarcode(barcode, json) {
81
+ const client = await (0, auth_1.getApiClient)();
82
+ const { data, error } = await client.GET('/albums', {
83
+ params: {
84
+ query: {
85
+ countryCode: await (0, auth_1.getCountryCode)(),
86
+ 'filter[barcodeId]': [barcode],
87
+ },
88
+ },
89
+ });
90
+ if (error || !data) {
91
+ console.error(`Error: Failed to get album by barcode — ${JSON.stringify(error)}`);
92
+ process.exit(1);
93
+ }
94
+ const items = data.data ?? [];
95
+ const albums = items.map((item) => {
96
+ const attrs = item.attributes;
97
+ return {
98
+ id: item.id,
99
+ title: attrs?.title ?? 'Unknown',
100
+ albumType: attrs?.albumType,
101
+ releaseDate: attrs?.releaseDate,
102
+ numberOfItems: attrs?.numberOfItems,
103
+ duration: formatDuration(attrs?.duration),
104
+ barcodeId: attrs?.barcodeId,
105
+ };
106
+ });
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;
114
+ }
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})` : ''}`);
121
+ }
122
+ console.log();
123
+ }
124
+ //# sourceMappingURL=album.js.map
@@ -0,0 +1,5 @@
1
+ export declare function getArtistInfo(artistId: string, json: boolean): Promise<void>;
2
+ export declare function getArtistRadio(artistId: string, json: boolean): Promise<void>;
3
+ export declare function getArtistTracks(artistId: string, json: boolean): Promise<void>;
4
+ export declare function getArtistAlbums(artistId: string, json: boolean): Promise<void>;
5
+ export declare function getSimilarArtists(artistId: string, json: boolean): Promise<void>;
package/dist/artist.js ADDED
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getArtistInfo = getArtistInfo;
4
+ exports.getArtistRadio = getArtistRadio;
5
+ exports.getArtistTracks = getArtistTracks;
6
+ exports.getArtistAlbums = getArtistAlbums;
7
+ exports.getSimilarArtists = getSimilarArtists;
8
+ const auth_1 = require("./auth");
9
+ function formatDuration(isoDuration) {
10
+ if (!isoDuration)
11
+ return '';
12
+ const match = isoDuration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/);
13
+ if (!match)
14
+ return isoDuration;
15
+ const h = match[1] ? `${match[1]}:` : '';
16
+ const m = (match[2] ?? '0').padStart(h ? 2 : 1, '0');
17
+ const s = (match[3] ?? '0').padStart(2, '0');
18
+ return `${h}${m}:${s}`;
19
+ }
20
+ async function getArtistInfo(artistId, json) {
21
+ const client = await (0, auth_1.getApiClient)();
22
+ const { data, error } = await client.GET('/artists/{id}', {
23
+ params: {
24
+ path: { id: artistId },
25
+ query: {
26
+ countryCode: await (0, auth_1.getCountryCode)(),
27
+ include: ['biography'],
28
+ },
29
+ },
30
+ });
31
+ if (error || !data) {
32
+ console.error(`Error: Failed to get artist info — ${JSON.stringify(error)}`);
33
+ process.exit(1);
34
+ }
35
+ const attrs = data.data?.attributes ?? {};
36
+ const included = data.included ?? [];
37
+ const biographyItem = included.find((item) => item.type === 'artistBiographies');
38
+ const biographyText = biographyItem?.attributes?.text ?? attrs.biography?.text ?? attrs.biography;
39
+ const result = {
40
+ id: artistId,
41
+ name: attrs.name ?? 'Unknown',
42
+ popularity: attrs.popularity,
43
+ handle: attrs.handle,
44
+ biography: biographyText,
45
+ };
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
+ }
59
+ async function getArtistRadio(artistId, json) {
60
+ const client = await (0, auth_1.getApiClient)();
61
+ const { data, error } = await client.GET('/artists/{id}/relationships/radio', {
62
+ params: {
63
+ path: { id: artistId },
64
+ query: {
65
+ countryCode: await (0, auth_1.getCountryCode)(),
66
+ include: ['radio'],
67
+ },
68
+ },
69
+ });
70
+ if (error || !data) {
71
+ console.error(`Error: Failed to get artist radio — ${JSON.stringify(error)}`);
72
+ process.exit(1);
73
+ }
74
+ // Radio returns playlists (mix playlists), not individual tracks
75
+ const radioData = data.data ?? [];
76
+ const included = data.included ?? [];
77
+ const playlists = radioData.map((item) => {
78
+ const incl = included.find((i) => i.id === item.id && i.type === 'playlists');
79
+ const attrs = incl?.attributes ?? {};
80
+ return {
81
+ id: item.id,
82
+ type: item.type,
83
+ name: attrs.name,
84
+ numberOfItems: attrs.numberOfItems,
85
+ description: attrs.description,
86
+ };
87
+ });
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;
95
+ }
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)` : ''}`);
99
+ }
100
+ console.log();
101
+ }
102
+ async function getArtistTracks(artistId, json) {
103
+ const client = await (0, auth_1.getApiClient)();
104
+ const { data, error } = await client.GET('/artists/{id}/relationships/tracks', {
105
+ params: {
106
+ path: { id: artistId },
107
+ query: {
108
+ countryCode: await (0, auth_1.getCountryCode)(),
109
+ 'collapseBy': 'FINGERPRINT',
110
+ include: ['tracks'],
111
+ },
112
+ },
113
+ });
114
+ if (error || !data) {
115
+ console.error(`Error: Failed to get artist tracks — ${JSON.stringify(error)}`);
116
+ process.exit(1);
117
+ }
118
+ const included = data.included ?? [];
119
+ const tracks = included
120
+ .filter((item) => item.type === 'tracks')
121
+ .map((item) => {
122
+ const attrs = item.attributes;
123
+ return {
124
+ id: item.id,
125
+ title: attrs?.title ?? 'Unknown',
126
+ duration: attrs?.duration,
127
+ isrc: attrs?.isrc,
128
+ popularity: attrs?.popularity,
129
+ };
130
+ });
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;
138
+ }
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})` : ''}`);
142
+ }
143
+ console.log();
144
+ }
145
+ async function getArtistAlbums(artistId, json) {
146
+ const client = await (0, auth_1.getApiClient)();
147
+ const { data, error } = await client.GET('/artists/{id}/relationships/albums', {
148
+ params: {
149
+ path: { id: artistId },
150
+ query: {
151
+ countryCode: await (0, auth_1.getCountryCode)(),
152
+ include: ['albums'],
153
+ },
154
+ },
155
+ });
156
+ if (error || !data) {
157
+ console.error(`Error: Failed to get artist albums — ${JSON.stringify(error)}`);
158
+ process.exit(1);
159
+ }
160
+ const included = data.included ?? [];
161
+ const albums = included
162
+ .filter((item) => item.type === 'albums')
163
+ .map((item) => {
164
+ const attrs = item.attributes;
165
+ return {
166
+ id: item.id,
167
+ title: attrs?.title ?? 'Unknown',
168
+ albumType: attrs?.albumType,
169
+ releaseDate: attrs?.releaseDate,
170
+ numberOfItems: attrs?.numberOfItems,
171
+ };
172
+ });
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;
180
+ }
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})` : ''}`);
187
+ }
188
+ console.log();
189
+ }
190
+ async function getSimilarArtists(artistId, json) {
191
+ const client = await (0, auth_1.getApiClient)();
192
+ const { data, error } = await client.GET('/artists/{id}/relationships/similarArtists', {
193
+ params: {
194
+ path: { id: artistId },
195
+ query: {
196
+ countryCode: await (0, auth_1.getCountryCode)(),
197
+ include: ['similarArtists'],
198
+ },
199
+ },
200
+ });
201
+ if (error || !data) {
202
+ console.error(`Error: Failed to get similar artists — ${JSON.stringify(error)}`);
203
+ process.exit(1);
204
+ }
205
+ const included = data.included ?? [];
206
+ const artists = included
207
+ .filter((item) => item.type === 'artists')
208
+ .map((item) => {
209
+ const attrs = item.attributes;
210
+ return {
211
+ id: item.id,
212
+ name: attrs?.name ?? 'Unknown',
213
+ popularity: attrs?.popularity,
214
+ };
215
+ });
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;
223
+ }
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})` : ''}`);
227
+ }
228
+ console.log();
229
+ }
230
+ //# sourceMappingURL=artist.js.map
package/dist/auth.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export declare function ensureInit(): Promise<void>;
2
+ export declare function authenticate(): Promise<void>;
3
+ export declare function getApiClient(): Promise<any>;
4
+ export declare function getCountryCode(): Promise<string>;
5
+ export declare function doLogout(): Promise<void>;