@bzenky/spoti 0.2.0 → 0.3.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.
Files changed (61) hide show
  1. package/README.md +72 -4
  2. package/dist/app.d.ts +19 -1
  3. package/dist/app.js +310 -16
  4. package/dist/app.js.map +1 -1
  5. package/dist/auth/config.d.ts +1 -1
  6. package/dist/auth/config.js +3 -3
  7. package/dist/auth/config.js.map +1 -1
  8. package/dist/cli.js +53 -0
  9. package/dist/cli.js.map +1 -1
  10. package/dist/services/auth.service.d.ts +1 -1
  11. package/dist/services/auth.service.js +9 -3
  12. package/dist/services/auth.service.js.map +1 -1
  13. package/dist/services/catalog.service.d.ts +9 -0
  14. package/dist/services/catalog.service.js +22 -0
  15. package/dist/services/catalog.service.js.map +1 -0
  16. package/dist/services/device.service.js +22 -2
  17. package/dist/services/device.service.js.map +1 -1
  18. package/dist/services/library.service.d.ts +13 -0
  19. package/dist/services/library.service.js +46 -0
  20. package/dist/services/library.service.js.map +1 -0
  21. package/dist/services/mappers.d.ts +10 -0
  22. package/dist/services/mappers.js +104 -0
  23. package/dist/services/mappers.js.map +1 -0
  24. package/dist/services/models.d.ts +45 -0
  25. package/dist/services/player.service.d.ts +4 -1
  26. package/dist/services/player.service.js +21 -14
  27. package/dist/services/player.service.js.map +1 -1
  28. package/dist/services/playlist.service.d.ts +9 -0
  29. package/dist/services/playlist.service.js +33 -0
  30. package/dist/services/playlist.service.js.map +1 -0
  31. package/dist/services/recent.service.d.ts +7 -0
  32. package/dist/services/recent.service.js +24 -0
  33. package/dist/services/recent.service.js.map +1 -0
  34. package/dist/services/search.service.d.ts +5 -1
  35. package/dist/services/search.service.js +32 -17
  36. package/dist/services/search.service.js.map +1 -1
  37. package/dist/services/update.service.d.ts +57 -0
  38. package/dist/services/update.service.js +313 -0
  39. package/dist/services/update.service.js.map +1 -0
  40. package/dist/spotify/client.js +14 -1
  41. package/dist/spotify/client.js.map +1 -1
  42. package/dist/spotify/scopes.d.ts +1 -1
  43. package/dist/spotify/scopes.js +4 -0
  44. package/dist/spotify/scopes.js.map +1 -1
  45. package/dist/spotify/types.d.ts +110 -8
  46. package/dist/storage/config.d.ts +2 -1
  47. package/dist/storage/config.js +18 -1
  48. package/dist/storage/config.js.map +1 -1
  49. package/dist/storage/update-cache.d.ts +29 -0
  50. package/dist/storage/update-cache.js +135 -0
  51. package/dist/storage/update-cache.js.map +1 -0
  52. package/dist/ui/output.d.ts +10 -1
  53. package/dist/ui/output.js +60 -0
  54. package/dist/ui/output.js.map +1 -1
  55. package/dist/ui/prompts.d.ts +12 -1
  56. package/dist/ui/prompts.js +73 -6
  57. package/dist/ui/prompts.js.map +1 -1
  58. package/dist/update-worker.d.ts +9 -0
  59. package/dist/update-worker.js +91 -0
  60. package/dist/update-worker.js.map +1 -0
  61. package/package.json +1 -1
@@ -5,5 +5,9 @@ export const LEGACY_SPOTIFY_SCOPES = [
5
5
  export const SPOTIFY_SCOPES = [
6
6
  ...LEGACY_SPOTIFY_SCOPES,
7
7
  'user-read-currently-playing',
8
+ 'playlist-read-private',
9
+ 'user-library-read',
10
+ 'user-library-modify',
11
+ 'user-read-recently-played',
8
12
  ];
9
13
  //# sourceMappingURL=scopes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../src/spotify/scopes.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,0BAA0B;IAC1B,4BAA4B;CACpB,CAAC;AAEX,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,GAAG,qBAAqB;IACxB,6BAA6B;CACrB,CAAC"}
1
+ {"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../src/spotify/scopes.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,0BAA0B;IAC1B,4BAA4B;CACpB,CAAC;AAEX,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,GAAG,qBAAqB;IACxB,6BAA6B;IAC7B,uBAAuB;IACvB,mBAAmB;IACnB,qBAAqB;IACrB,2BAA2B;CACnB,CAAC"}
@@ -3,6 +3,9 @@ export interface SpotifyImage {
3
3
  height: number | null;
4
4
  width: number | null;
5
5
  }
6
+ export interface SpotifyExternalUrls {
7
+ spotify?: string;
8
+ }
6
9
  export interface SpotifyUserProfile {
7
10
  id: string;
8
11
  display_name: string | null;
@@ -10,26 +13,117 @@ export interface SpotifyUserProfile {
10
13
  }
11
14
  export interface SpotifyArtist {
12
15
  id: string;
16
+ uri?: string;
17
+ name: string;
18
+ external_urls?: SpotifyExternalUrls;
19
+ }
20
+ export interface SpotifyFullArtist extends SpotifyArtist {
21
+ uri: string;
22
+ images: SpotifyImage[];
23
+ }
24
+ export interface SpotifySimplifiedAlbum {
25
+ id?: string;
26
+ uri?: string;
13
27
  name: string;
28
+ album_type?: string;
29
+ artists?: SpotifyArtist[];
30
+ images: SpotifyImage[];
31
+ release_date?: string;
32
+ total_tracks?: number;
33
+ external_urls?: SpotifyExternalUrls;
14
34
  }
15
35
  export interface SpotifyTrack {
16
- id: string;
36
+ type?: 'track';
37
+ id: string | null;
17
38
  uri: string;
18
39
  name: string;
19
40
  duration_ms: number;
20
41
  artists: SpotifyArtist[];
21
- album: {
22
- name: string;
23
- images: SpotifyImage[];
42
+ album: SpotifySimplifiedAlbum;
43
+ is_local?: boolean;
44
+ is_playable?: boolean;
45
+ external_urls?: SpotifyExternalUrls;
46
+ }
47
+ export interface SpotifySimplifiedTrack {
48
+ type?: 'track';
49
+ id: string | null;
50
+ uri: string;
51
+ name: string;
52
+ duration_ms: number;
53
+ artists: SpotifyArtist[];
54
+ is_local?: boolean;
55
+ is_playable?: boolean;
56
+ external_urls?: SpotifyExternalUrls;
57
+ }
58
+ export interface SpotifyAlbum extends SpotifySimplifiedAlbum {
59
+ id: string;
60
+ uri: string;
61
+ artists: SpotifyArtist[];
62
+ tracks: SpotifyPaging<SpotifySimplifiedTrack>;
63
+ }
64
+ export interface SpotifyPlaylistOwner {
65
+ id: string;
66
+ display_name: string | null;
67
+ }
68
+ export interface SpotifySimplifiedPlaylist {
69
+ id: string;
70
+ uri: string;
71
+ name: string;
72
+ description: string | null;
73
+ public: boolean | null;
74
+ collaborative: boolean;
75
+ owner: SpotifyPlaylistOwner;
76
+ images: SpotifyImage[] | null;
77
+ external_urls?: SpotifyExternalUrls;
78
+ items: {
79
+ total: number;
24
80
  };
25
- external_urls?: {
26
- spotify?: string;
81
+ }
82
+ export interface SpotifyPlaylist extends SpotifySimplifiedPlaylist {
83
+ followers?: {
84
+ total: number;
85
+ };
86
+ }
87
+ export interface SpotifyEpisode {
88
+ type: 'episode';
89
+ id?: string | null;
90
+ uri?: string;
91
+ name?: string;
92
+ }
93
+ export type SpotifyPlaybackItem = SpotifyTrack | SpotifyEpisode | Record<string, unknown>;
94
+ export interface SpotifyPlaylistItem {
95
+ added_at: string | null;
96
+ is_local: boolean;
97
+ item: SpotifyPlaybackItem | null;
98
+ }
99
+ export interface SpotifyRecentlyPlayedItem {
100
+ track: SpotifyPlaybackItem | null;
101
+ played_at: string;
102
+ context: {
103
+ uri: string;
104
+ } | null;
105
+ }
106
+ export interface SpotifyPaging<T> {
107
+ items: T[];
108
+ limit: number;
109
+ offset: number;
110
+ total: number;
111
+ next: string | null;
112
+ previous: string | null;
113
+ }
114
+ export interface SpotifyCursorPaging<T> {
115
+ items: T[];
116
+ limit: number;
117
+ next: string | null;
118
+ cursors?: {
119
+ after?: string;
120
+ before?: string;
27
121
  };
28
122
  }
29
123
  export interface SpotifyPlaybackState {
30
124
  is_playing: boolean;
31
125
  progress_ms: number | null;
32
- item: SpotifyTrack | null;
126
+ item: SpotifyPlaybackItem | null;
33
127
  device: {
34
128
  id: string | null;
35
129
  name: string;
@@ -39,10 +133,18 @@ export interface SpotifyPlaybackState {
39
133
  };
40
134
  }
41
135
  export interface SpotifySearchResponse {
42
- tracks: {
136
+ tracks?: SpotifyPaging<SpotifyTrack> | {
43
137
  items: SpotifyTrack[];
44
138
  };
139
+ albums?: SpotifyPaging<SpotifySimplifiedAlbum>;
140
+ artists?: SpotifyPaging<SpotifyFullArtist>;
141
+ playlists?: SpotifyPaging<SpotifySimplifiedPlaylist | null>;
142
+ }
143
+ export interface SpotifySavedTrack {
144
+ added_at: string;
145
+ track: SpotifyTrack;
45
146
  }
147
+ export type SpotifyRecentlyPlayedResponse = SpotifyCursorPaging<SpotifyRecentlyPlayedItem>;
46
148
  export interface SpotifyErrorBody {
47
149
  error?: {
48
150
  status?: number;
@@ -1,10 +1,11 @@
1
1
  import { z } from 'zod';
2
2
  declare const configSchema: z.ZodObject<{
3
+ spotifyClientId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
3
4
  watchAfterPlay: z.ZodDefault<z.ZodBoolean>;
4
5
  refreshIntervalMs: z.ZodDefault<z.ZodNumber>;
5
6
  }, z.core.$strict>;
6
7
  export type AppConfig = z.infer<typeof configSchema>;
7
- export declare const CONFIG_KEYS: readonly ["watchAfterPlay", "refreshIntervalMs"];
8
+ export declare const CONFIG_KEYS: readonly ["spotifyClientId", "watchAfterPlay", "refreshIntervalMs"];
8
9
  export type ConfigKey = (typeof CONFIG_KEYS)[number];
9
10
  export declare const DEFAULT_CONFIG: Readonly<AppConfig>;
10
11
  export interface ConfigStore {
@@ -4,11 +4,22 @@ import { join } from 'node:path';
4
4
  import { z } from 'zod';
5
5
  import { ConfigurationError, toError } from '../utils/errors.js';
6
6
  import { getConfigDirectory } from './credentials.js';
7
+ const spotifyClientIdSchema = z
8
+ .string()
9
+ .trim()
10
+ .min(1)
11
+ .max(256)
12
+ .regex(/^[A-Za-z0-9]+$/);
7
13
  const configSchema = z.strictObject({
14
+ spotifyClientId: spotifyClientIdSchema.nullable().default(null),
8
15
  watchAfterPlay: z.boolean().default(false),
9
16
  refreshIntervalMs: z.number().int().min(1_000).max(30_000).default(1_000),
10
17
  });
11
- export const CONFIG_KEYS = ['watchAfterPlay', 'refreshIntervalMs'];
18
+ export const CONFIG_KEYS = [
19
+ 'spotifyClientId',
20
+ 'watchAfterPlay',
21
+ 'refreshIntervalMs',
22
+ ];
12
23
  export const DEFAULT_CONFIG = Object.freeze(configSchema.parse({}));
13
24
  export class FileConfigStore {
14
25
  path;
@@ -84,6 +95,12 @@ export class FileConfigStore {
84
95
  }
85
96
  }
86
97
  export function parseConfigValue(key, value) {
98
+ if (key === 'spotifyClientId') {
99
+ const clientId = spotifyClientIdSchema.safeParse(value);
100
+ if (clientId.success)
101
+ return clientId.data;
102
+ throw new ConfigurationError('Invalid Spotify client ID: expected a non-empty alphanumeric value.');
103
+ }
87
104
  if (key === 'watchAfterPlay') {
88
105
  if (value === 'true')
89
106
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/storage/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/storage/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ export interface SuccessfulUpdateCheckCache {
2
+ checkedAt: number;
3
+ currentVersion: string;
4
+ latestVersion: string;
5
+ }
6
+ export interface UpdateCache {
7
+ version: 1;
8
+ lastCheckedAt: number;
9
+ lastSuccessfulCheck?: SuccessfulUpdateCheckCache;
10
+ lastFailedCheckAt?: number;
11
+ }
12
+ export interface UpdateCacheStore {
13
+ read(): Promise<UpdateCache | null>;
14
+ write(cache: UpdateCache): Promise<void>;
15
+ }
16
+ export interface FileUpdateCacheStoreOptions {
17
+ directory?: string;
18
+ }
19
+ export declare class UpdateCacheError extends Error {
20
+ constructor(message: string, options?: ErrorOptions);
21
+ }
22
+ export declare class FileUpdateCacheStore implements UpdateCacheStore {
23
+ readonly path: string;
24
+ private readonly directory;
25
+ constructor(options?: FileUpdateCacheStoreOptions);
26
+ read(): Promise<UpdateCache | null>;
27
+ write(cache: UpdateCache): Promise<void>;
28
+ }
29
+ export declare function parseUpdateCache(value: unknown): UpdateCache;
@@ -0,0 +1,135 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { chmod, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { getConfigDirectory } from './credentials.js';
5
+ export class UpdateCacheError extends Error {
6
+ constructor(message, options) {
7
+ super(message, options);
8
+ this.name = 'UpdateCacheError';
9
+ }
10
+ }
11
+ export class FileUpdateCacheStore {
12
+ path;
13
+ directory;
14
+ constructor(options = {}) {
15
+ this.directory = options.directory ?? getConfigDirectory();
16
+ this.path = join(this.directory, 'update-cache.json');
17
+ }
18
+ async read() {
19
+ let contents;
20
+ try {
21
+ contents = await readFile(this.path, 'utf8');
22
+ }
23
+ catch (error) {
24
+ if (isFileNotFound(error))
25
+ return null;
26
+ throw new UpdateCacheError(`Unable to read the update cache: ${errorMessage(error)}`, {
27
+ cause: error,
28
+ });
29
+ }
30
+ try {
31
+ return parseUpdateCache(JSON.parse(contents));
32
+ }
33
+ catch {
34
+ // Cache data is disposable. Treat malformed or partially written data as a cache miss.
35
+ return null;
36
+ }
37
+ }
38
+ async write(cache) {
39
+ const validated = parseUpdateCache(cache);
40
+ try {
41
+ await mkdir(this.directory, { recursive: true, mode: 0o700 });
42
+ await chmod(this.directory, 0o700);
43
+ }
44
+ catch (error) {
45
+ throw new UpdateCacheError(`Unable to prepare the update cache directory: ${errorMessage(error)}`, {
46
+ cause: error,
47
+ });
48
+ }
49
+ const temporaryPath = join(this.directory, `.update-cache-${randomUUID()}.tmp`);
50
+ try {
51
+ await writeFile(temporaryPath, `${JSON.stringify(validated, null, 2)}\n`, {
52
+ encoding: 'utf8',
53
+ flag: 'wx',
54
+ mode: 0o600,
55
+ });
56
+ await chmod(temporaryPath, 0o600);
57
+ await rename(temporaryPath, this.path);
58
+ await chmod(this.path, 0o600);
59
+ }
60
+ catch (error) {
61
+ await unlink(temporaryPath).catch(() => undefined);
62
+ throw new UpdateCacheError(`Unable to store the update cache: ${errorMessage(error)}`, {
63
+ cause: error,
64
+ });
65
+ }
66
+ }
67
+ }
68
+ export function parseUpdateCache(value) {
69
+ if (!isRecord(value) || !hasOnlyKeys(value, CACHE_KEYS)) {
70
+ throw new UpdateCacheError('The update cache has an invalid format.');
71
+ }
72
+ if (value.version !== 1 || !isTimestamp(value.lastCheckedAt)) {
73
+ throw new UpdateCacheError('The update cache has an invalid format.');
74
+ }
75
+ const successful = parseSuccessfulCheck(value.lastSuccessfulCheck);
76
+ const failedAt = value.lastFailedCheckAt;
77
+ if (failedAt !== undefined && !isTimestamp(failedAt)) {
78
+ throw new UpdateCacheError('The update cache has an invalid format.');
79
+ }
80
+ if (!successful && failedAt === undefined) {
81
+ throw new UpdateCacheError('The update cache has an invalid format.');
82
+ }
83
+ const newestOutcomeAt = Math.max(successful?.checkedAt ?? -1, failedAt ?? -1);
84
+ if (value.lastCheckedAt !== newestOutcomeAt) {
85
+ throw new UpdateCacheError('The update cache has inconsistent timestamps.');
86
+ }
87
+ return {
88
+ version: 1,
89
+ lastCheckedAt: value.lastCheckedAt,
90
+ ...(successful ? { lastSuccessfulCheck: successful } : {}),
91
+ ...(failedAt === undefined ? {} : { lastFailedCheckAt: failedAt }),
92
+ };
93
+ }
94
+ const CACHE_KEYS = new Set([
95
+ 'version',
96
+ 'lastCheckedAt',
97
+ 'lastSuccessfulCheck',
98
+ 'lastFailedCheckAt',
99
+ ]);
100
+ const SUCCESS_KEYS = new Set(['checkedAt', 'currentVersion', 'latestVersion']);
101
+ function parseSuccessfulCheck(value) {
102
+ if (value === undefined)
103
+ return undefined;
104
+ if (!isRecord(value) ||
105
+ !hasOnlyKeys(value, SUCCESS_KEYS) ||
106
+ !isTimestamp(value.checkedAt) ||
107
+ !isNonemptyString(value.currentVersion) ||
108
+ !isNonemptyString(value.latestVersion)) {
109
+ throw new UpdateCacheError('The update cache has an invalid successful check.');
110
+ }
111
+ return {
112
+ checkedAt: value.checkedAt,
113
+ currentVersion: value.currentVersion,
114
+ latestVersion: value.latestVersion,
115
+ };
116
+ }
117
+ function isRecord(value) {
118
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
119
+ }
120
+ function hasOnlyKeys(value, allowed) {
121
+ return Object.keys(value).every((key) => allowed.has(key));
122
+ }
123
+ function isTimestamp(value) {
124
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
125
+ }
126
+ function isNonemptyString(value) {
127
+ return typeof value === 'string' && value.length > 0;
128
+ }
129
+ function isFileNotFound(error) {
130
+ return isRecord(error) && error.code === 'ENOENT';
131
+ }
132
+ function errorMessage(error) {
133
+ return error instanceof Error ? error.message : String(error);
134
+ }
135
+ //# sourceMappingURL=update-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-cache.js","sourceRoot":"","sources":["../../src/storage/update-cache.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { CurrentPlayback, Track } from '../services/models.js';
1
+ import type { Album, AlbumDetail, Artist, CurrentPlayback, Playlist, PlaylistDetail, RecentlyPlayedTrack, SavedTrack, Track } from '../services/models.js';
2
2
  export interface Output {
3
3
  log(message: string): void;
4
4
  error(message: string): void;
@@ -6,3 +6,12 @@ export interface Output {
6
6
  export declare const consoleOutput: Output;
7
7
  export declare function formatPlayback(playback: CurrentPlayback): string;
8
8
  export declare function formatTrack(track: Track, index?: number): string;
9
+ export declare function formatAlbum(album: Album, index?: number): string;
10
+ export declare function formatAlbumDetail(album: AlbumDetail): string;
11
+ export declare function formatArtist(artist: Artist, index?: number): string;
12
+ export declare function formatArtistDetail(artist: Artist): string;
13
+ export declare function formatPlaylist(playlist: Playlist, index?: number): string;
14
+ export declare function formatPlaylistOverview(playlist: Playlist): string;
15
+ export declare function formatPlaylistDetail(playlist: PlaylistDetail): string;
16
+ export declare function formatSavedTrack(item: SavedTrack, index?: number): string;
17
+ export declare function formatRecentlyPlayed(item: RecentlyPlayedTrack, index?: number): string;
package/dist/ui/output.js CHANGED
@@ -18,4 +18,64 @@ export function formatTrack(track, index) {
18
18
  const prefix = index === undefined ? '' : `${index + 1}. `;
19
19
  return `${prefix}${track.name} — ${track.artists.join(', ')} · ${track.album}`;
20
20
  }
21
+ export function formatAlbum(album, index) {
22
+ const prefix = index === undefined ? '' : `${index + 1}. `;
23
+ const release = album.releaseDate ? ` · ${album.releaseDate}` : '';
24
+ return `${prefix}${album.name} — ${album.artists.join(', ')} · ${album.totalTracks} tracks${release}`;
25
+ }
26
+ export function formatAlbumDetail(album) {
27
+ const lines = [formatAlbum(album), '', 'Tracks:'];
28
+ if (album.tracks.length === 0)
29
+ lines.push('No playable tracks found.');
30
+ else
31
+ lines.push(...album.tracks.map((track, index) => formatTrack(track, index)));
32
+ if (album.tracks.length < album.totalTracks) {
33
+ lines.push(`Showing ${album.tracks.length} of ${album.totalTracks} tracks.`);
34
+ }
35
+ if (album.externalUrl)
36
+ lines.push('', `Spotify: ${album.externalUrl}`);
37
+ return lines.join('\n');
38
+ }
39
+ export function formatArtist(artist, index) {
40
+ const prefix = index === undefined ? '' : `${index + 1}. `;
41
+ return `${prefix}${artist.name}`;
42
+ }
43
+ export function formatArtistDetail(artist) {
44
+ const lines = [formatArtist(artist)];
45
+ if (artist.externalUrl)
46
+ lines.push(`Spotify: ${artist.externalUrl}`);
47
+ return lines.join('\n');
48
+ }
49
+ export function formatPlaylist(playlist, index) {
50
+ const prefix = index === undefined ? '' : `${index + 1}. `;
51
+ const itemLabel = playlist.totalTracks === 1 ? 'item' : 'items';
52
+ return `${prefix}${playlist.name} — ${playlist.ownerName} · ${playlist.totalTracks} ${itemLabel}`;
53
+ }
54
+ export function formatPlaylistOverview(playlist) {
55
+ const lines = [formatPlaylist(playlist)];
56
+ if (playlist.description)
57
+ lines.push(playlist.description);
58
+ if (playlist.externalUrl)
59
+ lines.push(`Spotify: ${playlist.externalUrl}`);
60
+ return lines.join('\n');
61
+ }
62
+ export function formatPlaylistDetail(playlist) {
63
+ const lines = [formatPlaylist(playlist)];
64
+ if (playlist.description)
65
+ lines.push(playlist.description);
66
+ lines.push('', 'Tracks:');
67
+ if (playlist.tracks.length === 0)
68
+ lines.push('No playable tracks found.');
69
+ else
70
+ lines.push(...playlist.tracks.map((track, index) => formatTrack(track, index)));
71
+ if (playlist.externalUrl)
72
+ lines.push('', `Spotify: ${playlist.externalUrl}`);
73
+ return lines.join('\n');
74
+ }
75
+ export function formatSavedTrack(item, index) {
76
+ return `${formatTrack(item.track, index)} · liked ${item.addedAt}`;
77
+ }
78
+ export function formatRecentlyPlayed(item, index) {
79
+ return `${formatTrack(item.track, index)} · played ${item.playedAt}`;
80
+ }
21
81
  //# sourceMappingURL=output.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/ui/output.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOrE,MAAM,CAAC,MAAM,aAAa,GAAW;IACnC,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;IACtC,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;CAC3C,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,QAAyB;IACtD,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACjD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC1E,OAAO;QACL,GAAG,SAAS,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE;QACrC,GAAG,OAAO,MAAM,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE;QACtC,EAAE;QACF,GAAG,cAAc,CAAC,QAAQ,CAAC,IAAI,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,cAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE;KACrI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAY,EAAE,KAAc;IACtD,MAAM,MAAM,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,IAAI,CAAC;IAC3D,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;AACjF,CAAC"}
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/ui/output.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,13 @@
1
- import type { Track } from '../services/models.js';
1
+ import type { Album, Artist, Playlist, Track } from '../services/models.js';
2
2
  export declare function selectTrack(tracks: Track[]): Promise<Track | null>;
3
+ export declare function selectAlbum(albums: Album[]): Promise<Album | null>;
4
+ export declare function selectArtist(artists: Artist[]): Promise<Artist | null>;
5
+ export declare function selectPlaylist(playlists: Playlist[]): Promise<Playlist | null>;
6
+ export type AlbumAction = 'play-album' | 'play-track';
7
+ export type ArtistAction = 'play-artist' | 'select-album';
8
+ export type PlaylistAction = 'play-playlist';
9
+ export declare function selectAlbumAction(): Promise<AlbumAction | null>;
10
+ export declare function selectArtistAction(): Promise<ArtistAction | null>;
11
+ export declare function selectPlaylistAction(): Promise<PlaylistAction | null>;
12
+ export declare function promptSpotifyClientId(): Promise<string | null>;
13
+ export declare function confirmUpdate(currentVersion: string, latestVersion: string): Promise<boolean>;
@@ -1,19 +1,86 @@
1
1
  import { createInterface } from 'node:readline/promises';
2
2
  import { stdin, stdout } from 'node:process';
3
- import { formatTrack } from './output.js';
3
+ import { formatAlbum, formatArtist, formatPlaylist, formatTrack } from './output.js';
4
4
  export async function selectTrack(tracks) {
5
- if (tracks.length === 0)
5
+ return selectItem(tracks, formatTrack);
6
+ }
7
+ export async function selectAlbum(albums) {
8
+ return selectItem(albums, formatAlbum);
9
+ }
10
+ export async function selectArtist(artists) {
11
+ return selectItem(artists, formatArtist);
12
+ }
13
+ export async function selectPlaylist(playlists) {
14
+ return selectItem(playlists, formatPlaylist);
15
+ }
16
+ export async function selectAlbumAction() {
17
+ return selectAction([
18
+ { value: 'play-album', label: 'Play the entire album' },
19
+ { value: 'play-track', label: 'Select a track' },
20
+ ]);
21
+ }
22
+ export async function selectArtistAction() {
23
+ return selectAction([
24
+ { value: 'play-artist', label: 'Play the artist' },
25
+ { value: 'select-album', label: 'Select an album' },
26
+ ]);
27
+ }
28
+ export async function selectPlaylistAction() {
29
+ return selectAction([{ value: 'play-playlist', label: 'Play the playlist' }]);
30
+ }
31
+ export async function promptSpotifyClientId() {
32
+ if (!stdin.isTTY || !stdout.isTTY)
33
+ return null;
34
+ const prompt = createInterface({ input: stdin, output: stdout });
35
+ try {
36
+ const answer = await prompt.question('Spotify Client ID: ');
37
+ return answer.trim() || null;
38
+ }
39
+ finally {
40
+ prompt.close();
41
+ }
42
+ }
43
+ export async function confirmUpdate(currentVersion, latestVersion) {
44
+ if (!stdin.isTTY || !stdout.isTTY)
45
+ return false;
46
+ const prompt = createInterface({ input: stdin, output: stdout });
47
+ try {
48
+ const answer = await prompt.question(`Update spoti ${currentVersion} → ${latestVersion} with npm? [Y/n] `);
49
+ return answer.trim() === '' || /^y(?:es)?$/i.test(answer.trim());
50
+ }
51
+ finally {
52
+ prompt.close();
53
+ }
54
+ }
55
+ async function selectAction(actions) {
56
+ if (!stdin.isTTY || !stdout.isTTY)
57
+ return null;
58
+ stdout.write(`\nWhat do you want to play?\n\n${actions
59
+ .map((action, index) => `${index + 1}. ${action.label}`)
60
+ .join('\n')}\n${actions.length + 1}. Nothing\n\n`);
61
+ const prompt = createInterface({ input: stdin, output: stdout });
62
+ try {
63
+ const answer = await prompt.question(`Select [1-${actions.length + 1}]: `);
64
+ const index = Number.parseInt(answer, 10) - 1;
65
+ return actions[index]?.value ?? null;
66
+ }
67
+ finally {
68
+ prompt.close();
69
+ }
70
+ }
71
+ async function selectItem(items, format) {
72
+ if (items.length === 0)
6
73
  return null;
7
74
  if (!stdin.isTTY || !stdout.isTTY)
8
- return tracks[0] ?? null;
9
- stdout.write(`${tracks.map((track, index) => formatTrack(track, index)).join('\n')}\n\n`);
75
+ return items[0] ?? null;
76
+ stdout.write(`${items.map((item, index) => format(item, index)).join('\n')}\n\n`);
10
77
  const prompt = createInterface({ input: stdin, output: stdout });
11
78
  try {
12
- const answer = await prompt.question(`Select [1-${tracks.length}] (Enter to cancel): `);
79
+ const answer = await prompt.question(`Select [1-${items.length}] (Enter to cancel): `);
13
80
  if (!answer.trim())
14
81
  return null;
15
82
  const index = Number.parseInt(answer, 10) - 1;
16
- return tracks[index] ?? null;
83
+ return items[index] ?? null;
17
84
  }
18
85
  finally {
19
86
  prompt.close();
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/ui/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG7C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAe;IAC/C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAE5D,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1F,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACjE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,aAAa,MAAM,CAAC,MAAM,uBAAuB,CAAC,CAAC;QACxF,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;QAC9C,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC;IAC/B,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/ui/prompts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { type UpdateChecker, type UpdateCheckResult } from './services/update.service.js';
2
+ /** Automatic update failures are intentionally hidden from normal CLI commands. */
3
+ export declare function runBackgroundUpdateCheck(checker: UpdateChecker): Promise<UpdateCheckResult | null>;
4
+ /**
5
+ * Starts at most one independent refresh process. The process can finish after the invoking
6
+ * CLI exits and writes the cache for a later invocation to display.
7
+ */
8
+ export declare function startUpdateWorker(currentVersion: string, cachePath: string): Promise<boolean>;
9
+ export declare function acquireUpdateWorkerLock(lockPath: string): Promise<boolean>;