@groovenet/cli 1.0.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,6 +27,7 @@ groovenet albums search "kind of blue" # browse albums
27
27
  groovenet playlists list # list playlists
28
28
  groovenet friends list # manage the friends system
29
29
  groovenet play <track-id> # play a track via MPD on the server
30
+ groovenet fingerprint-library # index reference audio for matching
30
31
  ```
31
32
 
32
33
  Every command accepts `--json` for machine-readable output:
@@ -46,7 +47,32 @@ Run `groovenet --help` (or `groovenet <command> --help`) for the full command re
46
47
  | `albums` | Browse and manage albums |
47
48
  | `playlists` | Manage playlists |
48
49
  | `friends` | Manage the friends system |
49
- | `play` / `pause` / `stop` / `now-playing` | Control server-side MPD playback |
50
+ | `fingerprint-library` | Build the reference fingerprint index used to recognise vinyl as it plays |
51
+
52
+ ### fingerprint-library
53
+
54
+ Fingerprints the library's reference audio so live vinyl playback can be
55
+ recognised against it. Safe to re-run: each track's audio is hashed and skipped
56
+ when nothing has changed.
57
+
58
+ ```bash
59
+ groovenet fingerprint-library # only tracks not yet indexed
60
+ groovenet fingerprint-library --changed # re-check already-indexed files
61
+ groovenet fingerprint-library --all --force # regenerate everything
62
+ groovenet fingerprint-library --track 12345-1 # one track
63
+ groovenet fingerprint-library --release 12345 # one release
64
+ groovenet fingerprint-library --no-wait # queue it and return
65
+ ```
66
+
67
+ ```
68
+ Indexing missing — chromaprint 1
69
+ 3653 queued, 241 without reference audio
70
+ ✗ 12345-2: ffmpeg exited 1: Invalid data found when processing input
71
+ ✓ 3412 indexed ⤼ 0 skipped ✗ 2 failed – 241 no audio
72
+ ```
73
+
74
+ Tracks with no local audio are counted, not failed — only the part of the
75
+ library you have downloaded can be fingerprinted. Exits 1 if any track failed.
50
76
 
51
77
  ## Related
52
78
 
@@ -5,8 +5,8 @@ import { addConfigCommands } from "../commands/config.js";
5
5
  import { addTracksCommands } from "../commands/tracks.js";
6
6
  import { addAlbumsCommands } from "../commands/albums.js";
7
7
  import { addPlaylistsCommands } from "../commands/playlists.js";
8
- import { addPlayCommands } from "../commands/play.js";
9
8
  import { addFriendsCommands } from "../commands/friends.js";
9
+ import { addFingerprintLibraryCommand } from "../commands/fingerprintLibrary.js";
10
10
  const require = createRequire(import.meta.url);
11
11
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
12
12
  const { version } = require("../../package.json");
@@ -19,8 +19,8 @@ addConfigCommands(program);
19
19
  addTracksCommands(program);
20
20
  addAlbumsCommands(program);
21
21
  addPlaylistsCommands(program);
22
- addPlayCommands(program);
23
22
  addFriendsCommands(program);
23
+ addFingerprintLibraryCommand(program);
24
24
  program.parseAsync(process.argv).catch((err) => {
25
25
  process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\n`);
26
26
  process.exit(1);
@@ -1 +1 @@
1
- {"version":3,"file":"groovenet.js","sourceRoot":"","sources":["../../src/bin/groovenet.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,sEAAsE;AACtE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAwB,CAAC;AAEzE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,WAAW,CAAC;KACjB,WAAW,CAAC,sCAAsC,CAAC;KACnD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAC3B,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAC3B,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAC3B,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,eAAe,CAAC,OAAO,CAAC,CAAC;AACzB,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAE5B,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAC/D,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"groovenet.js","sourceRoot":"","sources":["../../src/bin/groovenet.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AAEjF,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,sEAAsE;AACtE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAwB,CAAC;AAEzE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,WAAW,CAAC;KACjB,WAAW,CAAC,sCAAsC,CAAC;KACnD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAC3B,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAC3B,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAC3B,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAC5B,4BAA4B,CAAC,OAAO,CAAC,CAAC;AAEtC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAC/D,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { Command } from "commander";
2
+ import { GroovenetClient } from "@groovenet/client";
3
+ import type { FingerprintIndexRequest, FingerprintIndexRun } from "@groovenet/client";
4
+ export declare function makeClient(): GroovenetClient;
5
+ /** Just enough of the client for one run, so a test can pass two functions. */
6
+ export type FingerprintLibraryClient = Pick<GroovenetClient, "startFingerprintIndex" | "getFingerprintIndexRun">;
7
+ /**
8
+ * Where the command's output goes.
9
+ *
10
+ * Injected rather than reaching for `console` and `process.stdout` directly, so
11
+ * the whole run — header, per-failure lines, the rewritten progress line and
12
+ * the summary — can be asserted without a terminal.
13
+ */
14
+ export interface FingerprintLibraryIO {
15
+ /** A whole line, newline appended. */
16
+ log: (line: string) => void;
17
+ /** Raw, no newline — the progress line rewrites itself with \r. */
18
+ write: (text: string) => void;
19
+ }
20
+ export declare const consoleIO: FingerprintLibraryIO;
21
+ export interface FingerprintLibraryOptions {
22
+ missing?: boolean;
23
+ changed?: boolean;
24
+ all?: boolean;
25
+ track?: string;
26
+ release?: string;
27
+ friendId?: number;
28
+ force?: boolean;
29
+ wait?: boolean;
30
+ pollInterval?: number;
31
+ json?: boolean;
32
+ }
33
+ /**
34
+ * Turn the scope flags into one request.
35
+ *
36
+ * Exactly one scope, so `--missing --all` is refused rather than silently
37
+ * resolved to whichever the parser happened to see last. With no scope flag at
38
+ * all it is `--missing`: the cheap, common case, since the alternatives re-read
39
+ * and re-hash every already-indexed file.
40
+ */
41
+ export declare function resolveScope(opts: FingerprintLibraryOptions): FingerprintIndexRequest;
42
+ /** How many of the queued tracks have reached a terminal state. */
43
+ export declare function settledCount(run: FingerprintIndexRun): number;
44
+ /** `indexing 3412/3653 ██████████░░` — one line, rewritten in place. */
45
+ export declare function formatProgress(run: FingerprintIndexRun, width?: number): string;
46
+ /**
47
+ * The four counters the run reports.
48
+ *
49
+ * `unindexable` is listed apart from `failed` on purpose: a track with no
50
+ * `local_audio_url` has no reference audio to fingerprint, which is a fact
51
+ * about how much of the library has been downloaded, not a failure of the run.
52
+ */
53
+ export declare function formatSummary(run: FingerprintIndexRun): string;
54
+ /**
55
+ * Poll until every queued track has settled.
56
+ *
57
+ * `onProgress` is called with each poll so the caller owns the rendering — the
58
+ * loop itself stays testable without a terminal.
59
+ */
60
+ export declare function waitForRun(client: Pick<GroovenetClient, "getFingerprintIndexRun">, runId: string, opts: {
61
+ pollIntervalMs: number;
62
+ onProgress?: (run: FingerprintIndexRun) => void;
63
+ }): Promise<FingerprintIndexRun>;
64
+ /**
65
+ * One `fingerprint-library` run, start to finish. Returns the process exit
66
+ * code: non-zero when any track failed, so the command composes in a script.
67
+ *
68
+ * Separate from the commander wiring because everything interesting happens
69
+ * here — scope resolution, the two output modes, the polling and the exit code
70
+ * — and a commander action is not callable from a test.
71
+ */
72
+ export declare function runFingerprintLibrary(client: FingerprintLibraryClient, opts: FingerprintLibraryOptions, io?: FingerprintLibraryIO): Promise<number>;
73
+ export declare function addFingerprintLibraryCommand(program: Command): void;
74
+ //# sourceMappingURL=fingerprintLibrary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprintLibrary.d.ts","sourceRoot":"","sources":["../../src/commands/fingerprintLibrary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAc,MAAM,mBAAmB,CAAC;AAChE,OAAO,KAAK,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAItF,wBAAgB,UAAU,IAAI,eAAe,CAO5C;AAED,+EAA+E;AAC/E,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,eAAe,EACf,uBAAuB,GAAG,wBAAwB,CACnD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,sCAAsC;IACtC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,mEAAmE;IACnE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED,eAAO,MAAM,SAAS,EAAE,oBAGvB,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,yBAAyB,GAC9B,uBAAuB,CAwBzB;AAED,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAE7D;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,GAAG,EAAE,mBAAmB,EAAE,KAAK,SAAK,GAAG,MAAM,CAM3E;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAQ9D;AAMD;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,wBAAwB,CAAC,EACvD,KAAK,EAAE,MAAM,EACb,IAAI,EAAE;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,IAAI,CAAA;CAAE,GAChF,OAAO,CAAC,mBAAmB,CAAC,CAO9B;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,wBAAwB,EAChC,IAAI,EAAE,yBAAyB,EAC/B,EAAE,GAAE,oBAAgC,GACnC,OAAO,CAAC,MAAM,CAAC,CA8CjB;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwBnE"}
@@ -0,0 +1,165 @@
1
+ import { GroovenetClient, loadConfig } from "@groovenet/client";
2
+ import chalk from "chalk";
3
+ import { printError } from "../output.js";
4
+ export function makeClient() {
5
+ const cfg = loadConfig();
6
+ return new GroovenetClient({
7
+ baseUrl: cfg.api_base,
8
+ apiKey: cfg.api_key,
9
+ insecureTls: cfg.insecure_tls,
10
+ });
11
+ }
12
+ export const consoleIO = {
13
+ log: (line) => console.log(line),
14
+ write: (text) => process.stdout.write(text),
15
+ };
16
+ /**
17
+ * Turn the scope flags into one request.
18
+ *
19
+ * Exactly one scope, so `--missing --all` is refused rather than silently
20
+ * resolved to whichever the parser happened to see last. With no scope flag at
21
+ * all it is `--missing`: the cheap, common case, since the alternatives re-read
22
+ * and re-hash every already-indexed file.
23
+ */
24
+ export function resolveScope(opts) {
25
+ const chosen = [
26
+ opts.missing ? "missing" : null,
27
+ opts.changed ? "changed" : null,
28
+ opts.all ? "all" : null,
29
+ opts.track ? "track" : null,
30
+ opts.release ? "release" : null,
31
+ ].filter((scope) => scope !== null);
32
+ if (chosen.length > 1) {
33
+ throw new Error(`Choose one scope, not ${chosen.length}: ${chosen.map((s) => `--${s}`).join(" ")}`);
34
+ }
35
+ const scope = (chosen[0] ?? "missing");
36
+ const request = { scope };
37
+ if (scope === "track")
38
+ request.track_id = opts.track;
39
+ if (scope === "release")
40
+ request.release_id = opts.release;
41
+ if (opts.friendId !== undefined)
42
+ request.friend_id = opts.friendId;
43
+ if (opts.force)
44
+ request.force = true;
45
+ return request;
46
+ }
47
+ /** How many of the queued tracks have reached a terminal state. */
48
+ export function settledCount(run) {
49
+ return run.indexed + run.skipped + run.failed;
50
+ }
51
+ /** `indexing 3412/3653 ██████████░░` — one line, rewritten in place. */
52
+ export function formatProgress(run, width = 24) {
53
+ const done = settledCount(run);
54
+ const ratio = run.queued > 0 ? Math.min(done / run.queued, 1) : 1;
55
+ const filled = Math.round(ratio * width);
56
+ const bar = "█".repeat(filled) + "░".repeat(width - filled);
57
+ return ` indexing ${done}/${run.queued} ${bar}`;
58
+ }
59
+ /**
60
+ * The four counters the run reports.
61
+ *
62
+ * `unindexable` is listed apart from `failed` on purpose: a track with no
63
+ * `local_audio_url` has no reference audio to fingerprint, which is a fact
64
+ * about how much of the library has been downloaded, not a failure of the run.
65
+ */
66
+ export function formatSummary(run) {
67
+ const parts = [
68
+ chalk.green(`✓ ${run.indexed} indexed`),
69
+ chalk.gray(`⤼ ${run.skipped} skipped`),
70
+ run.failed > 0 ? chalk.red(`✗ ${run.failed} failed`) : chalk.gray("✗ 0 failed"),
71
+ chalk.gray(`– ${run.unindexable} no audio`),
72
+ ];
73
+ return ` ${parts.join(" ")}`;
74
+ }
75
+ function sleep(ms) {
76
+ return new Promise((resolve) => setTimeout(resolve, ms));
77
+ }
78
+ /**
79
+ * Poll until every queued track has settled.
80
+ *
81
+ * `onProgress` is called with each poll so the caller owns the rendering — the
82
+ * loop itself stays testable without a terminal.
83
+ */
84
+ export async function waitForRun(client, runId, opts) {
85
+ for (;;) {
86
+ const run = await client.getFingerprintIndexRun(runId);
87
+ opts.onProgress?.(run);
88
+ if (run.complete)
89
+ return run;
90
+ await sleep(opts.pollIntervalMs);
91
+ }
92
+ }
93
+ /**
94
+ * One `fingerprint-library` run, start to finish. Returns the process exit
95
+ * code: non-zero when any track failed, so the command composes in a script.
96
+ *
97
+ * Separate from the commander wiring because everything interesting happens
98
+ * here — scope resolution, the two output modes, the polling and the exit code
99
+ * — and a commander action is not callable from a test.
100
+ */
101
+ export async function runFingerprintLibrary(client, opts, io = consoleIO) {
102
+ const request = resolveScope(opts);
103
+ const started = await client.startFingerprintIndex(request);
104
+ if (!opts.json) {
105
+ io.log(chalk.bold(`Indexing ${started.scope}`) +
106
+ chalk.gray(` — ${started.fingerprint_type} ${started.fingerprint_version}`));
107
+ io.log(chalk.gray(` ${started.queued} queued, ${started.unindexable} without reference audio`));
108
+ }
109
+ if (!opts.wait) {
110
+ if (opts.json)
111
+ io.write(JSON.stringify(started, null, 2) + "\n");
112
+ else
113
+ io.log(chalk.gray(` run ${started.run_id}`));
114
+ return 0;
115
+ }
116
+ const seen = new Set();
117
+ const finished = await waitForRun(client, started.run_id, {
118
+ pollIntervalMs: opts.pollInterval ?? 1000,
119
+ onProgress: (run) => {
120
+ if (opts.json)
121
+ return;
122
+ // Failures scroll above the progress line as they happen, so a long run
123
+ // does not hide them until the very end.
124
+ for (const failure of run.errors) {
125
+ if (seen.has(failure))
126
+ continue;
127
+ seen.add(failure);
128
+ io.log(chalk.red(` ✗ ${failure}`));
129
+ }
130
+ io.write(`\r${formatProgress(run)}`);
131
+ },
132
+ });
133
+ if (opts.json) {
134
+ io.write(JSON.stringify(finished, null, 2) + "\n");
135
+ return finished.failed > 0 ? 1 : 0;
136
+ }
137
+ io.write("\r\x1b[2K");
138
+ io.log(formatSummary(finished));
139
+ return finished.failed > 0 ? 1 : 0;
140
+ }
141
+ export function addFingerprintLibraryCommand(program) {
142
+ program
143
+ .command("fingerprint-library")
144
+ .description("Build the reference fingerprint index the live vinyl matcher searches")
145
+ .option("--missing", "Only tracks with no fingerprint yet (default)")
146
+ .option("--changed", "Only tracks already indexed, re-checked for edits")
147
+ .option("--all", "Every track with reference audio")
148
+ .option("--track <id>", "One track")
149
+ .option("--release <id>", "Every track on one release")
150
+ .option("--friend-id <n>", "Narrow to one friend's library", parseInt)
151
+ .option("--force", "Re-fingerprint even when the audio is unchanged")
152
+ .option("--no-wait", "Queue the run and exit without waiting")
153
+ .option("--poll-interval <ms>", "How often to poll progress", parseInt, 1000)
154
+ .option("--json", "Output as JSON")
155
+ .action(async (opts) => {
156
+ try {
157
+ process.exitCode = await runFingerprintLibrary(makeClient(), opts);
158
+ }
159
+ catch (err) {
160
+ printError(err instanceof Error ? err.message : String(err));
161
+ process.exit(1);
162
+ }
163
+ });
164
+ }
165
+ //# sourceMappingURL=fingerprintLibrary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprintLibrary.js","sourceRoot":"","sources":["../../src/commands/fingerprintLibrary.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"playlists.d.ts","sourceRoot":"","sources":["../../src/commands/playlists.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkF3D"}
1
+ {"version":3,"file":"playlists.d.ts","sourceRoot":"","sources":["../../src/commands/playlists.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgG3D"}
@@ -6,6 +6,37 @@ function makeClient() {
6
6
  }
7
7
  export function addPlaylistsCommands(program) {
8
8
  const playlists = program.command("playlists").description("Manage playlists");
9
+ const sets = playlists.command("set").description("Manage a playlist's optional set record");
10
+ sets.command("show <id>").option("--json", "Output as JSON").action(async (id, opts) => {
11
+ try {
12
+ const set = await makeClient().getLiveSet(id);
13
+ opts.json ? printJson(set) : console.log(`${set.title || "Untitled set"} · ${set.status}`);
14
+ }
15
+ catch (err) {
16
+ printError(err instanceof Error ? err.message : String(err));
17
+ process.exit(1);
18
+ }
19
+ });
20
+ sets.command("create <id>").action(async (id) => {
21
+ try {
22
+ await makeClient().createLiveSet(id);
23
+ printSuccess("Set created.");
24
+ }
25
+ catch (err) {
26
+ printError(err instanceof Error ? err.message : String(err));
27
+ process.exit(1);
28
+ }
29
+ });
30
+ sets.command("delete <id>").action(async (id) => {
31
+ try {
32
+ await makeClient().deleteLiveSet(id);
33
+ printSuccess("Set removed; playlist kept.");
34
+ }
35
+ catch (err) {
36
+ printError(err instanceof Error ? err.message : String(err));
37
+ process.exit(1);
38
+ }
39
+ });
9
40
  playlists
10
41
  .command("list")
11
42
  .description("List all playlists")
@@ -1 +1 @@
1
- {"version":3,"file":"playlists.js","sourceRoot":"","sources":["../../src/commands/playlists.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"playlists.js","sourceRoot":"","sources":["../../src/commands/playlists.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groovenet/cli",
3
- "version": "1.0.3",
3
+ "version": "2.1.0",
4
4
  "description": "CLI for managing your Groovenet DJ collection",
5
5
  "type": "module",
6
6
  "engines": {
@@ -12,7 +12,10 @@
12
12
  "scripts": {
13
13
  "build": "tsc",
14
14
  "dev": "tsc --watch",
15
- "start": "node build/bin/groovenet.js"
15
+ "start": "node build/bin/groovenet.js",
16
+ "test": "vitest run",
17
+ "test:coverage": "vitest run --coverage --coverage.reporter=lcov",
18
+ "test:watch": "vitest"
16
19
  },
17
20
  "files": [
18
21
  "build/"
@@ -36,13 +39,15 @@
36
39
  "homepage": "https://github.com/Public-Vinyl-Radio/groovenet/tree/main/packages/groovenet-cli#readme",
37
40
  "bugs": "https://github.com/Public-Vinyl-Radio/groovenet/issues",
38
41
  "dependencies": {
39
- "@groovenet/client": "^1.0.2",
42
+ "@groovenet/client": "^2.1.0",
40
43
  "chalk": "^6",
41
44
  "cli-table3": "^0.6",
42
45
  "commander": "^15"
43
46
  },
44
47
  "devDependencies": {
45
- "@types/node": "^26",
46
- "typescript": "^7.0"
48
+ "@types/node": "^26.4.1",
49
+ "@vitest/coverage-v8": "^5.0.0",
50
+ "typescript": "^7.0",
51
+ "vitest": "^5.0.0"
47
52
  }
48
53
  }
@@ -1,3 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function addPlayCommands(program: Command): void;
3
- //# sourceMappingURL=play.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"play.d.ts","sourceRoot":"","sources":["../../src/commands/play.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAuEtD"}
@@ -1,81 +0,0 @@
1
- import { GroovenetClient, loadConfig } from "@groovenet/client";
2
- import { printJson, printSuccess, printError } from "../output.js";
3
- function makeClient() {
4
- const cfg = loadConfig();
5
- return new GroovenetClient({ baseUrl: cfg.api_base, apiKey: cfg.api_key, insecureTls: cfg.insecure_tls });
6
- }
7
- export function addPlayCommands(program) {
8
- program
9
- .command("play <track-id>")
10
- .description("Play a track via MPD on the server (resolves local_audio_url, sends to MPD)")
11
- .option("--friend-id <n>", "Friend ID (defaults to config default_friend_id)", parseInt)
12
- .action(async (trackId, opts) => {
13
- try {
14
- const cfg = loadConfig();
15
- const client = makeClient();
16
- const track = await client.getTrack(trackId, opts.friendId ?? cfg.default_friend_id);
17
- if (!track.local_audio_url) {
18
- printError(`Track "${track.title}" has no local audio file. Download it first.`);
19
- process.exit(1);
20
- }
21
- await client.play(track.local_audio_url);
22
- printSuccess(`▶ Playing: ${track.title} — ${track.artist}`);
23
- }
24
- catch (err) {
25
- printError(err instanceof Error ? err.message : String(err));
26
- process.exit(1);
27
- }
28
- });
29
- program
30
- .command("pause")
31
- .description("Pause playback")
32
- .action(async () => {
33
- try {
34
- const client = makeClient();
35
- await client.pause();
36
- printSuccess("⏸ Paused.");
37
- }
38
- catch (err) {
39
- printError(err instanceof Error ? err.message : String(err));
40
- process.exit(1);
41
- }
42
- });
43
- program
44
- .command("stop")
45
- .description("Stop playback")
46
- .action(async () => {
47
- try {
48
- const client = makeClient();
49
- await client.stop();
50
- printSuccess("⏹ Stopped.");
51
- }
52
- catch (err) {
53
- printError(err instanceof Error ? err.message : String(err));
54
- process.exit(1);
55
- }
56
- });
57
- program
58
- .command("now-playing")
59
- .description("Show current playback status")
60
- .option("--json", "Output as JSON")
61
- .action(async (opts) => {
62
- try {
63
- const client = makeClient();
64
- const status = await client.getPlaybackStatus();
65
- if (opts.json) {
66
- printJson(status);
67
- }
68
- else {
69
- console.log(`Playback enabled: ${status.enabled}`);
70
- if (status.status) {
71
- console.log("Status:", JSON.stringify(status.status, null, 2));
72
- }
73
- }
74
- }
75
- catch (err) {
76
- printError(err instanceof Error ? err.message : String(err));
77
- process.exit(1);
78
- }
79
- });
80
- }
81
- //# sourceMappingURL=play.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"play.js","sourceRoot":"","sources":["../../src/commands/play.ts"],"names":[],"mappings":"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"}