nixamp 0.51.0 → 0.52.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/dist/ai-label.d.ts +70 -3
- package/dist/ai-label.js +375 -29
- package/dist/label-routes.js +2 -1
- package/dist/main.js +9 -0
- package/dist/mcp.js +42 -0
- package/dist/music-cli.d.ts +2 -0
- package/dist/music-cli.js +109 -0
- package/dist/music-client.d.ts +62 -0
- package/dist/music-client.js +73 -0
- package/dist/station-cli.js +8 -1
- package/dist/station-mcp.d.ts +62 -0
- package/dist/station-mcp.js +5 -2
- package/dist/station-usage.d.ts +1 -1
- package/dist/station-usage.js +3 -0
- package/package.json +1 -1
- package/src/ai-label.ts +380 -29
- package/src/label-routes.ts +2 -1
- package/src/main.ts +8 -0
- package/src/mcp.ts +38 -0
- package/src/music-cli.ts +86 -0
- package/src/music-client.ts +86 -0
- package/src/station-cli.ts +8 -1
- package/src/station-mcp.ts +5 -2
- package/src/station-usage.ts +3 -0
- package/web/dist/assets/{ar--GRGOL7F.js → ar-DMx2aeAO.js} +1 -1
- package/web/dist/assets/{cs-B2ab-8qO.js → cs-Ddu0q3kG.js} +1 -1
- package/web/dist/assets/{da-Ek2g1uTg.js → da-CZ9gg_pb.js} +1 -1
- package/web/dist/assets/{de-CM7-9Fbk.js → de-CgJX4xg2.js} +1 -1
- package/web/dist/assets/{es-BFSOrr3I.js → es-Cp5ryDrJ.js} +1 -1
- package/web/dist/assets/{fi-D9Tf69sv.js → fi-D912EKux.js} +1 -1
- package/web/dist/assets/{fr-Gh9yYJGo.js → fr-FE_K0MPb.js} +1 -1
- package/web/dist/assets/{hi-B3ow3HJC.js → hi-BdYtQs1S.js} +1 -1
- package/web/dist/assets/{hu-paju-6Kr.js → hu-oh5tEg0p.js} +1 -1
- package/web/dist/assets/{id-mubOQFX0.js → id-NbCUWeB1.js} +1 -1
- package/web/dist/assets/{it-0EM-qnDT.js → it-CiHbKZ6Y.js} +1 -1
- package/web/dist/assets/{main-CECXge6G.js → main-8wV87D-g.js} +2 -2
- package/web/dist/assets/{nl-c6HalphH.js → nl-nLHK_AQ-.js} +1 -1
- package/web/dist/assets/{radio-CDUEoM0y.js → radio-BmHvBkVy.js} +1 -1
- package/web/dist/assets/{ru-CUKYVlgf.js → ru-BDdAS0Vv.js} +1 -1
- package/web/dist/assets/{sv-BUxBsZR5.js → sv-COweXM56.js} +1 -1
- package/web/dist/assets/{uk-708lBssa.js → uk-BJFzR1LS.js} +1 -1
- package/web/dist/assets/{vi-B1ropUvk.js → vi-qg2DJ6cR.js} +1 -1
- package/web/dist/assets/{zh-C_ndTsNh.js → zh-DUWOKDkb.js} +1 -1
- package/web/dist/index.html +2 -1
- package/web/dist/radio.html +1 -1
- package/web/dist/sw.js +20 -20
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const MUSIC_USAGE = "nixamp music: the AI label's songs as files (a song 10\u00A2, an album $1, everything by an artist $2; live radio is free)\n\n nixamp music [list] what you bought, with its download link\n nixamp music catalog [SERVER] the artists, albums and songs (live.streaming by default)\n nixamp music buy LINK a nixamp.com/buy link, or:\n nixamp music buy --server URL --kind song|album|artist --artist KEY [--album KEY] [--n N]\n [--email you@example.com] [--coin SOL] prints the CoinPay page to pay on\n nixamp music download ID|all [--out DIR] save the zip(s): MP3s with art and lyrics\n";
|
|
2
|
+
export declare function music(argv: string[]): Promise<number>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nixamp music`: the AI label's catalogue, buying, and what you bought.
|
|
3
|
+
*
|
|
4
|
+
* nixamp music [list] what you bought (signed in with that email)
|
|
5
|
+
* nixamp music catalog [SERVER] artists, albums and songs (live.streaming by default)
|
|
6
|
+
* nixamp music buy LINK|--server S --kind song|album|artist --artist A [--album B] [--n N]
|
|
7
|
+
* [--email E] [--coin SOL] prints the CoinPay page to pay on
|
|
8
|
+
* nixamp music download ID|all [--out DIR] the zip(s) of what you bought
|
|
9
|
+
*
|
|
10
|
+
* Listening is free on the live stations; the files are sold: a song 10¢, an
|
|
11
|
+
* album $1, everything by an artist $2.
|
|
12
|
+
*/
|
|
13
|
+
import { buy, catalogue, download, fromBuyLink, PRICE_WORDS, purchases } from "./music-client.js";
|
|
14
|
+
export const MUSIC_USAGE = `nixamp music: the AI label's songs as files (${PRICE_WORDS}; live radio is free)
|
|
15
|
+
|
|
16
|
+
nixamp music [list] what you bought, with its download link
|
|
17
|
+
nixamp music catalog [SERVER] the artists, albums and songs (live.streaming by default)
|
|
18
|
+
nixamp music buy LINK a nixamp.com/buy link, or:
|
|
19
|
+
nixamp music buy --server URL --kind song|album|artist --artist KEY [--album KEY] [--n N]
|
|
20
|
+
[--email you@example.com] [--coin SOL] prints the CoinPay page to pay on
|
|
21
|
+
nixamp music download ID|all [--out DIR] save the zip(s): MP3s with art and lyrics
|
|
22
|
+
`;
|
|
23
|
+
function flag(argv, name) {
|
|
24
|
+
const at = argv.indexOf(name);
|
|
25
|
+
return at === -1 ? undefined : argv[at + 1];
|
|
26
|
+
}
|
|
27
|
+
export async function music(argv) {
|
|
28
|
+
const [sub = "list", arg] = argv;
|
|
29
|
+
const json = argv.includes("--json");
|
|
30
|
+
try {
|
|
31
|
+
if (sub === "help" || argv.includes("--help")) {
|
|
32
|
+
console.log(MUSIC_USAGE);
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
if (sub === "list") {
|
|
36
|
+
const mine = await purchases();
|
|
37
|
+
if (json) {
|
|
38
|
+
console.log(JSON.stringify(mine, null, 2));
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
if (mine.purchases.length === 0) {
|
|
42
|
+
console.log(`Nothing bought yet for ${mine.email}. Every AI label song is for sale: ${PRICE_WORDS}.`);
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
for (const one of mine.purchases)
|
|
46
|
+
console.log(`${one.id} ${one.kind.padEnd(6)} ${one.title} · ${one.artistName} ${new Date(one.paidAt).toISOString().slice(0, 10)}\n ${one.zip ?? "(not paid yet)"}`);
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
if (sub === "catalog" || sub === "catalogue") {
|
|
50
|
+
const view = await catalogue(arg && !arg.startsWith("--") ? arg : undefined);
|
|
51
|
+
if (json) {
|
|
52
|
+
console.log(JSON.stringify(view, null, 2));
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
for (const genre of view.genres) {
|
|
56
|
+
console.log(`\n${genre.genre}${genre.station ? ` (live: /watch/${genre.station})` : ""}`);
|
|
57
|
+
for (const artist of genre.artists) {
|
|
58
|
+
console.log(` ${artist.name} [${artist.key}]`);
|
|
59
|
+
for (const album of artist.albums)
|
|
60
|
+
console.log(` ${album.year} ${album.title} [${album.key}] ${album.songs.length} songs${album.complete ? "" : " (recording)"}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
console.log(`\n℗ ${view.holder}. ${PRICE_WORDS}: nixamp music buy --server ${view.server ?? "URL"} --kind album --artist KEY --album KEY`);
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
if (sub === "buy") {
|
|
67
|
+
const fromLink = arg && !arg.startsWith("--") ? fromBuyLink(arg) : null;
|
|
68
|
+
if (arg && !arg.startsWith("--") && !fromLink) {
|
|
69
|
+
console.error("nixamp music buy: that is not a nixamp.com/buy link");
|
|
70
|
+
return 2;
|
|
71
|
+
}
|
|
72
|
+
const input = {
|
|
73
|
+
...(fromLink ?? { server: flag(argv, "--server"), kind: flag(argv, "--kind"), artist: flag(argv, "--artist"), album: flag(argv, "--album"), n: flag(argv, "--n") ? Number(flag(argv, "--n")) : undefined }),
|
|
74
|
+
coin: flag(argv, "--coin") ?? "USDC_POL",
|
|
75
|
+
...(flag(argv, "--email") ? { email: flag(argv, "--email") } : {}),
|
|
76
|
+
};
|
|
77
|
+
if (typeof input["n"] === "string")
|
|
78
|
+
input["n"] = Number(input["n"]);
|
|
79
|
+
const made = await buy(input);
|
|
80
|
+
if (json) {
|
|
81
|
+
console.log(JSON.stringify(made, null, 2));
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
console.log(`Pay here: ${made.url}\nYour receipt and download, once paid: ${made.receipt}`);
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
if (sub === "download") {
|
|
88
|
+
if (!arg) {
|
|
89
|
+
console.error("nixamp music download needs a purchase id (nixamp music) or all");
|
|
90
|
+
return 2;
|
|
91
|
+
}
|
|
92
|
+
const mine = await purchases();
|
|
93
|
+
const chosen = arg === "all" ? mine.purchases : mine.purchases.filter((one) => one.id === arg);
|
|
94
|
+
if (chosen.length === 0) {
|
|
95
|
+
console.error(`nixamp music: no purchase ${arg}`);
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
for (const one of chosen)
|
|
99
|
+
console.log(`saved ${await download(one, flag(argv, "--out") ?? ".")}`);
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
console.error(`nixamp music: no command "${sub}".\n\n${MUSIC_USAGE}`);
|
|
103
|
+
return 2;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error(`nixamp music: ${error.message}`);
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export declare const DEFAULT_CATALOGUE = "https://live.streaming.pit.world";
|
|
2
|
+
export interface CatalogueSong {
|
|
3
|
+
n: number;
|
|
4
|
+
title: string;
|
|
5
|
+
seconds: number;
|
|
6
|
+
lyrics?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface CatalogueAlbum {
|
|
9
|
+
key: string;
|
|
10
|
+
title: string;
|
|
11
|
+
year: number;
|
|
12
|
+
concept: string;
|
|
13
|
+
cover: boolean;
|
|
14
|
+
complete: boolean;
|
|
15
|
+
songs: CatalogueSong[];
|
|
16
|
+
}
|
|
17
|
+
export interface CatalogueArtist {
|
|
18
|
+
key: string;
|
|
19
|
+
name: string;
|
|
20
|
+
genre: string;
|
|
21
|
+
bio: string;
|
|
22
|
+
albums: CatalogueAlbum[];
|
|
23
|
+
}
|
|
24
|
+
export interface Catalogue {
|
|
25
|
+
holder: string;
|
|
26
|
+
server?: string;
|
|
27
|
+
genres: Array<{
|
|
28
|
+
genre: string;
|
|
29
|
+
station: string;
|
|
30
|
+
artists: CatalogueArtist[];
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
export interface Bought {
|
|
34
|
+
id: string;
|
|
35
|
+
kind: "song" | "album" | "artist";
|
|
36
|
+
title: string;
|
|
37
|
+
artistName: string;
|
|
38
|
+
server: string;
|
|
39
|
+
status: string;
|
|
40
|
+
paidAt: number;
|
|
41
|
+
amountUsd: number;
|
|
42
|
+
zip?: string;
|
|
43
|
+
link?: string;
|
|
44
|
+
}
|
|
45
|
+
type Fetcher = typeof fetch;
|
|
46
|
+
export declare function catalogue(server?: string, fetcher?: Fetcher): Promise<Catalogue>;
|
|
47
|
+
/** A /buy link read back into what it sells. */
|
|
48
|
+
export declare function fromBuyLink(link: string): Record<string, string> | null;
|
|
49
|
+
export declare function buy(input: Record<string, unknown>, fetcher?: Fetcher): Promise<{
|
|
50
|
+
url: string;
|
|
51
|
+
id: string;
|
|
52
|
+
token: string;
|
|
53
|
+
receipt: string;
|
|
54
|
+
}>;
|
|
55
|
+
export declare function purchases(fetcher?: Fetcher): Promise<{
|
|
56
|
+
email: string;
|
|
57
|
+
purchases: Bought[];
|
|
58
|
+
}>;
|
|
59
|
+
/** A purchase's zip, saved under `dir` with the name its server gives it. */
|
|
60
|
+
export declare function download(one: Bought, dir: string, fetcher?: Fetcher): Promise<string>;
|
|
61
|
+
export declare const PRICE_WORDS = "a song 10\u00A2, an album $1, everything by an artist $2";
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AI label's catalogue and the music store, for the terminal and MCP.
|
|
3
|
+
*
|
|
4
|
+
* The catalogue is read from the server that holds the music (live.streaming
|
|
5
|
+
* by default); buying and what was bought are nixamp.com's (music-store.ts),
|
|
6
|
+
* with this machine's sign-in when there is one.
|
|
7
|
+
*/
|
|
8
|
+
import { createWriteStream, mkdirSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { Readable } from "node:stream";
|
|
11
|
+
import { pipeline } from "node:stream/promises";
|
|
12
|
+
import { readSession } from "./session.js";
|
|
13
|
+
export const DEFAULT_CATALOGUE = "https://live.streaming.pit.world";
|
|
14
|
+
function site() {
|
|
15
|
+
return (readSession()?.site ?? process.env["NIXAMP_SITE"] ?? "https://nixamp.com").replace(/\/+$/, "");
|
|
16
|
+
}
|
|
17
|
+
function auth() {
|
|
18
|
+
const token = readSession()?.token;
|
|
19
|
+
return token ? { authorization: `Bearer ${token}` } : {};
|
|
20
|
+
}
|
|
21
|
+
export async function catalogue(server = DEFAULT_CATALOGUE, fetcher = fetch) {
|
|
22
|
+
const response = await fetcher(`${server.replace(/\/+$/, "")}/api/v1/label`, { signal: AbortSignal.timeout(15_000) });
|
|
23
|
+
if (!response.ok)
|
|
24
|
+
throw new Error(`${server} has no AI label catalogue (HTTP ${response.status})`);
|
|
25
|
+
return await response.json();
|
|
26
|
+
}
|
|
27
|
+
/** A /buy link read back into what it sells. */
|
|
28
|
+
export function fromBuyLink(link) {
|
|
29
|
+
try {
|
|
30
|
+
const url = new URL(link);
|
|
31
|
+
if (url.pathname !== "/buy")
|
|
32
|
+
return null;
|
|
33
|
+
return Object.fromEntries(url.searchParams.entries());
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function buy(input, fetcher = fetch) {
|
|
40
|
+
const response = await fetcher(`${site()}/api/v1/store/checkout`, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: { "content-type": "application/json", ...auth() },
|
|
43
|
+
body: JSON.stringify(input),
|
|
44
|
+
});
|
|
45
|
+
const body = await response.json().catch(() => ({}));
|
|
46
|
+
if (!response.ok || !body.url)
|
|
47
|
+
throw new Error(body.error ?? `nixamp.com answered ${response.status}`);
|
|
48
|
+
return { url: body.url, id: body.id ?? "", token: body.token ?? "", receipt: `${site()}/purchases/${body.id}?t=${body.token}` };
|
|
49
|
+
}
|
|
50
|
+
export async function purchases(fetcher = fetch) {
|
|
51
|
+
if (!readSession())
|
|
52
|
+
throw new Error("not signed in: run `nixamp login` (with the email you bought with)");
|
|
53
|
+
const response = await fetcher(`${site()}/api/v1/store/purchases`, { headers: auth() });
|
|
54
|
+
const body = await response.json().catch(() => ({}));
|
|
55
|
+
if (!response.ok)
|
|
56
|
+
throw new Error(body.error ?? `nixamp.com answered ${response.status}`);
|
|
57
|
+
return { email: body.email ?? "", purchases: body.purchases ?? [] };
|
|
58
|
+
}
|
|
59
|
+
/** A purchase's zip, saved under `dir` with the name its server gives it. */
|
|
60
|
+
export async function download(one, dir, fetcher = fetch) {
|
|
61
|
+
if (!one.zip)
|
|
62
|
+
throw new Error(`"${one.title}" is not paid yet`);
|
|
63
|
+
const response = await fetcher(one.zip);
|
|
64
|
+
if (!response.ok || !response.body)
|
|
65
|
+
throw new Error(`the download of "${one.title}" answered HTTP ${response.status}`);
|
|
66
|
+
const named = /filename\*=UTF-8''([^;]+)/.exec(response.headers.get("content-disposition") ?? "")?.[1];
|
|
67
|
+
const name = (named ? decodeURIComponent(named) : `${one.artistName} - ${one.title}.zip`).replace(/[/\\]/g, "");
|
|
68
|
+
mkdirSync(dir, { recursive: true });
|
|
69
|
+
const path = join(dir, name);
|
|
70
|
+
await pipeline(Readable.fromWeb(response.body), createWriteStream(path));
|
|
71
|
+
return path;
|
|
72
|
+
}
|
|
73
|
+
export const PRICE_WORDS = "a song 10¢, an album $1, everything by an artist $2";
|
package/dist/station-cli.js
CHANGED
|
@@ -466,7 +466,9 @@ async function labelCommand(argv, call, show) {
|
|
|
466
466
|
`files ${String(body["root"])}/<genre>/<artist>/<year>-<album>/`,
|
|
467
467
|
`composer ${String(body["composer"] ?? "none")}; words by ${String(body["writer"] ?? "nobody (no model key)")}`,
|
|
468
468
|
`this month ${String(body["secondsThisMonth"])} of ${Number(settings["monthlySeconds"]) === 0 ? "unlimited" : String(settings["monthlySeconds"])} s${body["busy"] ? `; recording ${String(body["busy"])}` : ""}`,
|
|
469
|
-
`albums ${String(settings["songsPerAlbum"])} songs of ${String(settings["songSeconds"])} s; ${String(settings["seedAlbums"])} to start, then one every ${String(settings["everyDays"])} days`,
|
|
469
|
+
`albums ${String(settings["songsPerAlbum"])} songs of ${String(settings["songSeconds"])} s; ${String(settings["seedAlbums"])} to start, then one every ${String(settings["everyDays"])} days; ${String(settings["parallel"] ?? 1)} at a time`,
|
|
470
|
+
`genres ${(settings["genreOrder"] ?? []).join(", ")} first`,
|
|
471
|
+
`after that ${Number(settings["albumsPerDay"] ?? 0) > 0 ? `${String(settings["albumsPerDay"])} album a day, a new artist ${Math.round(Number(settings["newArtistChance"] ?? 0) * 100)}% of the time` : `one per artist every ${String(settings["everyDays"])} days`}`,
|
|
470
472
|
...(body["waiting"] ? [`waiting ${String(body["waiting"])}`] : []),
|
|
471
473
|
"",
|
|
472
474
|
...(artists.length ? artists.map(describe) : ["Nobody signed yet. nixamp station label sign --roster"]),
|
|
@@ -530,6 +532,11 @@ async function labelCommand(argv, call, show) {
|
|
|
530
532
|
seedAlbums: number("--seed-albums"),
|
|
531
533
|
songsPerAlbum: number("--songs"),
|
|
532
534
|
songSeconds: number("--seconds"),
|
|
535
|
+
parallel: number("--parallel"),
|
|
536
|
+
albumsPerDay: number("--per-day"),
|
|
537
|
+
videos: has(argv, "--no-videos") ? false : has(argv, "--videos") ? true : undefined,
|
|
538
|
+
newArtistChance: number("--new-artists"),
|
|
539
|
+
genreOrder: flag(argv, "--genres"),
|
|
533
540
|
enabled: has(argv, "--off") ? false : has(argv, "--on") ? true : undefined,
|
|
534
541
|
publish: has(argv, "--unlisted") ? false : has(argv, "--public") ? true : undefined,
|
|
535
542
|
}).filter(([, value]) => value !== undefined));
|
package/dist/station-mcp.d.ts
CHANGED
|
@@ -46,6 +46,10 @@ export declare const STATION_TOOLS: ({
|
|
|
46
46
|
seedAlbums?: undefined;
|
|
47
47
|
songsPerAlbum?: undefined;
|
|
48
48
|
songSeconds?: undefined;
|
|
49
|
+
parallel?: undefined;
|
|
50
|
+
albumsPerDay?: undefined;
|
|
51
|
+
newArtistChance?: undefined;
|
|
52
|
+
genreOrder?: undefined;
|
|
49
53
|
state?: undefined;
|
|
50
54
|
reason?: undefined;
|
|
51
55
|
open?: undefined;
|
|
@@ -117,6 +121,10 @@ export declare const STATION_TOOLS: ({
|
|
|
117
121
|
seedAlbums?: undefined;
|
|
118
122
|
songsPerAlbum?: undefined;
|
|
119
123
|
songSeconds?: undefined;
|
|
124
|
+
parallel?: undefined;
|
|
125
|
+
albumsPerDay?: undefined;
|
|
126
|
+
newArtistChance?: undefined;
|
|
127
|
+
genreOrder?: undefined;
|
|
120
128
|
state?: undefined;
|
|
121
129
|
reason?: undefined;
|
|
122
130
|
open?: undefined;
|
|
@@ -211,6 +219,10 @@ export declare const STATION_TOOLS: ({
|
|
|
211
219
|
seedAlbums?: undefined;
|
|
212
220
|
songsPerAlbum?: undefined;
|
|
213
221
|
songSeconds?: undefined;
|
|
222
|
+
parallel?: undefined;
|
|
223
|
+
albumsPerDay?: undefined;
|
|
224
|
+
newArtistChance?: undefined;
|
|
225
|
+
genreOrder?: undefined;
|
|
214
226
|
state?: undefined;
|
|
215
227
|
reason?: undefined;
|
|
216
228
|
open?: undefined;
|
|
@@ -302,6 +314,10 @@ export declare const STATION_TOOLS: ({
|
|
|
302
314
|
seedAlbums?: undefined;
|
|
303
315
|
songsPerAlbum?: undefined;
|
|
304
316
|
songSeconds?: undefined;
|
|
317
|
+
parallel?: undefined;
|
|
318
|
+
albumsPerDay?: undefined;
|
|
319
|
+
newArtistChance?: undefined;
|
|
320
|
+
genreOrder?: undefined;
|
|
305
321
|
state?: undefined;
|
|
306
322
|
reason?: undefined;
|
|
307
323
|
open?: undefined;
|
|
@@ -375,6 +391,10 @@ export declare const STATION_TOOLS: ({
|
|
|
375
391
|
seedAlbums?: undefined;
|
|
376
392
|
songsPerAlbum?: undefined;
|
|
377
393
|
songSeconds?: undefined;
|
|
394
|
+
parallel?: undefined;
|
|
395
|
+
albumsPerDay?: undefined;
|
|
396
|
+
newArtistChance?: undefined;
|
|
397
|
+
genreOrder?: undefined;
|
|
378
398
|
state?: undefined;
|
|
379
399
|
reason?: undefined;
|
|
380
400
|
open?: undefined;
|
|
@@ -455,6 +475,10 @@ export declare const STATION_TOOLS: ({
|
|
|
455
475
|
seedAlbums?: undefined;
|
|
456
476
|
songsPerAlbum?: undefined;
|
|
457
477
|
songSeconds?: undefined;
|
|
478
|
+
parallel?: undefined;
|
|
479
|
+
albumsPerDay?: undefined;
|
|
480
|
+
newArtistChance?: undefined;
|
|
481
|
+
genreOrder?: undefined;
|
|
458
482
|
state?: undefined;
|
|
459
483
|
reason?: undefined;
|
|
460
484
|
open?: undefined;
|
|
@@ -535,6 +559,10 @@ export declare const STATION_TOOLS: ({
|
|
|
535
559
|
seedAlbums?: undefined;
|
|
536
560
|
songsPerAlbum?: undefined;
|
|
537
561
|
songSeconds?: undefined;
|
|
562
|
+
parallel?: undefined;
|
|
563
|
+
albumsPerDay?: undefined;
|
|
564
|
+
newArtistChance?: undefined;
|
|
565
|
+
genreOrder?: undefined;
|
|
538
566
|
state?: undefined;
|
|
539
567
|
reason?: undefined;
|
|
540
568
|
open?: undefined;
|
|
@@ -632,6 +660,10 @@ export declare const STATION_TOOLS: ({
|
|
|
632
660
|
seedAlbums?: undefined;
|
|
633
661
|
songsPerAlbum?: undefined;
|
|
634
662
|
songSeconds?: undefined;
|
|
663
|
+
parallel?: undefined;
|
|
664
|
+
albumsPerDay?: undefined;
|
|
665
|
+
newArtistChance?: undefined;
|
|
666
|
+
genreOrder?: undefined;
|
|
635
667
|
state?: undefined;
|
|
636
668
|
reason?: undefined;
|
|
637
669
|
open?: undefined;
|
|
@@ -738,6 +770,24 @@ export declare const STATION_TOOLS: ({
|
|
|
738
770
|
songSeconds: {
|
|
739
771
|
type: string;
|
|
740
772
|
};
|
|
773
|
+
parallel: {
|
|
774
|
+
type: string;
|
|
775
|
+
};
|
|
776
|
+
albumsPerDay: {
|
|
777
|
+
type: string;
|
|
778
|
+
description: string;
|
|
779
|
+
};
|
|
780
|
+
newArtistChance: {
|
|
781
|
+
type: string;
|
|
782
|
+
description: string;
|
|
783
|
+
};
|
|
784
|
+
genreOrder: {
|
|
785
|
+
type: string;
|
|
786
|
+
items: {
|
|
787
|
+
readonly type: "string";
|
|
788
|
+
};
|
|
789
|
+
description: string;
|
|
790
|
+
};
|
|
741
791
|
state?: undefined;
|
|
742
792
|
reason?: undefined;
|
|
743
793
|
open?: undefined;
|
|
@@ -798,6 +848,10 @@ export declare const STATION_TOOLS: ({
|
|
|
798
848
|
seedAlbums?: undefined;
|
|
799
849
|
songsPerAlbum?: undefined;
|
|
800
850
|
songSeconds?: undefined;
|
|
851
|
+
parallel?: undefined;
|
|
852
|
+
albumsPerDay?: undefined;
|
|
853
|
+
newArtistChance?: undefined;
|
|
854
|
+
genreOrder?: undefined;
|
|
801
855
|
server: {
|
|
802
856
|
type: "string";
|
|
803
857
|
description: string;
|
|
@@ -887,6 +941,10 @@ export declare const STATION_TOOLS: ({
|
|
|
887
941
|
seedAlbums?: undefined;
|
|
888
942
|
songsPerAlbum?: undefined;
|
|
889
943
|
songSeconds?: undefined;
|
|
944
|
+
parallel?: undefined;
|
|
945
|
+
albumsPerDay?: undefined;
|
|
946
|
+
newArtistChance?: undefined;
|
|
947
|
+
genreOrder?: undefined;
|
|
890
948
|
state?: undefined;
|
|
891
949
|
reason?: undefined;
|
|
892
950
|
open?: undefined;
|
|
@@ -978,6 +1036,10 @@ export declare const STATION_TOOLS: ({
|
|
|
978
1036
|
seedAlbums?: undefined;
|
|
979
1037
|
songsPerAlbum?: undefined;
|
|
980
1038
|
songSeconds?: undefined;
|
|
1039
|
+
parallel?: undefined;
|
|
1040
|
+
albumsPerDay?: undefined;
|
|
1041
|
+
newArtistChance?: undefined;
|
|
1042
|
+
genreOrder?: undefined;
|
|
981
1043
|
state?: undefined;
|
|
982
1044
|
reason?: undefined;
|
|
983
1045
|
open?: undefined;
|
package/dist/station-mcp.js
CHANGED
|
@@ -118,7 +118,10 @@ export const STATION_TOOLS = [
|
|
|
118
118
|
sound: { ...STRING, description: "The music in words: instruments, tempo, mood. Name no real artist." },
|
|
119
119
|
voice: STRING, bio: STRING, themes: STRING, enabled: { type: "boolean" }, publish: { type: "boolean" }, holder: STRING,
|
|
120
120
|
monthlySeconds: { type: "integer" }, everyDays: { type: "integer" }, seedAlbums: { type: "integer" },
|
|
121
|
-
songsPerAlbum: { type: "integer" }, songSeconds: { type: "integer" },
|
|
121
|
+
songsPerAlbum: { type: "integer" }, songSeconds: { type: "integer" }, parallel: { type: "integer" },
|
|
122
|
+
albumsPerDay: { type: "integer", description: "After the first sweep: new albums a day, by a random artist or a newly invented one." },
|
|
123
|
+
newArtistChance: { type: "number", description: "0-1: how often the day's album is by a new artist." },
|
|
124
|
+
genreOrder: { type: "array", items: STRING, description: "Genres whose artists get albums first, in order." },
|
|
122
125
|
},
|
|
123
126
|
},
|
|
124
127
|
},
|
|
@@ -237,7 +240,7 @@ export async function callStationTool(name, args, options = {}) {
|
|
|
237
240
|
const key = typeof args["key"] === "string" && /^[a-f0-9]{10}$/.test(args["key"]) ? args["key"] : "";
|
|
238
241
|
const answer = action === "status" ? await call("GET", "/api/v1/stations/label")
|
|
239
242
|
: action === "sign" ? await call("POST", "/api/v1/stations/label/artists", pick(["preset", "roster", "name", "genre", "sound", "voice", "bio", "themes", "enabled"]))
|
|
240
|
-
: action === "configure" ? await call("POST", "/api/v1/stations/label", pick(["holder", "monthlySeconds", "everyDays", "seedAlbums", "songsPerAlbum", "songSeconds", "enabled", "publish"]))
|
|
243
|
+
: action === "configure" ? await call("POST", "/api/v1/stations/label", pick(["holder", "monthlySeconds", "everyDays", "seedAlbums", "songsPerAlbum", "songSeconds", "parallel", "genreOrder", "albumsPerDay", "newArtistChance", "enabled", "publish"]))
|
|
241
244
|
: action === "run" ? await call("POST", "/api/v1/stations/label/run", {})
|
|
242
245
|
: action === "resume" ? await call("POST", "/api/v1/stations/label", { resume: true })
|
|
243
246
|
: !key ? null
|
package/dist/station-usage.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** `nixamp station` usage, apart so `nixamp help station` does not load the client. */
|
|
2
|
-
export declare const USAGE = "nixamp station \u2014 make a collection into a station\n\n nixamp station list\n nixamp station create --name NAME (--folder PATH | --playlist ID | --items 0,1,2) [--shuffle] [--no-repeat] [--bitrate 128] [--breaks 600 (the default; 0 for none)]\n nixamp station show ID\n nixamp station status ID\n nixamp station start ID\n nixamp station stop ID\n nixamp station skip ID\n nixamp station breaks ID SECONDS a spot between entries every SECONDS (120..86400; --off for none)\n nixamp station retry ID try every left-out file again\n nixamp station publish ID --basis owned|permission|licensed|public-domain [--note TEXT] [--unlisted] [--no-list]\n nixamp station unpublish ID [--private]\n nixamp station link ID [--rotate]\n nixamp station delete ID\n nixamp station guide-host NAME [--remove]\n NAME's front page is the public station guide (e.g. a Moshpit name)\n nixamp station guide-ads [--site ID] [--slot ID] [--format banner_300x250|text_link] [--off]\n crawlproof analytics (project ID) and an ad unit (slot ID) on the guide\n nixamp station guide-peer URL [--remove]\n the guide also lists the public, listed stations of the server at URL\n nixamp station auto [--no-video] [--min 10] [--films 3] [--tv 2] [--breaks 600] [--no-start]\n a station per genre, kept in step with the library (--off stops it)\n\n AI radio (music composed for a station, a few tracks a day):\n nixamp station ai what is composing, this month's seconds, the stations\n nixamp station ai create (--preset lofi|synthwave|ambient|chiptune|punk|jazz | --name NAME --style TEXT)\n [--vocals] [--seconds 120] [--per-day 2] [--unlisted] public on the guide unless --unlisted\n nixamp station ai generate KEY compose one now\n nixamp station ai set KEY [--style TEXT] [--per-day N] [--seconds N] [--off|--on]\n nixamp station ai remove KEY [--files]\n nixamp station ai budget SECONDS seconds of music a month across AI stations (0: no cap)\n nixamp station ai resume after the provider ran out of credit\n nixamp station ai key NAME ELEVENLABS_API_KEY | GEMINI_API_KEY | OPENAI_API_KEY | ANTHROPIC_API_KEY,\n NIXAMP_OPENAI_URL | NIXAMP_OPENAI_MODEL (an OpenAI-compatible stand-in),\n NIXAMP_MUSIC_MODEL (music_v1|music_v2|music_v2_5), from stdin\n\n AI label (AI artists who release albums on their own; a station per genre; the catalogue at /label):\n nixamp station label artists, albums, this month's seconds, what it is waiting for\n nixamp station label sign --roster the roster: three artists in each of Rock, Hip-Hop, Synthwave, Country,\n Metal and Pop (nixamp station label lists their keys)\n nixamp station label sign (--preset KEY | --name NAME --genre GENRE --sound TEXT [--voice TEXT] [--bio TEXT] [--themes TEXT])\n nixamp station label album KEY plan the artist's next album now and record it\n nixamp station label set KEY [--sound TEXT] [--voice TEXT] [--bio TEXT] [--themes TEXT] [--off|--on]\n nixamp station label drop KEY [--files]\n nixamp station label config [--holder \"Profullstack, Inc.\"] [--budget SECONDS] [--every-days 30] [--seed-albums 1]\n [--songs 8] [--seconds 150] [--on|--off] [--public|--unlisted]\n nixamp station label run record whatever is due now\n nixamp station label resume after the provider ran out of credit\n\n Listener shares (anybody shares an MP3 link or file from the guide; AI files and checks it):\n nixamp station shares the setting and what is waiting\n nixamp station shares on [--owner-moderation] [--unlisted] [--max-mb 60] [--per-hour 6]\n nixamp station shares off\n nixamp station shares list [--state held|approved|reported|rejected]\n nixamp station shares approve|reject|delete SHARE\n nixamp station shares trust ADDRESS [--remove] believe this proxy's X-Forwarded-For\n\n Lyrics (shown on the station page from each file's own tag, else nichedb.dev; ours published back):\n nixamp station lyrics the key, what was published, which stations may publish\n nixamp station lyrics publish ID publish a station's words now (public, listed, basis owned only)\n nixamp station lyrics key nichedb's key (NIXAMP_LYRICS_KEY), from stdin (--clear forgets it)\n\n Copyright holders (the now-playing card: who holds a track's copyright, and how to support them):\n nixamp station holder list\n nixamp station holder set NAME [--artist ARTIST]... [--station ID]... [--support https://...] [--label Subscribe] [--license TEXT]\n nixamp station holder remove ID\n\n --json machine-readable output\n --url, --key another server, as with `nixamp admin`\n\nA new station is private and stopped. Starting it does not publish it.";
|
|
2
|
+
export declare const USAGE = "nixamp station \u2014 make a collection into a station\n\n nixamp station list\n nixamp station create --name NAME (--folder PATH | --playlist ID | --items 0,1,2) [--shuffle] [--no-repeat] [--bitrate 128] [--breaks 600 (the default; 0 for none)]\n nixamp station show ID\n nixamp station status ID\n nixamp station start ID\n nixamp station stop ID\n nixamp station skip ID\n nixamp station breaks ID SECONDS a spot between entries every SECONDS (120..86400; --off for none)\n nixamp station retry ID try every left-out file again\n nixamp station publish ID --basis owned|permission|licensed|public-domain [--note TEXT] [--unlisted] [--no-list]\n nixamp station unpublish ID [--private]\n nixamp station link ID [--rotate]\n nixamp station delete ID\n nixamp station guide-host NAME [--remove]\n NAME's front page is the public station guide (e.g. a Moshpit name)\n nixamp station guide-ads [--site ID] [--slot ID] [--format banner_300x250|text_link] [--off]\n crawlproof analytics (project ID) and an ad unit (slot ID) on the guide\n nixamp station guide-peer URL [--remove]\n the guide also lists the public, listed stations of the server at URL\n nixamp station auto [--no-video] [--min 10] [--films 3] [--tv 2] [--breaks 600] [--no-start]\n a station per genre, kept in step with the library (--off stops it)\n\n AI radio (music composed for a station, a few tracks a day):\n nixamp station ai what is composing, this month's seconds, the stations\n nixamp station ai create (--preset lofi|synthwave|ambient|chiptune|punk|jazz | --name NAME --style TEXT)\n [--vocals] [--seconds 120] [--per-day 2] [--unlisted] public on the guide unless --unlisted\n nixamp station ai generate KEY compose one now\n nixamp station ai set KEY [--style TEXT] [--per-day N] [--seconds N] [--off|--on]\n nixamp station ai remove KEY [--files]\n nixamp station ai budget SECONDS seconds of music a month across AI stations (0: no cap)\n nixamp station ai resume after the provider ran out of credit\n nixamp station ai key NAME ELEVENLABS_API_KEY | GEMINI_API_KEY | OPENAI_API_KEY | ANTHROPIC_API_KEY,\n NIXAMP_OPENAI_URL | NIXAMP_OPENAI_MODEL (an OpenAI-compatible stand-in),\n NIXAMP_MUSIC_MODEL (music_v1|music_v2|music_v2_5), from stdin\n\n AI label (AI artists who release albums on their own; a station per genre; the catalogue at /label):\n nixamp station label artists, albums, this month's seconds, what it is waiting for\n nixamp station label sign --roster the roster: three artists in each of Rock, Hip-Hop, Synthwave, Country,\n Metal and Pop (nixamp station label lists their keys)\n nixamp station label sign (--preset KEY | --name NAME --genre GENRE --sound TEXT [--voice TEXT] [--bio TEXT] [--themes TEXT])\n nixamp station label album KEY plan the artist's next album now and record it\n nixamp station label set KEY [--sound TEXT] [--voice TEXT] [--bio TEXT] [--themes TEXT] [--off|--on]\n nixamp station label drop KEY [--files]\n nixamp station label config [--holder \"Profullstack, Inc.\"] [--budget SECONDS] [--every-days 30] [--seed-albums 1]\n [--songs 8] [--seconds 150] [--on|--off] [--public|--unlisted]\n [--parallel 4] [--genres \"Punk,Blues,Country\"] songs made at once; genres first\n [--per-day 1] [--new-artists 0.25] after the first sweep: albums a day, share by new artists\n [--videos|--no-videos] a music video per song and a TV station of them\n nixamp station label run record whatever is due now\n nixamp station label resume after the provider ran out of credit\n\n Listener shares (anybody shares an MP3 link or file from the guide; AI files and checks it):\n nixamp station shares the setting and what is waiting\n nixamp station shares on [--owner-moderation] [--unlisted] [--max-mb 60] [--per-hour 6]\n nixamp station shares off\n nixamp station shares list [--state held|approved|reported|rejected]\n nixamp station shares approve|reject|delete SHARE\n nixamp station shares trust ADDRESS [--remove] believe this proxy's X-Forwarded-For\n\n Lyrics (shown on the station page from each file's own tag, else nichedb.dev; ours published back):\n nixamp station lyrics the key, what was published, which stations may publish\n nixamp station lyrics publish ID publish a station's words now (public, listed, basis owned only)\n nixamp station lyrics key nichedb's key (NIXAMP_LYRICS_KEY), from stdin (--clear forgets it)\n\n Copyright holders (the now-playing card: who holds a track's copyright, and how to support them):\n nixamp station holder list\n nixamp station holder set NAME [--artist ARTIST]... [--station ID]... [--support https://...] [--label Subscribe] [--license TEXT]\n nixamp station holder remove ID\n\n --json machine-readable output\n --url, --key another server, as with `nixamp admin`\n\nA new station is private and stopped. Starting it does not publish it.";
|
package/dist/station-usage.js
CHANGED
|
@@ -46,6 +46,9 @@ export const USAGE = `nixamp station — make a collection into a station
|
|
|
46
46
|
nixamp station label drop KEY [--files]
|
|
47
47
|
nixamp station label config [--holder "Profullstack, Inc."] [--budget SECONDS] [--every-days 30] [--seed-albums 1]
|
|
48
48
|
[--songs 8] [--seconds 150] [--on|--off] [--public|--unlisted]
|
|
49
|
+
[--parallel 4] [--genres "Punk,Blues,Country"] songs made at once; genres first
|
|
50
|
+
[--per-day 1] [--new-artists 0.25] after the first sweep: albums a day, share by new artists
|
|
51
|
+
[--videos|--no-videos] a music video per song and a TV station of them
|
|
49
52
|
nixamp station label run record whatever is due now
|
|
50
53
|
nixamp station label resume after the provider ran out of credit
|
|
51
54
|
|