@bzenky/spoti 0.1.0 → 0.2.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 (40) hide show
  1. package/README.md +74 -2
  2. package/dist/app.d.ts +8 -0
  3. package/dist/app.js +206 -2
  4. package/dist/app.js.map +1 -1
  5. package/dist/auth/spotify-auth-client.d.ts +1 -0
  6. package/dist/auth/spotify-auth-client.js +4 -0
  7. package/dist/auth/spotify-auth-client.js.map +1 -1
  8. package/dist/cli.js +8 -1
  9. package/dist/cli.js.map +1 -1
  10. package/dist/services/auth.service.js +8 -1
  11. package/dist/services/auth.service.js.map +1 -1
  12. package/dist/services/device.service.d.ts +20 -0
  13. package/dist/services/device.service.js +61 -0
  14. package/dist/services/device.service.js.map +1 -0
  15. package/dist/services/player.service.d.ts +8 -1
  16. package/dist/services/player.service.js +61 -3
  17. package/dist/services/player.service.js.map +1 -1
  18. package/dist/services/queue.service.d.ts +18 -0
  19. package/dist/services/queue.service.js +43 -0
  20. package/dist/services/queue.service.js.map +1 -0
  21. package/dist/spotify/scopes.d.ts +2 -1
  22. package/dist/spotify/scopes.js +5 -1
  23. package/dist/spotify/scopes.js.map +1 -1
  24. package/dist/spotify/types.d.ts +2 -0
  25. package/dist/storage/config.d.ts +29 -0
  26. package/dist/storage/config.js +111 -0
  27. package/dist/storage/config.js.map +1 -0
  28. package/dist/storage/credentials.d.ts +1 -0
  29. package/dist/storage/credentials.js +1 -0
  30. package/dist/storage/credentials.js.map +1 -1
  31. package/dist/storage/index.d.ts +2 -0
  32. package/dist/storage/index.js +1 -0
  33. package/dist/storage/index.js.map +1 -1
  34. package/dist/ui/watch.d.ts +7 -0
  35. package/dist/ui/watch.js +44 -0
  36. package/dist/ui/watch.js.map +1 -0
  37. package/dist/version.d.ts +2 -0
  38. package/dist/version.js +11 -0
  39. package/dist/version.js.map +1 -0
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -78,12 +78,43 @@ Control playback:
78
78
 
79
79
  ```bash
80
80
  spoti now
81
+ spoti now --watch
81
82
  spoti play "Numb"
82
83
  spoti play "Numb" --first
84
+ spoti play "Numb" --watch
85
+ spoti play "Numb" --no-watch
83
86
  spoti pause
84
87
  spoti resume
85
88
  spoti next
86
89
  spoti previous
90
+ spoti volume 50
91
+ spoti volume +10
92
+ spoti volume -10
93
+ ```
94
+
95
+ Manage playback devices:
96
+
97
+ ```bash
98
+ spoti devices
99
+ spoti device "My Computer"
100
+ ```
101
+
102
+ When playback has no active device, `spoti play` and `spoti resume` automatically target it if exactly one controllable device is available.
103
+
104
+ Seek within the current track:
105
+
106
+ ```bash
107
+ spoti seek 1:30
108
+ spoti seek +30
109
+ spoti seek -10
110
+ ```
111
+
112
+ View the queue or search for a track to add:
113
+
114
+ ```bash
115
+ spoti queue
116
+ spoti queue "Faint"
117
+ spoti queue "Faint" --first
87
118
  ```
88
119
 
89
120
  Search without starting playback:
@@ -101,6 +132,34 @@ spoti logout
101
132
 
102
133
  When attached to an interactive terminal, `spoti play <query>` asks you to select a result. In non-interactive usage it chooses the first result automatically; `--first` makes that behavior explicit.
103
134
 
135
+ Watch mode continuously refreshes the current track and progress until `Ctrl+C` is pressed. `--watch` enables it for one command, while `--no-watch` overrides a saved preference.
136
+
137
+ ## Configuration
138
+
139
+ View all settings:
140
+
141
+ ```bash
142
+ spoti config
143
+ ```
144
+
145
+ Read, update, or reset settings:
146
+
147
+ ```bash
148
+ spoti config get watchAfterPlay
149
+ spoti config set watchAfterPlay true
150
+ spoti config set refreshIntervalMs 2000
151
+ spoti config reset
152
+ ```
153
+
154
+ Available settings:
155
+
156
+ | Setting | Default | Description |
157
+ | --- | ---: | --- |
158
+ | `watchAfterPlay` | `false` | Keep `spoti play` open in watch mode after playback starts. |
159
+ | `refreshIntervalMs` | `1000` | Watch refresh interval from `1000` to `30000` milliseconds. |
160
+
161
+ Command flags take precedence over saved configuration. Application preferences are stored in `$XDG_CONFIG_HOME/spoti/config.json`, or `~/.config/spoti/config.json` when `XDG_CONFIG_HOME` is not set.
162
+
104
163
  ## Credentials
105
164
 
106
165
  Credentials are stored locally in:
@@ -115,7 +174,7 @@ or, when `XDG_CONFIG_HOME` is not set:
115
174
  ~/.config/spoti/credentials.json
116
175
  ```
117
176
 
118
- The credentials file is created with user-only permissions (`0600`). Access tokens refresh automatically. Keep `SPOTIFY_CLIENT_ID` available in the environment because Spotify requires it during token refresh. Never provide or store a Spotify client secret in `spoti`.
177
+ The credentials file is created with user-only permissions (`0600`). Access tokens refresh automatically. Version `0.2.0` adds the `user-read-currently-playing` scope for queue access; credentials created by `0.1.0` require one new `spoti login` authorization. Keep `SPOTIFY_CLIENT_ID` available in the environment because Spotify requires it during token refresh. Never provide or store a Spotify client secret in `spoti`.
119
178
 
120
179
  ## Spotify API policy
121
180
 
@@ -132,6 +191,19 @@ npm test
132
191
  npm run build
133
192
  ```
134
193
 
194
+ ## Releases
195
+
196
+ GitHub Releases are created automatically when a version tag is pushed. The tag must match the version in `package.json`:
197
+
198
+ ```bash
199
+ npm version patch
200
+ npm run verify
201
+ git push origin main
202
+ git push origin v0.1.1
203
+ ```
204
+
205
+ The release workflow attaches the npm package tarball and a `SHA256SUMS` file, and generates release notes from the Git history. Publishing to npm remains a separate explicit step.
206
+
135
207
  ## Current scope
136
208
 
137
- Version `0.1.0` supports authentication, current playback, track search, direct track playback, pause/resume, and next/previous controls. Queue, volume, seek, shuffle, repeat, device switching, library features, JSON output, and a TUI are planned for later releases.
209
+ Version `0.2.0` supports authentication, track search and playback, current playback and watch mode, pause/resume, next/previous, persistent configuration, volume and seek controls, queue management, device listing and selection, and automatic single-device fallback. Shuffle, repeat, album/artist/playlist context playback, library features, JSON output, update notifications, and a TUI are planned for later releases.
package/dist/app.d.ts CHANGED
@@ -1,14 +1,22 @@
1
1
  import { Command } from 'commander';
2
2
  import type { AuthService } from './services/auth.service.js';
3
+ import type { DeviceService } from './services/device.service.js';
3
4
  import type { PlayerService } from './services/player.service.js';
5
+ import type { QueueService } from './services/queue.service.js';
4
6
  import type { SearchService } from './services/search.service.js';
7
+ import { type ConfigStore } from './storage/config.js';
5
8
  import { type Output } from './ui/output.js';
6
9
  import { selectTrack } from './ui/prompts.js';
10
+ import { type PlaybackWatcher } from './ui/watch.js';
7
11
  export interface AppDependencies {
8
12
  auth: AuthService;
9
13
  player: PlayerService;
10
14
  search: SearchService;
15
+ device: DeviceService;
16
+ queue: QueueService;
17
+ config: ConfigStore;
11
18
  output: Output;
12
19
  chooseTrack?: typeof selectTrack;
20
+ watchPlayback?: PlaybackWatcher;
13
21
  }
14
22
  export declare function createProgram(dependencies: AppDependencies): Command;
package/dist/app.js CHANGED
@@ -1,13 +1,19 @@
1
1
  import { Command } from 'commander';
2
+ import { CONFIG_KEYS, parseConfigValue, } from './storage/config.js';
2
3
  import { formatPlayback, formatTrack } from './ui/output.js';
3
4
  import { selectTrack } from './ui/prompts.js';
5
+ import { watchPlayback } from './ui/watch.js';
6
+ import { ConfigurationError } from './utils/errors.js';
7
+ import { formatDuration } from './utils/time.js';
8
+ import { VERSION } from './version.js';
4
9
  export function createProgram(dependencies) {
5
10
  const program = new Command();
6
11
  const chooseTrack = dependencies.chooseTrack ?? selectTrack;
12
+ const startWatching = dependencies.watchPlayback ?? watchPlayback;
7
13
  program
8
14
  .name('spoti')
9
15
  .description('Control Spotify from your terminal')
10
- .version('0.1.0');
16
+ .version(VERSION);
11
17
  program
12
18
  .command('login')
13
19
  .description('Log in to Spotify')
@@ -34,10 +40,52 @@ export function createProgram(dependencies) {
34
40
  const user = await dependencies.auth.getCurrentUser();
35
41
  dependencies.output.log(`✓ Logged in as ${user.displayName}`);
36
42
  });
43
+ const configCommand = program
44
+ .command('config')
45
+ .description('View and update spoti configuration')
46
+ .action(async () => {
47
+ dependencies.output.log(formatConfig(await dependencies.config.read()));
48
+ });
49
+ configCommand
50
+ .command('get')
51
+ .description('Read a configuration value')
52
+ .argument('<key>', 'configuration key')
53
+ .action(async (keyName) => {
54
+ const key = parseConfigKey(keyName);
55
+ const config = await dependencies.config.read();
56
+ dependencies.output.log(String(config[key]));
57
+ });
58
+ configCommand
59
+ .command('set')
60
+ .description('Set a configuration value')
61
+ .argument('<key>', 'configuration key')
62
+ .argument('<value>', 'new value')
63
+ .action(async (keyName, rawValue) => {
64
+ const key = parseConfigKey(keyName);
65
+ const value = parseConfigValue(key, rawValue);
66
+ await dependencies.config.set(key, value);
67
+ dependencies.output.log(`✓ ${key} = ${String(value)}`);
68
+ });
69
+ configCommand
70
+ .command('reset')
71
+ .description('Reset configuration to defaults')
72
+ .action(async () => {
73
+ await dependencies.config.reset();
74
+ dependencies.output.log('✓ Configuration reset');
75
+ });
37
76
  program
38
77
  .command('now')
39
78
  .description('Show the current Spotify playback')
40
- .action(async () => {
79
+ .option('-w, --watch', 'continuously refresh playback information')
80
+ .action(async (options) => {
81
+ if (options.watch) {
82
+ const config = await dependencies.config.read();
83
+ await startWatching({
84
+ player: dependencies.player,
85
+ refreshIntervalMs: config.refreshIntervalMs,
86
+ });
87
+ return;
88
+ }
41
89
  const playback = await dependencies.player.getCurrentPlayback();
42
90
  dependencies.output.log(playback ? formatPlayback(playback) : 'Nothing is currently playing.');
43
91
  });
@@ -71,6 +119,99 @@ export function createProgram(dependencies) {
71
119
  await dependencies.player.previous();
72
120
  dependencies.output.log('⏮ Returned to previous track');
73
121
  });
122
+ program
123
+ .command('devices')
124
+ .description('List available Spotify Connect devices')
125
+ .action(async () => {
126
+ const devices = await dependencies.device.getDevices();
127
+ if (devices.length === 0) {
128
+ dependencies.output.log('No Spotify devices are available. Open Spotify on a device and try again.');
129
+ return;
130
+ }
131
+ dependencies.output.log(devices
132
+ .map((device, index) => {
133
+ const state = device.isActive
134
+ ? 'active'
135
+ : device.isRestricted
136
+ ? 'restricted'
137
+ : 'available';
138
+ const volume = device.volumePercent === null ? '' : ` · ${device.volumePercent}%`;
139
+ return `${index + 1}. ${device.name} · ${device.type} · ${state}${volume}`;
140
+ })
141
+ .join('\n'));
142
+ });
143
+ program
144
+ .command('device')
145
+ .description('Transfer playback to a Spotify Connect device')
146
+ .argument('<name-or-id...>', 'exact device name or ID')
147
+ .action(async (nameOrIdParts) => {
148
+ const device = await dependencies.device.findDevice(nameOrIdParts.join(' '));
149
+ if (!device.id)
150
+ throw new ConfigurationError('The selected device has no usable ID.');
151
+ await dependencies.device.transferPlayback(device.id);
152
+ dependencies.output.log(`✓ Active device: ${device.name}`);
153
+ });
154
+ program
155
+ .command('seek')
156
+ .description('Seek within the current track')
157
+ .argument('<position>', 'absolute time such as 1:30, or a change such as +30 or -10')
158
+ .allowUnknownOption()
159
+ .action(async (rawPosition) => {
160
+ const input = parseSeekInput(rawPosition);
161
+ const position = input.relative
162
+ ? await dependencies.player.changePosition(input.milliseconds)
163
+ : await dependencies.player.seek(input.milliseconds);
164
+ dependencies.output.log(`⏩ Position: ${formatDuration(position)}`);
165
+ });
166
+ program
167
+ .command('volume')
168
+ .description('Set or adjust the active device volume')
169
+ .argument('<value>', 'volume from 0-100, or a relative change such as +10 or -10')
170
+ .allowUnknownOption()
171
+ .action(async (rawValue) => {
172
+ const input = parseVolumeInput(rawValue);
173
+ const volume = input.relative
174
+ ? await dependencies.player.changeVolume(input.value)
175
+ : await dependencies.player.setVolume(input.value);
176
+ dependencies.output.log(`${volume === 0 ? '🔇' : '🔊'} Volume: ${volume}%`);
177
+ });
178
+ program
179
+ .command('queue')
180
+ .description('Show the playback queue or add a searched track')
181
+ .argument('[query...]', 'track name to add')
182
+ .option('--first', 'queue the first search result without prompting')
183
+ .action(async (queryParts, options) => {
184
+ const query = queryParts.join(' ').trim();
185
+ if (!query) {
186
+ const playbackQueue = await dependencies.queue.getQueue();
187
+ const lines = playbackQueue.currentlyPlaying
188
+ ? [
189
+ `Now: ${formatQueueItem(playbackQueue.currentlyPlaying)}`,
190
+ '',
191
+ 'Up next:',
192
+ ]
193
+ : ['Up next:'];
194
+ if (playbackQueue.queue.length === 0)
195
+ lines.push('Queue is empty.');
196
+ else {
197
+ lines.push(...playbackQueue.queue.map((item, index) => `${index + 1}. ${formatQueueItem(item)}`));
198
+ }
199
+ dependencies.output.log(lines.join('\n'));
200
+ return;
201
+ }
202
+ const tracks = await dependencies.search.searchTracks(query);
203
+ if (tracks.length === 0) {
204
+ dependencies.output.log(`No tracks found for "${query}".`);
205
+ return;
206
+ }
207
+ const track = options.first ? tracks[0] : await chooseTrack(tracks);
208
+ if (!track) {
209
+ dependencies.output.log('Selection cancelled.');
210
+ return;
211
+ }
212
+ await dependencies.queue.addItem(track.uri);
213
+ dependencies.output.log(`✓ Queued ${track.name} — ${track.artists.join(', ')}`);
214
+ });
74
215
  program
75
216
  .command('search')
76
217
  .description('Search Spotify tracks')
@@ -90,11 +231,21 @@ export function createProgram(dependencies) {
90
231
  .description('Search for and play a track, or resume with no query')
91
232
  .argument('[query...]', 'track name to search for')
92
233
  .option('--first', 'play the first search result without prompting')
234
+ .option('--watch', 'continuously refresh playback information')
235
+ .option('--no-watch', 'return to the shell after starting playback')
93
236
  .action(async (queryParts, options) => {
94
237
  const query = queryParts.join(' ').trim();
95
238
  if (!query) {
239
+ const config = await dependencies.config.read();
240
+ const shouldWatch = options.watch ?? config.watchAfterPlay;
96
241
  await dependencies.player.resume();
97
242
  dependencies.output.log('▶ Resumed');
243
+ if (shouldWatch) {
244
+ await startWatching({
245
+ player: dependencies.player,
246
+ refreshIntervalMs: config.refreshIntervalMs,
247
+ });
248
+ }
98
249
  return;
99
250
  }
100
251
  const tracks = await dependencies.search.searchTracks(query);
@@ -107,11 +258,64 @@ export function createProgram(dependencies) {
107
258
  dependencies.output.log('Selection cancelled.');
108
259
  return;
109
260
  }
261
+ const config = await dependencies.config.read();
262
+ const shouldWatch = options.watch ?? config.watchAfterPlay;
110
263
  await dependencies.player.playTrack(track.uri);
111
264
  dependencies.output.log(`▶ Playing ${track.name} — ${track.artists.join(', ')}`);
265
+ if (shouldWatch) {
266
+ await startWatching({
267
+ player: dependencies.player,
268
+ refreshIntervalMs: config.refreshIntervalMs,
269
+ });
270
+ }
112
271
  });
113
272
  return program;
114
273
  }
274
+ function parseSeekInput(value) {
275
+ const relative = value.startsWith('+') || value.startsWith('-');
276
+ const sign = value.startsWith('-') ? -1 : 1;
277
+ const time = relative ? value.slice(1) : value;
278
+ const parts = time.split(':');
279
+ if (parts.length > 2 || parts.some((part) => !/^\d+$/.test(part))) {
280
+ throw new ConfigurationError('Seek position must be seconds or minutes:seconds, such as 90, 1:30, +30, or -10.');
281
+ }
282
+ const minutes = parts.length === 2 ? Number(parts[0]) : 0;
283
+ const seconds = Number(parts.at(-1));
284
+ if (!Number.isSafeInteger(minutes) || !Number.isSafeInteger(seconds) || seconds >= 60 && parts.length === 2) {
285
+ throw new ConfigurationError('Seek position must be seconds or minutes:seconds, such as 90, 1:30, +30, or -10.');
286
+ }
287
+ return {
288
+ relative,
289
+ milliseconds: sign * (minutes * 60 + seconds) * 1_000,
290
+ };
291
+ }
292
+ function formatQueueItem(item) {
293
+ return `${item.name} — ${item.subtitle} · ${formatDuration(item.durationMs)}`;
294
+ }
295
+ function parseVolumeInput(value) {
296
+ if (/^[+-]\d+$/.test(value)) {
297
+ const delta = Number(value);
298
+ if (Number.isSafeInteger(delta) && Math.abs(delta) <= 100) {
299
+ return { relative: true, value: delta };
300
+ }
301
+ throw new ConfigurationError('Relative volume must be between -100 and +100.');
302
+ }
303
+ if (/^\d+$/.test(value)) {
304
+ const volume = Number(value);
305
+ if (Number.isSafeInteger(volume) && volume <= 100) {
306
+ return { relative: false, value: volume };
307
+ }
308
+ }
309
+ throw new ConfigurationError('Volume must be from 0 to 100, or a change such as +10 or -10.');
310
+ }
311
+ function parseConfigKey(value) {
312
+ if (CONFIG_KEYS.includes(value))
313
+ return value;
314
+ throw new ConfigurationError(`Unknown configuration key "${value}". Valid keys: ${CONFIG_KEYS.join(', ')}.`);
315
+ }
316
+ function formatConfig(config) {
317
+ return CONFIG_KEYS.map((key) => `${key}: ${String(config[key])}`).join('\n');
318
+ }
115
319
  function parseLimit(value) {
116
320
  const limit = /^\d+$/.test(value) ? Number(value) : Number.NaN;
117
321
  if (!Number.isInteger(limit) || limit < 1 || limit > 10) {
package/dist/app.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ export interface TokenSet {
5
5
  accessToken: string;
6
6
  refreshToken?: string;
7
7
  expiresIn: number;
8
+ scopes?: string[];
8
9
  }
9
10
  export interface ExchangeCodeInput {
10
11
  clientId: string;
@@ -8,6 +8,7 @@ const tokenResponseSchema = z.object({
8
8
  access_token: z.string().min(1),
9
9
  refresh_token: z.string().min(1).optional(),
10
10
  expires_in: z.number().int().positive(),
11
+ scope: z.string().optional(),
11
12
  });
12
13
  const userProfileSchema = z.object({
13
14
  id: z.string().min(1),
@@ -89,6 +90,9 @@ export class SpotifyAuthClient {
89
90
  if (token.data.refresh_token !== undefined) {
90
91
  result.refreshToken = token.data.refresh_token;
91
92
  }
93
+ if (token.data.scope !== undefined) {
94
+ result.scopes = token.data.scope.split(' ').filter(Boolean);
95
+ }
92
96
  return result;
93
97
  }
94
98
  async fetchWithTimeout(url, init) {
@@ -1 +1 @@
1
- {"version":3,"file":"spotify-auth-client.js","sourceRoot":"","sources":["../../src/auth/spotify-auth-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"spotify-auth-client.js","sourceRoot":"","sources":["../../src/auth/spotify-auth-client.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -1,17 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  import { createProgram } from './app.js';
3
3
  import { AuthService } from './services/auth.service.js';
4
+ import { DeviceService } from './services/device.service.js';
4
5
  import { PlayerService } from './services/player.service.js';
6
+ import { QueueService } from './services/queue.service.js';
5
7
  import { SearchService } from './services/search.service.js';
8
+ import { FileConfigStore } from './storage/config.js';
6
9
  import { SpotifyClient } from './spotify/client.js';
7
10
  import { consoleOutput } from './ui/output.js';
8
11
  import { AppError, toError } from './utils/errors.js';
9
12
  const auth = new AuthService();
10
13
  const spotify = new SpotifyClient(auth);
14
+ const device = new DeviceService(spotify);
11
15
  const program = createProgram({
12
16
  auth,
13
- player: new PlayerService(spotify),
17
+ device,
18
+ player: new PlayerService(spotify, device),
19
+ queue: new QueueService(spotify),
14
20
  search: new SearchService(spotify),
21
+ config: new FileConfigStore(),
15
22
  output: consoleOutput,
16
23
  });
17
24
  try {
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC;AAC/B,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC;AACxC,MAAM,OAAO,GAAG,aAAa,CAAC;IAC5B,IAAI;IACJ,MAAM,EAAE,IAAI,aAAa,CAAC,OAAO,CAAC;IAClC,MAAM,EAAE,IAAI,aAAa,CAAC,OAAO,CAAC;IAClC,MAAM,EAAE,aAAa;CACtB,CAAC,CAAC;AAEH,IAAI,CAAC;IACH,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,MAAM,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACvC,aAAa,CAAC,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAC7C,OAAO,CAAC,QAAQ,GAAG,KAAK,YAAY,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC;AAC/B,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC;AACxC,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,OAAO,CAAC,CAAC;AAC1C,MAAM,OAAO,GAAG,aAAa,CAAC;IAC5B,IAAI;IACJ,MAAM;IACN,MAAM,EAAE,IAAI,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1C,KAAK,EAAE,IAAI,YAAY,CAAC,OAAO,CAAC;IAChC,MAAM,EAAE,IAAI,aAAa,CAAC,OAAO,CAAC;IAClC,MAAM,EAAE,IAAI,eAAe,EAAE;IAC7B,MAAM,EAAE,aAAa;CACtB,CAAC,CAAC;AAEH,IAAI,CAAC;IACH,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,MAAM,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACvC,aAAa,CAAC,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAC7C,OAAO,CAAC,QAAQ,GAAG,KAAK,YAAY,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC"}
@@ -1,4 +1,4 @@
1
- import { SPOTIFY_SCOPES } from '../spotify/scopes.js';
1
+ import { LEGACY_SPOTIFY_SCOPES, SPOTIFY_SCOPES, } from '../spotify/scopes.js';
2
2
  import { generateOAuthState, generatePkcePair, } from '../auth/pkce.js';
3
3
  import { loadAuthConfig } from '../auth/config.js';
4
4
  import { requestAuthorizationCode } from '../auth/callback.js';
@@ -44,6 +44,7 @@ export class AuthService {
44
44
  accessToken: token.accessToken,
45
45
  refreshToken: token.refreshToken,
46
46
  expiresAt: this.clock() + token.expiresIn * 1_000,
47
+ scopes: token.scopes ?? [...SPOTIFY_SCOPES],
47
48
  };
48
49
  const user = await this.spotifyAuthApi.getCurrentUser(credentials.accessToken);
49
50
  await this.credentialStore.write(credentials);
@@ -56,6 +57,11 @@ export class AuthService {
56
57
  const credentials = await this.credentialStore.read();
57
58
  if (!credentials)
58
59
  throw new AuthenticationRequiredError();
60
+ const grantedScopes = credentials.scopes ?? [...LEGACY_SPOTIFY_SCOPES];
61
+ const missingScopes = SPOTIFY_SCOPES.filter((scope) => !grantedScopes.includes(scope));
62
+ if (missingScopes.length > 0) {
63
+ throw new AuthenticationRequiredError(`spoti needs new Spotify permissions (${missingScopes.join(', ')}).\n\nRun: spoti login`);
64
+ }
59
65
  if (!isTokenExpired(credentials, this.clock()))
60
66
  return credentials.accessToken;
61
67
  return this.startTokenRefresh(credentials);
@@ -106,6 +112,7 @@ export class AuthService {
106
112
  accessToken: token.accessToken,
107
113
  refreshToken: token.refreshToken ?? credentials.refreshToken,
108
114
  expiresAt: this.clock() + token.expiresIn * 1_000,
115
+ scopes: token.scopes ?? credentials.scopes ?? [...LEGACY_SPOTIFY_SCOPES],
109
116
  };
110
117
  await this.credentialStore.write(updatedCredentials);
111
118
  return updatedCredentials.accessToken;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.service.js","sourceRoot":"","sources":["../../src/services/auth.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.service.js","sourceRoot":"","sources":["../../src/services/auth.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { SpotifyApi } from '../spotify/client.js';
2
+ export interface Device {
3
+ id: string | null;
4
+ isActive: boolean;
5
+ isPrivateSession: boolean;
6
+ isRestricted: boolean;
7
+ name: string;
8
+ type: string;
9
+ volumePercent: number | null;
10
+ supportsVolume: boolean;
11
+ }
12
+ export declare class DeviceService {
13
+ private readonly spotify;
14
+ constructor(spotify: SpotifyApi);
15
+ getDevices(): Promise<Device[]>;
16
+ getActiveDevice(): Promise<Device>;
17
+ getControllableDevices(): Promise<Device[]>;
18
+ findDevice(nameOrId: string): Promise<Device>;
19
+ transferPlayback(deviceId: string, play?: boolean): Promise<void>;
20
+ }
@@ -0,0 +1,61 @@
1
+ import { AppError, NoActiveDeviceError } from '../utils/errors.js';
2
+ export class DeviceService {
3
+ spotify;
4
+ constructor(spotify) {
5
+ this.spotify = spotify;
6
+ }
7
+ async getDevices() {
8
+ const response = await this.spotify.get('/me/player/devices');
9
+ return response.devices.map(mapDevice);
10
+ }
11
+ async getActiveDevice() {
12
+ const activeDevice = (await this.getDevices()).find((device) => device.isActive);
13
+ if (!activeDevice)
14
+ throw new NoActiveDeviceError();
15
+ return activeDevice;
16
+ }
17
+ async getControllableDevices() {
18
+ return (await this.getDevices()).filter((device) => device.id !== null && !device.isRestricted);
19
+ }
20
+ async findDevice(nameOrId) {
21
+ const query = nameOrId.trim();
22
+ const normalizedQuery = query.toLocaleLowerCase();
23
+ const devices = await this.getControllableDevices();
24
+ const idMatch = devices.find((device) => device.id?.toLocaleLowerCase() === normalizedQuery);
25
+ if (idMatch)
26
+ return idMatch;
27
+ const nameMatches = devices.filter((device) => device.name.toLocaleLowerCase() === normalizedQuery);
28
+ if (nameMatches.length === 1)
29
+ return nameMatches[0];
30
+ if (nameMatches.length > 1) {
31
+ const deviceIds = nameMatches.map((device) => device.id).join(', ');
32
+ throw new AppError(`Multiple controllable Spotify devices are named "${query}". Use a device ID instead: ${deviceIds}.`);
33
+ }
34
+ throw new AppError(`No controllable Spotify device matches "${query}". Check the device name or ID and ensure Spotify is open on that device.`);
35
+ }
36
+ async transferPlayback(deviceId, play) {
37
+ const normalizedDeviceId = deviceId.trim();
38
+ if (!normalizedDeviceId) {
39
+ throw new AppError('A Spotify device ID is required to transfer playback.');
40
+ }
41
+ await this.spotify.put('/me/player', {
42
+ body: {
43
+ device_ids: [normalizedDeviceId],
44
+ ...(play === undefined ? {} : { play }),
45
+ },
46
+ });
47
+ }
48
+ }
49
+ function mapDevice(device) {
50
+ return {
51
+ id: device.id,
52
+ isActive: device.is_active,
53
+ isPrivateSession: device.is_private_session,
54
+ isRestricted: device.is_restricted,
55
+ name: device.name,
56
+ type: device.type,
57
+ volumePercent: device.volume_percent,
58
+ supportsVolume: device.supports_volume,
59
+ };
60
+ }
61
+ //# sourceMappingURL=device.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.service.js","sourceRoot":"","sources":["../../src/services/device.service.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,19 @@
1
1
  import type { SpotifyApi } from '../spotify/client.js';
2
+ import type { DeviceService } from './device.service.js';
2
3
  import type { CurrentPlayback } from './models.js';
3
4
  export declare class PlayerService {
4
5
  private readonly spotify;
5
- constructor(spotify: SpotifyApi);
6
+ private readonly deviceService?;
7
+ constructor(spotify: SpotifyApi, deviceService?: Pick<DeviceService, "getControllableDevices"> | undefined);
6
8
  getCurrentPlayback(): Promise<CurrentPlayback | null>;
7
9
  playTrack(uri: string): Promise<void>;
8
10
  pause(): Promise<void>;
9
11
  resume(): Promise<void>;
10
12
  next(): Promise<void>;
11
13
  previous(): Promise<void>;
14
+ setVolume(volumePercent: number): Promise<number>;
15
+ changeVolume(delta: number): Promise<number>;
16
+ seek(positionMs: number): Promise<number>;
17
+ changePosition(deltaMs: number): Promise<number>;
18
+ private putWithDeviceFallback;
12
19
  }
@@ -1,7 +1,10 @@
1
+ import { AppError, NoActiveDeviceError } from '../utils/errors.js';
1
2
  export class PlayerService {
2
3
  spotify;
3
- constructor(spotify) {
4
+ deviceService;
5
+ constructor(spotify, deviceService) {
4
6
  this.spotify = spotify;
7
+ this.deviceService = deviceService;
5
8
  }
6
9
  async getCurrentPlayback() {
7
10
  const playback = await this.spotify.get('/me/player');
@@ -15,13 +18,13 @@ export class PlayerService {
15
18
  };
16
19
  }
17
20
  async playTrack(uri) {
18
- await this.spotify.put('/me/player/play', { body: { uris: [uri] } });
21
+ await this.putWithDeviceFallback('/me/player/play', { body: { uris: [uri] } });
19
22
  }
20
23
  async pause() {
21
24
  await this.spotify.put('/me/player/pause');
22
25
  }
23
26
  async resume() {
24
- await this.spotify.put('/me/player/play');
27
+ await this.putWithDeviceFallback('/me/player/play');
25
28
  }
26
29
  async next() {
27
30
  await this.spotify.post('/me/player/next');
@@ -29,6 +32,61 @@ export class PlayerService {
29
32
  async previous() {
30
33
  await this.spotify.post('/me/player/previous');
31
34
  }
35
+ async setVolume(volumePercent) {
36
+ const volume = clampVolume(volumePercent);
37
+ await this.spotify.put('/me/player/volume', {
38
+ query: { volume_percent: volume },
39
+ });
40
+ return volume;
41
+ }
42
+ async changeVolume(delta) {
43
+ const playback = await this.spotify.get('/me/player');
44
+ if (!playback)
45
+ throw new NoActiveDeviceError();
46
+ if (!playback.device.supports_volume || playback.device.volume_percent === null) {
47
+ throw new AppError('The active Spotify device does not support volume control.');
48
+ }
49
+ return this.setVolume(playback.device.volume_percent + delta);
50
+ }
51
+ async seek(positionMs) {
52
+ const position = Math.max(0, Math.round(positionMs));
53
+ await this.spotify.put('/me/player/seek', {
54
+ query: { position_ms: position },
55
+ });
56
+ return position;
57
+ }
58
+ async changePosition(deltaMs) {
59
+ const playback = await this.spotify.get('/me/player');
60
+ if (!playback?.item) {
61
+ throw new AppError('Nothing is currently playing, so there is no position to seek.');
62
+ }
63
+ return this.seek((playback.progress_ms ?? 0) + deltaMs);
64
+ }
65
+ async putWithDeviceFallback(path, options) {
66
+ try {
67
+ await this.spotify.put(path, options);
68
+ return;
69
+ }
70
+ catch (error) {
71
+ if (!(error instanceof NoActiveDeviceError) || !this.deviceService)
72
+ throw error;
73
+ }
74
+ const devices = await this.deviceService.getControllableDevices();
75
+ const device = devices.length === 1 ? devices[0] : undefined;
76
+ if (!device?.id) {
77
+ if (devices.length > 1) {
78
+ throw new AppError('Multiple Spotify devices are available, but none is active.\n\nRun: spoti devices\nThen select one with: spoti device "<name>"');
79
+ }
80
+ throw new NoActiveDeviceError();
81
+ }
82
+ await this.spotify.put(path, {
83
+ ...options,
84
+ query: { ...options?.query, device_id: device.id },
85
+ });
86
+ }
87
+ }
88
+ function clampVolume(volume) {
89
+ return Math.min(100, Math.max(0, Math.round(volume)));
32
90
  }
33
91
  function mapTrack(track) {
34
92
  const externalUrl = track.external_urls?.spotify;
@@ -1 +1 @@
1
- {"version":3,"file":"player.service.js","sourceRoot":"","sources":["../../src/services/player.service.ts"],"names":[],"mappings":"AAIA,MAAM,OAAO,aAAa;IACK;IAA7B,YAA6B,OAAmB;QAAnB,YAAO,GAAP,OAAO,CAAY;IAAG,CAAC;IAEpD,KAAK,CAAC,kBAAkB;QACtB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAmC,YAAY,CAAC,CAAC;QACxF,IAAI,CAAC,QAAQ,EAAE,IAAI;YAAE,OAAO,IAAI,CAAC;QACjC,OAAO;YACL,SAAS,EAAE,QAAQ,CAAC,UAAU;YAC9B,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;YAC9B,UAAU,EAAE,QAAQ,CAAC,WAAW,IAAI,CAAC;YACrC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI;SACjC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAW;QACzB,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAO,iBAAiB,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAO,kBAAkB,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,MAAM;QACV,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAO,iBAAiB,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAO,iBAAiB,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAO,qBAAqB,CAAC,CAAC;IACvD,CAAC;CACF;AAED,SAAS,QAAQ,CAAC,KAAwC;IACxD,MAAM,WAAW,GAAG,KAAK,CAAC,aAAa,EAAE,OAAO,CAAC;IACjD,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC;QACnD,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI;QACvB,UAAU,EAAE,KAAK,CAAC,WAAW;QAC7B,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"player.service.js","sourceRoot":"","sources":["../../src/services/player.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAInE,MAAM,OAAO,aAAa;IAEL;IACA;IAFnB,YACmB,OAAmB,EACnB,aAA6D;QAD7D,YAAO,GAAP,OAAO,CAAY;QACnB,kBAAa,GAAb,aAAa,CAAgD;IAC7E,CAAC;IAEJ,KAAK,CAAC,kBAAkB;QACtB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAmC,YAAY,CAAC,CAAC;QACxF,IAAI,CAAC,QAAQ,EAAE,IAAI;YAAE,OAAO,IAAI,CAAC;QACjC,OAAO;YACL,SAAS,EAAE,QAAQ,CAAC,UAAU;YAC9B,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;YAC9B,UAAU,EAAE,QAAQ,CAAC,WAAW,IAAI,CAAC;YACrC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI;SACjC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAW;QACzB,MAAM,IAAI,CAAC,qBAAqB,CAAC,iBAAiB,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IACjF,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAO,kBAAkB,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,MAAM;QACV,MAAM,IAAI,CAAC,qBAAqB,CAAC,iBAAiB,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAO,iBAAiB,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAO,qBAAqB,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,aAAqB;QACnC,MAAM,MAAM,GAAG,WAAW,CAAC,aAAa,CAAC,CAAC;QAC1C,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAO,mBAAmB,EAAE;YAChD,KAAK,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE;SAClC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAa;QAC9B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAmC,YAAY,CAAC,CAAC;QACxF,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,mBAAmB,EAAE,CAAC;QAC/C,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,eAAe,IAAI,QAAQ,CAAC,MAAM,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;YAChF,MAAM,IAAI,QAAQ,CAAC,4DAA4D,CAAC,CAAC;QACnF,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,GAAG,KAAK,CAAC,CAAC;IAChE,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,UAAkB;QAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;QACrD,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAO,iBAAiB,EAAE;YAC9C,KAAK,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,OAAe;QAClC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAmC,YAAY,CAAC,CAAC;QACxF,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;YACpB,MAAM,IAAI,QAAQ,CAAC,gEAAgE,CAAC,CAAC;QACvF,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,WAAW,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC;IAC1D,CAAC;IAEO,KAAK,CAAC,qBAAqB,CACjC,IAAY,EACZ,OAAwB;QAExB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAO,IAAI,EAAE,OAAO,CAAC,CAAC;YAC5C,OAAO;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,CAAC,KAAK,YAAY,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa;gBAAE,MAAM,KAAK,CAAC;QAClF,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,sBAAsB,EAAE,CAAC;QAClE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7D,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC;YAChB,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,IAAI,QAAQ,CAChB,gIAAgI,CACjI,CAAC;YACJ,CAAC;YACD,MAAM,IAAI,mBAAmB,EAAE,CAAC;QAClC,CAAC;QAED,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAO,IAAI,EAAE;YACjC,GAAG,OAAO;YACV,KAAK,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,EAAE;SACnD,CAAC,CAAC;IACL,CAAC;CACF;AAED,SAAS,WAAW,CAAC,MAAc;IACjC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,QAAQ,CAAC,KAAwC;IACxD,MAAM,WAAW,GAAG,KAAK,CAAC,aAAa,EAAE,OAAO,CAAC;IACjD,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC;QACnD,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI;QACvB,UAAU,EAAE,KAAK,CAAC,WAAW;QAC7B,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { SpotifyApi } from '../spotify/client.js';
2
+ export interface QueueItem {
3
+ name: string;
4
+ uri: string;
5
+ type: 'track' | 'episode';
6
+ subtitle: string;
7
+ durationMs: number;
8
+ }
9
+ export interface PlaybackQueue {
10
+ currentlyPlaying: QueueItem | null;
11
+ queue: QueueItem[];
12
+ }
13
+ export declare class QueueService {
14
+ private readonly spotify;
15
+ constructor(spotify: SpotifyApi);
16
+ getQueue(): Promise<PlaybackQueue>;
17
+ addItem(uri: string): Promise<void>;
18
+ }
@@ -0,0 +1,43 @@
1
+ export class QueueService {
2
+ spotify;
3
+ constructor(spotify) {
4
+ this.spotify = spotify;
5
+ }
6
+ async getQueue() {
7
+ const response = await this.spotify.get('/me/player/queue');
8
+ return {
9
+ currentlyPlaying: response.currently_playing
10
+ ? mapQueueItem(response.currently_playing)
11
+ : null,
12
+ queue: response.queue
13
+ .map(mapQueueItem)
14
+ .filter((item) => item !== null),
15
+ };
16
+ }
17
+ async addItem(uri) {
18
+ await this.spotify.post('/me/player/queue', { query: { uri } });
19
+ }
20
+ }
21
+ function mapQueueItem(item) {
22
+ switch (item.type) {
23
+ case 'track':
24
+ return {
25
+ name: item.name,
26
+ uri: item.uri,
27
+ type: item.type,
28
+ subtitle: item.artists.map((artist) => artist.name).join(', '),
29
+ durationMs: item.duration_ms,
30
+ };
31
+ case 'episode':
32
+ return {
33
+ name: item.name,
34
+ uri: item.uri,
35
+ type: item.type,
36
+ subtitle: item.show.name,
37
+ durationMs: item.duration_ms,
38
+ };
39
+ default:
40
+ return null;
41
+ }
42
+ }
43
+ //# sourceMappingURL=queue.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.service.js","sourceRoot":"","sources":["../../src/services/queue.service.ts"],"names":[],"mappings":"AAsCA,MAAM,OAAO,YAAY;IACM;IAA7B,YAA6B,OAAmB;QAAnB,YAAO,GAAP,OAAO,CAAY;IAAG,CAAC;IAEpD,KAAK,CAAC,QAAQ;QACZ,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAuB,kBAAkB,CAAC,CAAC;QAElF,OAAO;YACL,gBAAgB,EAAE,QAAQ,CAAC,iBAAiB;gBAC1C,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,iBAAiB,CAAC;gBAC1C,CAAC,CAAC,IAAI;YACR,KAAK,EAAE,QAAQ,CAAC,KAAK;iBAClB,GAAG,CAAC,YAAY,CAAC;iBACjB,MAAM,CAAC,CAAC,IAAI,EAAqB,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC;SACtD,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW;QACvB,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAO,kBAAkB,EAAE,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACxE,CAAC;CACF;AAED,SAAS,YAAY,CAAC,IAAsB;IAC1C,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,OAAO;YACV,OAAO;gBACL,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;gBAC9D,UAAU,EAAE,IAAI,CAAC,WAAW;aAC7B,CAAC;QACJ,KAAK,SAAS;YACZ,OAAO;gBACL,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI;gBACxB,UAAU,EAAE,IAAI,CAAC,WAAW;aAC7B,CAAC;QACJ;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC"}
@@ -1 +1,2 @@
1
- export declare const SPOTIFY_SCOPES: readonly ["user-read-playback-state", "user-modify-playback-state"];
1
+ export declare const LEGACY_SPOTIFY_SCOPES: readonly ["user-read-playback-state", "user-modify-playback-state"];
2
+ export declare const SPOTIFY_SCOPES: readonly ["user-read-playback-state", "user-modify-playback-state", "user-read-currently-playing"];
@@ -1,5 +1,9 @@
1
- export const SPOTIFY_SCOPES = [
1
+ export const LEGACY_SPOTIFY_SCOPES = [
2
2
  'user-read-playback-state',
3
3
  'user-modify-playback-state',
4
4
  ];
5
+ export const SPOTIFY_SCOPES = [
6
+ ...LEGACY_SPOTIFY_SCOPES,
7
+ 'user-read-currently-playing',
8
+ ];
5
9
  //# sourceMappingURL=scopes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../src/spotify/scopes.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,0BAA0B;IAC1B,4BAA4B;CACpB,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;CACrB,CAAC"}
@@ -34,6 +34,8 @@ export interface SpotifyPlaybackState {
34
34
  id: string | null;
35
35
  name: string;
36
36
  is_active: boolean;
37
+ volume_percent: number | null;
38
+ supports_volume: boolean;
37
39
  };
38
40
  }
39
41
  export interface SpotifySearchResponse {
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+ declare const configSchema: z.ZodObject<{
3
+ watchAfterPlay: z.ZodDefault<z.ZodBoolean>;
4
+ refreshIntervalMs: z.ZodDefault<z.ZodNumber>;
5
+ }, z.core.$strict>;
6
+ export type AppConfig = z.infer<typeof configSchema>;
7
+ export declare const CONFIG_KEYS: readonly ["watchAfterPlay", "refreshIntervalMs"];
8
+ export type ConfigKey = (typeof CONFIG_KEYS)[number];
9
+ export declare const DEFAULT_CONFIG: Readonly<AppConfig>;
10
+ export interface ConfigStore {
11
+ read(): Promise<AppConfig>;
12
+ write(config: AppConfig): Promise<void>;
13
+ set<Key extends ConfigKey>(key: Key, value: AppConfig[Key]): Promise<void>;
14
+ reset(): Promise<void>;
15
+ }
16
+ export interface FileConfigStoreOptions {
17
+ directory?: string;
18
+ }
19
+ export declare class FileConfigStore implements ConfigStore {
20
+ readonly path: string;
21
+ private readonly directory;
22
+ constructor(options?: FileConfigStoreOptions);
23
+ read(): Promise<AppConfig>;
24
+ write(config: AppConfig): Promise<void>;
25
+ set<Key extends ConfigKey>(key: Key, value: AppConfig[Key]): Promise<void>;
26
+ reset(): Promise<void>;
27
+ }
28
+ export declare function parseConfigValue<Key extends ConfigKey>(key: Key, value: string): AppConfig[Key];
29
+ export {};
@@ -0,0 +1,111 @@
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 { z } from 'zod';
5
+ import { ConfigurationError, toError } from '../utils/errors.js';
6
+ import { getConfigDirectory } from './credentials.js';
7
+ const configSchema = z.strictObject({
8
+ watchAfterPlay: z.boolean().default(false),
9
+ refreshIntervalMs: z.number().int().min(1_000).max(30_000).default(1_000),
10
+ });
11
+ export const CONFIG_KEYS = ['watchAfterPlay', 'refreshIntervalMs'];
12
+ export const DEFAULT_CONFIG = Object.freeze(configSchema.parse({}));
13
+ export class FileConfigStore {
14
+ path;
15
+ directory;
16
+ constructor(options = {}) {
17
+ this.directory = options.directory ?? getConfigDirectory();
18
+ this.path = join(this.directory, 'config.json');
19
+ }
20
+ async read() {
21
+ let contents;
22
+ try {
23
+ contents = await readFile(this.path, 'utf8');
24
+ }
25
+ catch (error) {
26
+ if (isFileNotFound(error))
27
+ return { ...DEFAULT_CONFIG };
28
+ throw new ConfigurationError(`Unable to read application configuration: ${toError(error).message}`);
29
+ }
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(contents);
33
+ }
34
+ catch {
35
+ throw new ConfigurationError('Application configuration is not valid JSON.');
36
+ }
37
+ const config = configSchema.safeParse(parsed);
38
+ if (!config.success) {
39
+ throw new ConfigurationError('Application configuration has an invalid format.');
40
+ }
41
+ return config.data;
42
+ }
43
+ async write(config) {
44
+ const validated = configSchema.safeParse(config);
45
+ if (!validated.success) {
46
+ throw new ConfigurationError('Cannot store invalid application configuration.');
47
+ }
48
+ try {
49
+ await mkdir(this.directory, { recursive: true, mode: 0o700 });
50
+ await chmod(this.directory, 0o700);
51
+ }
52
+ catch (error) {
53
+ throw new ConfigurationError(`Unable to prepare the application configuration directory: ${toError(error).message}`);
54
+ }
55
+ const temporaryPath = join(this.directory, `.config-${randomUUID()}.tmp`);
56
+ try {
57
+ await writeFile(temporaryPath, `${JSON.stringify(validated.data, null, 2)}\n`, {
58
+ encoding: 'utf8',
59
+ flag: 'wx',
60
+ mode: 0o600,
61
+ });
62
+ await chmod(temporaryPath, 0o600);
63
+ await rename(temporaryPath, this.path);
64
+ await chmod(this.path, 0o600);
65
+ }
66
+ catch (error) {
67
+ await unlink(temporaryPath).catch(() => undefined);
68
+ throw new ConfigurationError(`Unable to store application configuration: ${toError(error).message}`);
69
+ }
70
+ }
71
+ async set(key, value) {
72
+ const current = await this.read();
73
+ await this.write({ ...current, [key]: value });
74
+ }
75
+ async reset() {
76
+ try {
77
+ await unlink(this.path);
78
+ }
79
+ catch (error) {
80
+ if (!isFileNotFound(error)) {
81
+ throw new ConfigurationError(`Unable to reset application configuration: ${toError(error).message}`);
82
+ }
83
+ }
84
+ }
85
+ }
86
+ export function parseConfigValue(key, value) {
87
+ if (key === 'watchAfterPlay') {
88
+ if (value === 'true')
89
+ return true;
90
+ if (value === 'false')
91
+ return false;
92
+ throw new ConfigurationError('Invalid value for watchAfterPlay: expected either "true" or "false".');
93
+ }
94
+ if (key === 'refreshIntervalMs') {
95
+ if (/^-?\d+$/.test(value)) {
96
+ const interval = Number(value);
97
+ if (Number.isSafeInteger(interval) && interval >= 1_000 && interval <= 30_000) {
98
+ return interval;
99
+ }
100
+ }
101
+ throw new ConfigurationError('Invalid value for refreshIntervalMs: expected an integer from 1000 to 30000.');
102
+ }
103
+ throw new ConfigurationError(`Unknown configuration key: ${String(key)}.`);
104
+ }
105
+ function isFileNotFound(error) {
106
+ return (typeof error === 'object' &&
107
+ error !== null &&
108
+ 'code' in error &&
109
+ error.code === 'ENOENT');
110
+ }
111
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/storage/config.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ declare const credentialsSchema: z.ZodObject<{
3
3
  accessToken: z.ZodString;
4
4
  refreshToken: z.ZodString;
5
5
  expiresAt: z.ZodNumber;
6
+ scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
6
7
  }, z.core.$strict>;
7
8
  export type Credentials = z.infer<typeof credentialsSchema>;
8
9
  export interface CredentialStore {
@@ -8,6 +8,7 @@ const credentialsSchema = z.strictObject({
8
8
  accessToken: z.string().min(1),
9
9
  refreshToken: z.string().min(1),
10
10
  expiresAt: z.number().int().nonnegative(),
11
+ scopes: z.array(z.string().min(1)).optional(),
11
12
  });
12
13
  export function getConfigDirectory(environment = process.env, homeDirectory = homedir()) {
13
14
  const xdgConfigHome = environment.XDG_CONFIG_HOME?.trim();
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/storage/credentials.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/storage/credentials.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,4 @@
1
+ export { CONFIG_KEYS, DEFAULT_CONFIG, FileConfigStore, parseConfigValue, } from './config.js';
2
+ export type { AppConfig, ConfigKey, ConfigStore, FileConfigStoreOptions, } from './config.js';
1
3
  export { FileCredentialStore, getConfigDirectory, isTokenExpired, } from './credentials.js';
2
4
  export type { CredentialStore, Credentials, FileCredentialStoreOptions, } from './credentials.js';
@@ -1,2 +1,3 @@
1
+ export { CONFIG_KEYS, DEFAULT_CONFIG, FileConfigStore, parseConfigValue, } from './config.js';
1
2
  export { FileCredentialStore, getConfigDirectory, isTokenExpired, } from './credentials.js';
2
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,GACf,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,cAAc,EACd,eAAe,EACf,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAOrB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,GACf,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { PlayerService } from '../services/player.service.js';
2
+ export interface WatchPlaybackOptions {
3
+ player: Pick<PlayerService, 'getCurrentPlayback'>;
4
+ refreshIntervalMs: number;
5
+ }
6
+ export type PlaybackWatcher = (options: WatchPlaybackOptions) => Promise<void>;
7
+ export declare const watchPlayback: PlaybackWatcher;
@@ -0,0 +1,44 @@
1
+ import { stdin, stdout } from 'node:process';
2
+ import { AppError } from '../utils/errors.js';
3
+ import { formatPlayback } from './output.js';
4
+ export const watchPlayback = async ({ player, refreshIntervalMs }) => {
5
+ if (!stdin.isTTY || !stdout.isTTY) {
6
+ throw new AppError('Watch mode requires an interactive terminal.');
7
+ }
8
+ let stopped = false;
9
+ let resolveStop = () => undefined;
10
+ const stopPromise = new Promise((resolve) => {
11
+ resolveStop = resolve;
12
+ });
13
+ const stop = () => {
14
+ stopped = true;
15
+ resolveStop();
16
+ };
17
+ process.once('SIGINT', stop);
18
+ stdout.write('\u001B[?1049h\u001B[?25l');
19
+ try {
20
+ while (!stopped) {
21
+ const playback = await player.getCurrentPlayback();
22
+ const content = playback
23
+ ? formatPlayback(playback)
24
+ : 'Nothing is currently playing.';
25
+ stdout.write(`\u001B[2J\u001B[H${content}\n\nPress Ctrl+C to exit`);
26
+ if (!stopped)
27
+ await waitForRefresh(refreshIntervalMs, stopPromise);
28
+ }
29
+ }
30
+ finally {
31
+ process.removeListener('SIGINT', stop);
32
+ stdout.write('\u001B[?25h\u001B[?1049l');
33
+ }
34
+ };
35
+ async function waitForRefresh(milliseconds, stopPromise) {
36
+ let timer;
37
+ const timerPromise = new Promise((resolve) => {
38
+ timer = setTimeout(resolve, milliseconds);
39
+ });
40
+ await Promise.race([timerPromise, stopPromise]);
41
+ if (timer)
42
+ clearTimeout(timer);
43
+ }
44
+ //# sourceMappingURL=watch.js.map
@@ -0,0 +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;AAS7C,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,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,CAAC;gBAC1B,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"}
@@ -0,0 +1,2 @@
1
+ export declare function readPackageVersion(): string;
2
+ export declare const VERSION: string;
@@ -0,0 +1,11 @@
1
+ import { readFileSync } from 'node:fs';
2
+ export function readPackageVersion() {
3
+ const packageUrl = new URL('../package.json', import.meta.url);
4
+ const metadata = JSON.parse(readFileSync(packageUrl, 'utf8'));
5
+ if (typeof metadata.version !== 'string' || metadata.version.length === 0) {
6
+ throw new Error('package.json does not contain a valid version.');
7
+ }
8
+ return metadata.version;
9
+ }
10
+ export const VERSION = readPackageVersion();
11
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAMvC,MAAM,UAAU,kBAAkB;IAChC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAoB,CAAC;IACjF,IAAI,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ,IAAI,QAAQ,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1E,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,QAAQ,CAAC,OAAO,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bzenky/spoti",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A local-first command-line client for controlling Spotify",
5
5
  "type": "module",
6
6
  "bin": {