@bzenky/spoti 0.3.0 → 0.5.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/README.md +68 -6
- package/dist/app.d.ts +10 -2
- package/dist/app.js +207 -82
- package/dist/app.js.map +1 -1
- package/dist/cli.js +4 -2
- package/dist/cli.js.map +1 -1
- package/dist/services/device.service.d.ts +3 -3
- package/dist/services/device.service.js +19 -11
- package/dist/services/device.service.js.map +1 -1
- package/dist/services/player.service.d.ts +4 -2
- package/dist/services/player.service.js +35 -5
- package/dist/services/player.service.js.map +1 -1
- package/dist/services/queue.service.js +11 -6
- package/dist/services/queue.service.js.map +1 -1
- package/dist/services/search.service.d.ts +4 -4
- package/dist/services/search.service.js +10 -9
- package/dist/services/search.service.js.map +1 -1
- package/dist/spotify/client.d.ts +2 -0
- package/dist/spotify/client.js +170 -46
- package/dist/spotify/client.js.map +1 -1
- package/dist/spotify/types.d.ts +2 -0
- package/dist/storage/config.d.ts +3 -0
- package/dist/storage/config.js +3 -0
- package/dist/storage/config.js.map +1 -1
- package/dist/ui/completions.d.ts +3 -0
- package/dist/ui/completions.js +237 -0
- package/dist/ui/completions.js.map +1 -0
- package/dist/ui/interactive-search.d.ts +36 -0
- package/dist/ui/interactive-search.js +337 -0
- package/dist/ui/interactive-search.js.map +1 -0
- package/dist/ui/output.d.ts +27 -11
- package/dist/ui/output.js +87 -45
- package/dist/ui/output.js.map +1 -1
- package/dist/ui/progress.d.ts +6 -0
- package/dist/ui/progress.js +25 -0
- package/dist/ui/progress.js.map +1 -0
- package/dist/ui/prompts.d.ts +11 -1
- package/dist/ui/prompts.js +65 -13
- package/dist/ui/prompts.js.map +1 -1
- package/dist/ui/watch.js +3 -2
- package/dist/ui/watch.js.map +1 -1
- package/dist/utils/errors.js +2 -1
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/text.d.ts +2 -0
- package/dist/utils/text.js +10 -0
- package/dist/utils/text.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type ProgressRunner = <Result>(label: string, task: () => Promise<Result>) => Promise<Result>;
|
|
2
|
+
export interface ProgressTerminal {
|
|
3
|
+
readonly isTTY: boolean;
|
|
4
|
+
write(value: string): void;
|
|
5
|
+
}
|
|
6
|
+
export declare function withProgress<Result>(label: string, task: () => Promise<Result>, terminal?: ProgressTerminal): Promise<Result>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { stderr } from 'node:process';
|
|
2
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
3
|
+
const defaultTerminal = {
|
|
4
|
+
isTTY: Boolean(stderr.isTTY),
|
|
5
|
+
write: (value) => stderr.write(value),
|
|
6
|
+
};
|
|
7
|
+
export async function withProgress(label, task, terminal = defaultTerminal) {
|
|
8
|
+
if (!terminal.isTTY)
|
|
9
|
+
return task();
|
|
10
|
+
let frameIndex = 0;
|
|
11
|
+
terminal.write(`${frames[frameIndex]} ${label}`);
|
|
12
|
+
const timer = setInterval(() => {
|
|
13
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
14
|
+
terminal.write(`\r${frames[frameIndex]} ${label}`);
|
|
15
|
+
}, 80);
|
|
16
|
+
timer.unref();
|
|
17
|
+
try {
|
|
18
|
+
return await task();
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
clearInterval(timer);
|
|
22
|
+
terminal.write('\r\u001B[2K');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.js","sourceRoot":"","sources":["../../src/ui/progress.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAYtC,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAC;AAE3E,MAAM,eAAe,GAAqB;IACxC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;IAC5B,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;CACtC,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAa,EACb,IAA2B,EAC3B,WAA6B,eAAe;IAE5C,IAAI,CAAC,QAAQ,CAAC,KAAK;QAAE,OAAO,IAAI,EAAE,CAAC;IAEnC,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,QAAQ,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,UAAU,GAAG,CAAC,UAAU,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;QAC9C,QAAQ,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,UAAU,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;IACrD,CAAC,EAAE,EAAE,CAAC,CAAC;IACP,KAAK,CAAC,KAAK,EAAE,CAAC;IAEd,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,EAAE,CAAC;IACtB,CAAC;YAAS,CAAC;QACT,aAAa,CAAC,KAAK,CAAC,CAAC;QACrB,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAChC,CAAC;AACH,CAAC"}
|
package/dist/ui/prompts.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import type { Album, Artist, Playlist, Track } from '../services/models.js';
|
|
1
|
+
import type { Album, Artist, Playlist, RecentlyPlayedTrack, SavedTrack, Track } from '../services/models.js';
|
|
2
2
|
export declare function selectTrack(tracks: Track[]): Promise<Track | null>;
|
|
3
3
|
export declare function selectAlbum(albums: Album[]): Promise<Album | null>;
|
|
4
4
|
export declare function selectArtist(artists: Artist[]): Promise<Artist | null>;
|
|
5
5
|
export declare function selectPlaylist(playlists: Playlist[]): Promise<Playlist | null>;
|
|
6
|
+
export declare function selectRecentTrack(items: RecentlyPlayedTrack[]): Promise<Track | null>;
|
|
7
|
+
export declare function selectLikedTrack(items: SavedTrack[]): Promise<Track | null>;
|
|
8
|
+
export declare function selectListedPlaylist(playlists: Playlist[]): Promise<Playlist | null>;
|
|
6
9
|
export type AlbumAction = 'play-album' | 'play-track';
|
|
7
10
|
export type ArtistAction = 'play-artist' | 'select-album';
|
|
8
11
|
export type PlaylistAction = 'play-playlist';
|
|
@@ -11,3 +14,10 @@ export declare function selectArtistAction(): Promise<ArtistAction | null>;
|
|
|
11
14
|
export declare function selectPlaylistAction(): Promise<PlaylistAction | null>;
|
|
12
15
|
export declare function promptSpotifyClientId(): Promise<string | null>;
|
|
13
16
|
export declare function confirmUpdate(currentVersion: string, latestVersion: string): Promise<boolean>;
|
|
17
|
+
export type SelectionInput = {
|
|
18
|
+
status: 'cancelled' | 'invalid';
|
|
19
|
+
} | {
|
|
20
|
+
status: 'selected';
|
|
21
|
+
index: number;
|
|
22
|
+
};
|
|
23
|
+
export declare function parseSelectionInput(value: string, itemCount: number): SelectionInput;
|
package/dist/ui/prompts.js
CHANGED
|
@@ -1,17 +1,39 @@
|
|
|
1
1
|
import { createInterface } from 'node:readline/promises';
|
|
2
2
|
import { stdin, stdout } from 'node:process';
|
|
3
|
-
import { formatAlbum, formatArtist, formatPlaylist, formatTrack } from './output.js';
|
|
3
|
+
import { createOutputStyles, formatAlbum, formatArtist, formatPlaylist, formatTrack, } from './output.js';
|
|
4
4
|
export async function selectTrack(tracks) {
|
|
5
|
-
|
|
5
|
+
const styles = createOutputStyles(Boolean(stdout.isTTY), process.env);
|
|
6
|
+
return selectItem(tracks, (track, index) => formatTrack(track, index, styles));
|
|
6
7
|
}
|
|
7
8
|
export async function selectAlbum(albums) {
|
|
8
|
-
|
|
9
|
+
const styles = createOutputStyles(Boolean(stdout.isTTY), process.env);
|
|
10
|
+
return selectItem(albums, (album, index) => formatAlbum(album, index, styles));
|
|
9
11
|
}
|
|
10
12
|
export async function selectArtist(artists) {
|
|
11
|
-
|
|
13
|
+
const styles = createOutputStyles(Boolean(stdout.isTTY), process.env);
|
|
14
|
+
return selectItem(artists, (artist, index) => formatArtist(artist, index, styles));
|
|
12
15
|
}
|
|
13
16
|
export async function selectPlaylist(playlists) {
|
|
14
|
-
|
|
17
|
+
const styles = createOutputStyles(Boolean(stdout.isTTY), process.env);
|
|
18
|
+
return selectItem(playlists, (playlist, index) => formatPlaylist(playlist, index, styles));
|
|
19
|
+
}
|
|
20
|
+
export async function selectRecentTrack(items) {
|
|
21
|
+
return selectDisplayedTrack(items.map((item) => item.track), 'recent');
|
|
22
|
+
}
|
|
23
|
+
export async function selectLikedTrack(items) {
|
|
24
|
+
return selectDisplayedTrack(items.map((item) => item.track), 'liked');
|
|
25
|
+
}
|
|
26
|
+
export async function selectListedPlaylist(playlists) {
|
|
27
|
+
if (playlists.length === 0 || !stdin.isTTY || !stdout.isTTY)
|
|
28
|
+
return null;
|
|
29
|
+
const prompt = createInterface({ input: stdin, output: stdout });
|
|
30
|
+
try {
|
|
31
|
+
const index = await promptForSelection(prompt, `\nPlay a playlist [1-${playlists.length}] (Enter to keep current playback): `, playlists.length);
|
|
32
|
+
return index === null ? null : (playlists[index] ?? null);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
prompt.close();
|
|
36
|
+
}
|
|
15
37
|
}
|
|
16
38
|
export async function selectAlbumAction() {
|
|
17
39
|
return selectAction([
|
|
@@ -60,9 +82,20 @@ async function selectAction(actions) {
|
|
|
60
82
|
.join('\n')}\n${actions.length + 1}. Nothing\n\n`);
|
|
61
83
|
const prompt = createInterface({ input: stdin, output: stdout });
|
|
62
84
|
try {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
85
|
+
const index = await promptForSelection(prompt, `Select [1-${actions.length + 1}]: `, actions.length + 1);
|
|
86
|
+
return index === null ? null : (actions[index]?.value ?? null);
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
prompt.close();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function selectDisplayedTrack(tracks, source) {
|
|
93
|
+
if (tracks.length === 0 || !stdin.isTTY || !stdout.isTTY)
|
|
94
|
+
return null;
|
|
95
|
+
const prompt = createInterface({ input: stdin, output: stdout });
|
|
96
|
+
try {
|
|
97
|
+
const index = await promptForSelection(prompt, `\nPlay a ${source} track [1-${tracks.length}] (Enter to keep current playback): `, tracks.length);
|
|
98
|
+
return index === null ? null : (tracks[index] ?? null);
|
|
66
99
|
}
|
|
67
100
|
finally {
|
|
68
101
|
prompt.close();
|
|
@@ -76,14 +109,33 @@ async function selectItem(items, format) {
|
|
|
76
109
|
stdout.write(`${items.map((item, index) => format(item, index)).join('\n')}\n\n`);
|
|
77
110
|
const prompt = createInterface({ input: stdin, output: stdout });
|
|
78
111
|
try {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
return null;
|
|
82
|
-
const index = Number.parseInt(answer, 10) - 1;
|
|
83
|
-
return items[index] ?? null;
|
|
112
|
+
const index = await promptForSelection(prompt, `Select [1-${items.length}] (Enter to cancel): `, items.length);
|
|
113
|
+
return index === null ? null : (items[index] ?? null);
|
|
84
114
|
}
|
|
85
115
|
finally {
|
|
86
116
|
prompt.close();
|
|
87
117
|
}
|
|
88
118
|
}
|
|
119
|
+
export function parseSelectionInput(value, itemCount) {
|
|
120
|
+
const normalized = value.trim();
|
|
121
|
+
if (!normalized)
|
|
122
|
+
return { status: 'cancelled' };
|
|
123
|
+
if (!/^\d+$/.test(normalized))
|
|
124
|
+
return { status: 'invalid' };
|
|
125
|
+
const index = Number(normalized) - 1;
|
|
126
|
+
if (!Number.isSafeInteger(index) || index < 0 || index >= itemCount) {
|
|
127
|
+
return { status: 'invalid' };
|
|
128
|
+
}
|
|
129
|
+
return { status: 'selected', index };
|
|
130
|
+
}
|
|
131
|
+
async function promptForSelection(prompt, question, itemCount) {
|
|
132
|
+
while (true) {
|
|
133
|
+
const result = parseSelectionInput(await prompt.question(question), itemCount);
|
|
134
|
+
if (result.status === 'cancelled')
|
|
135
|
+
return null;
|
|
136
|
+
if (result.status === 'selected')
|
|
137
|
+
return result.index;
|
|
138
|
+
stdout.write(`Selection must be a number between 1 and ${itemCount}.\n`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
89
141
|
//# sourceMappingURL=prompts.js.map
|
package/dist/ui/prompts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/ui/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/ui/prompts.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/watch.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { stdin, stdout } from 'node:process';
|
|
2
2
|
import { AppError } from '../utils/errors.js';
|
|
3
|
-
import { formatPlayback } from './output.js';
|
|
3
|
+
import { createOutputStyles, formatPlayback } from './output.js';
|
|
4
4
|
export const watchPlayback = async ({ player, refreshIntervalMs }) => {
|
|
5
5
|
if (!stdin.isTTY || !stdout.isTTY) {
|
|
6
6
|
throw new AppError('Watch mode requires an interactive terminal.');
|
|
7
7
|
}
|
|
8
|
+
const styles = createOutputStyles(true, process.env);
|
|
8
9
|
let stopped = false;
|
|
9
10
|
let resolveStop = () => undefined;
|
|
10
11
|
const stopPromise = new Promise((resolve) => {
|
|
@@ -20,7 +21,7 @@ export const watchPlayback = async ({ player, refreshIntervalMs }) => {
|
|
|
20
21
|
while (!stopped) {
|
|
21
22
|
const playback = await player.getCurrentPlayback();
|
|
22
23
|
const content = playback
|
|
23
|
-
? formatPlayback(playback)
|
|
24
|
+
? formatPlayback(playback, styles)
|
|
24
25
|
: 'Nothing is currently playing.';
|
|
25
26
|
stdout.write(`\u001B[2J\u001B[H${content}\n\nPress Ctrl+C to exit`);
|
|
26
27
|
if (!stopped)
|
package/dist/ui/watch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/ui/watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG7C,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/ui/watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG7C,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AASjE,MAAM,CAAC,MAAM,aAAa,GAAoB,KAAK,EAAE,EAAE,MAAM,EAAE,iBAAiB,EAAE,EAAE,EAAE;IACpF,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAClC,MAAM,IAAI,QAAQ,CAAC,8CAA8C,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACrD,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,WAAW,GAAe,GAAG,EAAE,CAAC,SAAS,CAAC;IAC9C,MAAM,WAAW,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAChD,WAAW,GAAG,OAAO,CAAC;IACxB,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,GAAS,EAAE;QACtB,OAAO,GAAG,IAAI,CAAC;QACf,WAAW,EAAE,CAAC;IAChB,CAAC,CAAC;IAEF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC7B,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAEzC,IAAI,CAAC;QACH,OAAO,CAAC,OAAO,EAAE,CAAC;YAChB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,kBAAkB,EAAE,CAAC;YACnD,MAAM,OAAO,GAAG,QAAQ;gBACtB,CAAC,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAClC,CAAC,CAAC,+BAA+B,CAAC;YACpC,MAAM,CAAC,KAAK,CAAC,oBAAoB,OAAO,0BAA0B,CAAC,CAAC;YACpE,IAAI,CAAC,OAAO;gBAAE,MAAM,cAAc,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC,CAAC;AAEF,KAAK,UAAU,cAAc,CAAC,YAAoB,EAAE,WAA0B;IAC5E,IAAI,KAAiC,CAAC;IACtC,MAAM,YAAY,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACjD,KAAK,GAAG,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IACH,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC;IAChD,IAAI,KAAK;QAAE,YAAY,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC"}
|
package/dist/utils/errors.js
CHANGED
|
@@ -28,7 +28,8 @@ export class PremiumRequiredError extends AppError {
|
|
|
28
28
|
export class RateLimitedError extends AppError {
|
|
29
29
|
retryAfterSeconds;
|
|
30
30
|
constructor(retryAfterSeconds) {
|
|
31
|
-
|
|
31
|
+
const unit = retryAfterSeconds === 1 ? 'second' : 'seconds';
|
|
32
|
+
super(`Spotify rate limit reached. Try again in ${retryAfterSeconds} ${unit}.`);
|
|
32
33
|
this.retryAfterSeconds = retryAfterSeconds;
|
|
33
34
|
}
|
|
34
35
|
}
|
package/dist/utils/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGf;IAFlB,YACE,OAAe,EACC,WAAW,CAAC;QAE5B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,aAAQ,GAAR,QAAQ,CAAI;QAG5B,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,QAAQ;CAAG;AAEnD,MAAM,OAAO,2BAA4B,SAAQ,QAAQ;IACvD,YAAY,OAAO,GAAG,4CAA4C;QAChE,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;CACF;AAED,MAAM,OAAO,wBAAyB,SAAQ,QAAQ;CAAG;AAEzD,MAAM,OAAO,mBAAoB,SAAQ,QAAQ;IAC/C;QACE,KAAK,CACH,uFAAuF,CACxF,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,oBAAqB,SAAQ,QAAQ;IAChD;QACE,KAAK,CAAC,mDAAmD,CAAC,CAAC;IAC7D,CAAC;CACF;AAED,MAAM,OAAO,gBAAiB,SAAQ,QAAQ;IAChB;IAA5B,YAA4B,iBAAyB;QACnD,KAAK,CAAC,4CAA4C,iBAAiB,
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGf;IAFlB,YACE,OAAe,EACC,WAAW,CAAC;QAE5B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,aAAQ,GAAR,QAAQ,CAAI;QAG5B,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,QAAQ;CAAG;AAEnD,MAAM,OAAO,2BAA4B,SAAQ,QAAQ;IACvD,YAAY,OAAO,GAAG,4CAA4C;QAChE,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;CACF;AAED,MAAM,OAAO,wBAAyB,SAAQ,QAAQ;CAAG;AAEzD,MAAM,OAAO,mBAAoB,SAAQ,QAAQ;IAC/C;QACE,KAAK,CACH,uFAAuF,CACxF,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,oBAAqB,SAAQ,QAAQ;IAChD;QACE,KAAK,CAAC,mDAAmD,CAAC,CAAC;IAC7D,CAAC;CACF;AAED,MAAM,OAAO,gBAAiB,SAAQ,QAAQ;IAChB;IAA5B,YAA4B,iBAAyB;QACnD,MAAM,IAAI,GAAG,iBAAiB,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5D,KAAK,CAAC,4CAA4C,iBAAiB,IAAI,IAAI,GAAG,CAAC,CAAC;QAFtD,sBAAiB,GAAjB,iBAAiB,CAAQ;IAGrD,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,QAAQ;IAGzB;IAFlB,YACE,OAAe,EACC,MAAc;QAE9B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,WAAM,GAAN,MAAM,CAAQ;IAGhC,CAAC;CACF;AAED,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
2
|
+
/** Normalize external or user-provided text before placing it on one terminal line. */
|
|
3
|
+
export function sanitizeOneLineText(value) {
|
|
4
|
+
const withoutControls = Array.from(stripVTControlCharacters(value), (character) => {
|
|
5
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
6
|
+
return codePoint <= 31 || (codePoint >= 127 && codePoint <= 159) ? ' ' : character;
|
|
7
|
+
}).join('');
|
|
8
|
+
return withoutControls.replace(/\s+/g, ' ').trim();
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/utils/text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAErD,uFAAuF;AACvF,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,MAAM,eAAe,GAAG,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE;QAChF,MAAM,SAAS,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAChD,OAAO,SAAS,IAAI,EAAE,IAAI,CAAC,SAAS,IAAI,GAAG,IAAI,SAAS,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IACrF,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACZ,OAAO,eAAe,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACrD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bzenky/spoti",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A local-first command-line client for controlling Spotify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"commander": "^15.0.0",
|
|
48
48
|
"open": "^11.0.2",
|
|
49
|
+
"picocolors": "^1.1.1",
|
|
49
50
|
"zod": "^4.5.4"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|