nixamp 0.78.5 → 0.80.1

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 (107) hide show
  1. package/README.md +6 -5
  2. package/backtoschool/dist/assets/{ar-CYu0byMf.js → ar-BYDVUbTZ.js} +1 -1
  3. package/backtoschool/dist/assets/{cs-BTTM7DeB.js → cs-DsCx7BAy.js} +1 -1
  4. package/backtoschool/dist/assets/{da-iixezQtd.js → da-5tLM7Wal.js} +1 -1
  5. package/backtoschool/dist/assets/{de-Sx3acevR.js → de-2Q8k6IKm.js} +1 -1
  6. package/backtoschool/dist/assets/{es-ByCnw8ja.js → es-CVwFkQ0E.js} +1 -1
  7. package/backtoschool/dist/assets/{fi-BCeFoeH5.js → fi-D7KuvUvL.js} +1 -1
  8. package/backtoschool/dist/assets/{fr-Nz5hMzAE.js → fr-CkWg-nDv.js} +1 -1
  9. package/backtoschool/dist/assets/{hi-x5-IG1rl.js → hi-DayNvhO4.js} +1 -1
  10. package/backtoschool/dist/assets/{hls-OR5FY6YP-O3qhTXm4.js → hls-OR5FY6YP-D2hwLr-P.js} +1 -1
  11. package/backtoschool/dist/assets/{hu-QqIkGcMe.js → hu-BqS46UyM.js} +1 -1
  12. package/backtoschool/dist/assets/{id-OVwO1Uoi.js → id-DTiKw_BS.js} +1 -1
  13. package/backtoschool/dist/assets/{index-Dgqs6yM_.js → index-EM-0SwBp.js} +3 -3
  14. package/backtoschool/dist/assets/{it-CmssY5Ut.js → it-B60apeGa.js} +1 -1
  15. package/backtoschool/dist/assets/{mpegts-RQlLEU1A.js → mpegts-C4v5WI5W.js} +1 -1
  16. package/backtoschool/dist/assets/{mpegts-Y2FARLFE-y49QH7a5.js → mpegts-Y2FARLFE-D4OTyGyp.js} +1 -1
  17. package/backtoschool/dist/assets/{nl-VkQxlPku.js → nl-DctlIuWS.js} +1 -1
  18. package/backtoschool/dist/assets/{ru-Dk8FAbDx.js → ru-CO8g0gfi.js} +1 -1
  19. package/backtoschool/dist/assets/{sv-CNs1UySx.js → sv-CoFMz-Qg.js} +1 -1
  20. package/backtoschool/dist/assets/{uk-BDB1YayY.js → uk-C3_GprSP.js} +1 -1
  21. package/backtoschool/dist/assets/{vi-DnEIKzy7.js → vi-B0RDG4iN.js} +1 -1
  22. package/backtoschool/dist/assets/{zh-B_atHA4S.js → zh--yO6izcR.js} +1 -1
  23. package/backtoschool/dist/index.html +3 -3
  24. package/dist/ai-keys.d.ts +1 -1
  25. package/dist/ai-keys.js +1 -1
  26. package/dist/ai-label.d.ts +38 -0
  27. package/dist/ai-label.js +237 -5
  28. package/dist/ai-text.d.ts +42 -1
  29. package/dist/ai-text.js +69 -7
  30. package/dist/event-writer.d.ts +10 -1
  31. package/dist/event-writer.js +27 -8
  32. package/dist/label-routes.js +9 -0
  33. package/dist/library-mcp.d.ts +6 -6
  34. package/dist/music-hub.d.ts +11 -0
  35. package/dist/music-hub.js +33 -2
  36. package/dist/nixamp-link-api.d.ts +5 -1
  37. package/dist/nixamp-link-api.js +12 -2
  38. package/dist/nixamp-link-types.d.ts +2 -0
  39. package/dist/nixamp-link.d.ts +22 -9
  40. package/dist/nixamp-link.js +75 -58
  41. package/dist/oauth.d.ts +1 -0
  42. package/dist/oauth.js +8 -1
  43. package/dist/profiles.d.ts +1 -1
  44. package/dist/profiles.js +0 -0
  45. package/dist/server.js +19 -10
  46. package/dist/station-cli.js +24 -0
  47. package/dist/station-mcp.d.ts +6 -6
  48. package/dist/station-usage.d.ts +1 -1
  49. package/dist/station-usage.js +4 -1
  50. package/dist/universal-footer.d.ts +2 -0
  51. package/dist/universal-footer.js +39 -0
  52. package/package.json +7 -5
  53. package/src/ai-keys.ts +1 -1
  54. package/src/ai-label.ts +240 -5
  55. package/src/ai-text.ts +84 -8
  56. package/src/event-writer.ts +36 -11
  57. package/src/label-routes.ts +9 -0
  58. package/src/music-hub.ts +38 -2
  59. package/src/nixamp-link-api.ts +15 -3
  60. package/src/nixamp-link-types.ts +2 -0
  61. package/src/nixamp-link.ts +77 -61
  62. package/src/oauth.ts +9 -1
  63. package/src/profiles.ts +0 -0
  64. package/src/server.ts +19 -10
  65. package/src/station-cli.ts +23 -0
  66. package/src/station-usage.ts +4 -1
  67. package/src/universal-footer.ts +41 -0
  68. package/web/dist/assets/{ar-CqVLKLkR.js → ar-C5qVRZh9.js} +1 -1
  69. package/web/dist/assets/{chunk-55GBNX64-BDcCEJUP.js → chunk-NW3WDGEQ-CnyjTRUh.js} +1 -1
  70. package/web/dist/assets/{cs-DwgnZ10A.js → cs-fajZsJrs.js} +1 -1
  71. package/web/dist/assets/{da-C6V-fzX4.js → da-fycV1k1N.js} +1 -1
  72. package/web/dist/assets/{de-CyZ00iAl.js → de-QUzTQ_9X.js} +1 -1
  73. package/web/dist/assets/{es-jlhhpSVu.js → es-CQu2leAJ.js} +1 -1
  74. package/web/dist/assets/{fi-o92ZPZq7.js → fi-AgKEsukC.js} +1 -1
  75. package/web/dist/assets/{fr-BcCMVWa_.js → fr-CRbHnW4y.js} +1 -1
  76. package/web/dist/assets/{hi-Dbgp5pgN.js → hi-BuU7xy9L.js} +1 -1
  77. package/web/dist/assets/{hls-OR5FY6YP-_WOdANsC.js → hls-OR5FY6YP-BxZwqFB9.js} +1 -1
  78. package/web/dist/assets/{hu-CRa0dBhC.js → hu-C40Z-HcJ.js} +1 -1
  79. package/web/dist/assets/{id-BF7Q2wE4.js → id-2b7626UL.js} +1 -1
  80. package/web/dist/assets/{it-z9MVvK3u.js → it-7sr3nJuS.js} +1 -1
  81. package/web/dist/assets/main-zuI3sOLC.js +33 -0
  82. package/web/dist/assets/{mpegts-CL9RC5Ls.js → mpegts-DPRtdBXL.js} +1 -1
  83. package/web/dist/assets/{mpegts-Y2FARLFE-C91SD8Pl.js → mpegts-Y2FARLFE-BY7parIQ.js} +1 -1
  84. package/web/dist/assets/{nl-C-9KQwX0.js → nl-BdDGgfzj.js} +1 -1
  85. package/web/dist/assets/{radio-BnQ78a-O.js → radio-CeRhNjpU.js} +1 -1
  86. package/web/dist/assets/{ru-D835Y1dr.js → ru-BMGI8Oa4.js} +1 -1
  87. package/web/dist/assets/{station-C_6L4CDU.js → station-D5tW9jq4.js} +1 -1
  88. package/web/dist/assets/station-D6KfgFXf.css +1 -0
  89. package/web/dist/assets/{sv-DLSLSdjL.js → sv-BXASdl7c.js} +1 -1
  90. package/web/dist/assets/{uk-DgCBRDrZ.js → uk-urnhuqtG.js} +1 -1
  91. package/web/dist/assets/{vi-C23GbtRZ.js → vi-BhLPAKuD.js} +1 -1
  92. package/web/dist/assets/{vod-CbSC5NFA.js → vod-Dt73bt8P.js} +1 -1
  93. package/web/dist/assets/{watch-D-ZRxfJ_.js → watch-GxgHBQcI.js} +1 -1
  94. package/web/dist/assets/{zh-D0dXViuL.js → zh-BaOCIB9u.js} +1 -1
  95. package/web/dist/index.html +58 -12
  96. package/web/dist/music.html +1 -1
  97. package/web/dist/radio.html +5 -5
  98. package/web/dist/station.html +4 -4
  99. package/web/dist/sw.js +30 -30
  100. package/web/dist/vod.html +4 -4
  101. package/web/dist/watch.html +4 -4
  102. package/dist/pit-footer.d.ts +0 -3
  103. package/dist/pit-footer.js +0 -40
  104. package/src/pit-footer.ts +0 -40
  105. package/web/dist/assets/main-Q8gHrr9u.js +0 -26
  106. package/web/dist/assets/station-CxJvFwzV.css +0 -1
  107. /package/web/dist/assets/{chunk-55GBNX64-DR1XNWCJ.css → chunk-NW3WDGEQ-DR1XNWCJ.css} +0 -0
package/dist/server.js CHANGED
@@ -37,7 +37,7 @@ import { anonymousHandle, Handles } from "./handles.js";
37
37
  import { OpenDirs } from "./opendirs.js";
38
38
  import { Servers } from "./servers.js";
39
39
  import { AccountProfiles, hostCard, PHOTO_LIMIT } from "./profiles.js";
40
- import { createProfileHandlers, saveSignupProfile, validateProfile } from "@profullstack/profile";
40
+ import { createProfileHandlers, saveOpenProfileContact, saveSignupProfile, validateProfile } from "@profullstack/profile";
41
41
  import { SIGNUP_PROFILE_FIELDS, signupProfiles } from "./signup-profiles.js";
42
42
  import { NixampLinks, nixampExchange } from "./nixamp-link.js";
43
43
  import { handleNixampLinkApi, nixampLinkPath } from "./nixamp-link-api.js";
@@ -67,7 +67,7 @@ import { handleAiRoutes, isPublicSharePath } from "./ai-routes.js";
67
67
  import { AiLabel, VeoUnavailable } from "./ai-label.js";
68
68
  import { buyLink, handleLabelRoutes, isPublicLabelPath } from "./label-routes.js";
69
69
  import { GrantVerifier, MusicStore, PRICES } from "./music-store.js";
70
- import { handleMusicRoutes, isMusicPath, MusicHub, musicPageFor, serverId } from "./music-hub.js";
70
+ import { handleMusicRoutes, isMusicPath, MusicHub, musicPageFor, parseMoved, serverId } from "./music-hub.js";
71
71
  import { fileLedger, pgLedger, VeoBudgetError, VeoStudio } from "./veo-studio.js";
72
72
  import { KnownServers } from "./known-servers.js";
73
73
  import { handleStoreRoutes, isStorePath } from "./store-routes.js";
@@ -83,7 +83,7 @@ import { aiKey } from "./ai-keys.js";
83
83
  import { STATION_APP_CSP, STATION_PAGE, STATION_PAGE_CSP, STATION_PAGE_HTML, WATCH_APP_CSP } from "./station-page.js";
84
84
  import { handleRoomProxy, handleRooms, isRoomPath, ROOM_PAGE, roomAddress, RoomProbe, roomEntries, roomSlugOf, roomSlugs, RoomStore, watchBase } from "./guide-rooms.js";
85
85
  import { handleVods, VodProbe, vodEntries, VodStore, vodView } from "./guide-vods.js";
86
- import { withPitFooter } from "./pit-footer.js";
86
+ import { withUniversalFooter } from "./universal-footer.js";
87
87
  import { handleFollowPage } from "./follow-page.js";
88
88
  import { asRadioPage, buildCatalogue, isRadioPath, NAV_STYLE, RADIO_ALBUM, RADIO_ARTIST, RADIO_CSP, RADIO_STATION, RADIO_SUB, RADIO_BUY, radioBuyLink, radioBuyQuery, radioNav, radioPeers, radioRewriter, radioStations, renderAlbum, stationImage, renderArtist, renderDirectory, renderMissing, rewriteResponse, } from "./radio-pages.js";
89
89
  import { adCsp, guideAds, guideCsp, withPageAd, guideHosts, guidePeers, peerBase, PeerGuides, renderGuide } from "./station-guide.js";
@@ -112,6 +112,7 @@ import { Durable } from "./durable.js";
112
112
  import { notifyAll, resendEmail, webPush } from "./notify.js";
113
113
  import { vapidKeysFromEnv, vapidPublicKeyResponse } from "@profullstack/notifications/server";
114
114
  import { inviteSubject, inviteText, isEmail, isPhone, watchLink } from "./invite.js";
115
+ import { c0mputeKey } from "./ai-text.js";
115
116
  import { EventWriter } from "./event-writer.js";
116
117
  import { handleLiveApi } from "./live-api.js";
117
118
  import { LiveEvents, eventStructuredData, isTicketed } from "./live-events.js";
@@ -4594,7 +4595,7 @@ export function createHandler(engine, options) {
4594
4595
  bytes = await readBytes(request, PHOTO_LIMIT);
4595
4596
  }
4596
4597
  catch {
4597
- json(response, 413, { error: "a photo may be up to 1 MB" });
4598
+ json(response, 413, { error: "a photo may be up to 10 MB" });
4598
4599
  return;
4599
4600
  }
4600
4601
  const kept = await profiles.setPhoto(who.id, bytes);
@@ -4847,6 +4848,14 @@ export function createHandler(engine, options) {
4847
4848
  html(response, 401, signInFailedPage(result.error || "Could not sign in."));
4848
4849
  return;
4849
4850
  }
4851
+ if (identity.provider === "coinpay" && identity.openprofileUrl && options.signupProfiles) {
4852
+ try {
4853
+ await saveOpenProfileContact(options.signupProfiles, String(result.account.id), identity.openprofileUrl);
4854
+ }
4855
+ catch (e) {
4856
+ console.warn("[coinpay] OpenProfile import skipped:", e instanceof Error ? e.message : e);
4857
+ }
4858
+ }
4850
4859
  if (pending.userCode) {
4851
4860
  // This round trip was approving a terminal. The browser is finished;
4852
4861
  // the session belongs to whatever is polling.
@@ -6667,7 +6676,7 @@ export function createHandler(engine, options) {
6667
6676
  // somewhere usable, but it says 404 so it is never indexed.
6668
6677
  const shell = readIfPossible(join(web, "index.html"));
6669
6678
  if (shell !== null) {
6670
- html(response, 404, requestHost === "nixamp.com" ? withPitFooter(shell) : shell);
6679
+ html(response, 404, requestHost === "nixamp.com" ? withUniversalFooter(shell) : shell);
6671
6680
  return;
6672
6681
  }
6673
6682
  }
@@ -6707,17 +6716,17 @@ export function createHandler(engine, options) {
6707
6716
  }
6708
6717
  const shell = subject ? readIfPossible(file) : null;
6709
6718
  if (subject && shell !== null) {
6710
- html(response, 200, requestHost === "nixamp.com" ? withPitFooter(joinDocument(shell, subject, eventSite ?? "https://nixamp.com")) : joinDocument(shell, subject, eventSite ?? "https://nixamp.com"));
6719
+ html(response, 200, requestHost === "nixamp.com" ? withUniversalFooter(joinDocument(shell, subject, eventSite ?? "https://nixamp.com")) : joinDocument(shell, subject, eventSite ?? "https://nixamp.com"));
6711
6720
  return;
6712
6721
  }
6713
6722
  }
6714
6723
  // nixamp.com's own pages (the app shell, the radio console) carry the
6715
- // pit.world global footer, appended on the way out. A local install
6724
+ // shared Profullstack footer, appended on the way out. A local install
6716
6725
  // serving the same files on its own host is left alone.
6717
6726
  if (requestHost === "nixamp.com" && file.endsWith(".html") && (request.method === "GET" || request.method === "HEAD")) {
6718
6727
  const shell = readIfPossible(file);
6719
6728
  if (shell !== null) {
6720
- const page = withPitFooter(shell);
6729
+ const page = withUniversalFooter(shell);
6721
6730
  response.writeHead(200, {
6722
6731
  ...CORS,
6723
6732
  "content-type": "text/html; charset=utf-8",
@@ -7748,7 +7757,7 @@ export async function serve(argv, version = "0.1.0") {
7748
7757
  return [...new Set([...guidePeers(), ...listed, ...(knownServers?.list().map((one) => one.base) ?? [])])];
7749
7758
  };
7750
7759
  // nixamp.com/music: every server's label in one catalogue (music-hub.ts).
7751
- const musicHub = options.directory ? new MusicHub({ peers: musicServers }) : undefined;
7760
+ const musicHub = options.directory ? new MusicHub({ peers: musicServers, moved: parseMoved(process.env["NIXAMP_MUSIC_MOVED"]) }) : undefined;
7752
7761
  const musicStore = supportPayments && supportPayments.enabled && pool && jwtSecret ? new MusicStore({
7753
7762
  db: pool,
7754
7763
  support: supportPayments,
@@ -8440,7 +8449,7 @@ export async function serve(argv, version = "0.1.0") {
8440
8449
  ...(favorites ? { favorites } : {}),
8441
8450
  ...(settingsSync ? { settingsSync } : {}),
8442
8451
  ...(events ? { events, eventWriter: new EventWriter({
8443
- openaiKey: process.env["OPENAI_API_KEY"], anthropicKey: process.env["ANTHROPIC_API_KEY"],
8452
+ c0mputeKey: c0mputeKey(), openaiKey: process.env["OPENAI_API_KEY"], anthropicKey: process.env["ANTHROPIC_API_KEY"],
8444
8453
  openaiModel: process.env["NIXAMP_WRITER_OPENAI_MODEL"], claudeModel: process.env["NIXAMP_WRITER_CLAUDE_MODEL"],
8445
8454
  }) } : {}),
8446
8455
  ...(events && follows ? { onEventUpdated: (updated, previous) => {
@@ -552,6 +552,30 @@ async function labelCommand(argv, call, show) {
552
552
  return `Label: ${String(settings["holder"])}, ${String(settings["songsPerAlbum"])} songs of ${String(settings["songSeconds"])} s an album, every ${String(settings["everyDays"])} days, at most ${Number(settings["monthlySeconds"]) === 0 ? "unlimited" : `${String(settings["monthlySeconds"])} s`} a month${settings["enabled"] ? "" : " (off)"}.`;
553
553
  });
554
554
  }
555
+ case "adopt": {
556
+ // Albums already under the label's folder (copied from another server) back on the label.
557
+ // --keys FILE: {"artists": {"<name>": "<old key>"}, "albums": {"<artist key>/<album title>": "<old key>"}}.
558
+ const file = flag(argv, "--keys");
559
+ let keys = {};
560
+ if (file) {
561
+ try {
562
+ keys = JSON.parse((await import("node:fs")).readFileSync(file, "utf8"));
563
+ }
564
+ catch (error) {
565
+ console.error(`nixamp station label adopt: could not read ${file}: ${error.message}`);
566
+ return 2;
567
+ }
568
+ }
569
+ const body = { keys: keys["artists"] ?? {}, albums: keys["albums"] ?? {}, ...(has(argv, "--off") ? { enabled: false } : {}) };
570
+ return show(await call("POST", "/api/v1/stations/label/adopt", body), (answer) => {
571
+ const done = answer["adopted"];
572
+ const skipped = done["skipped"] ?? [];
573
+ return [
574
+ `Adopted ${String(done["artists"])} artists, ${String(done["albums"])} albums, ${String(done["songs"])} songs and ${String(done["videos"])} filmed videos; ${String(done["oldKeys"])} artists kept their old keys.`,
575
+ ...skipped.map((one) => ` skipped ${one}`),
576
+ ].join("\n");
577
+ });
578
+ }
555
579
  case "run":
556
580
  return show(await call("POST", "/api/v1/stations/label/run", {}), (body) => body["waiting"] ? `Nothing to record now: ${String(body["waiting"])}.` : "Recording whatever is due.");
557
581
  case "resume":
@@ -2,6 +2,7 @@ export declare const STATION_TOOLS: ({
2
2
  name: string;
3
3
  description: string;
4
4
  inputSchema: {
5
+ required?: undefined;
5
6
  type: string;
6
7
  properties: {
7
8
  server: {
@@ -72,7 +73,6 @@ export declare const STATION_TOOLS: ({
72
73
  id?: undefined;
73
74
  private?: undefined;
74
75
  };
75
- required?: undefined;
76
76
  };
77
77
  } | {
78
78
  name: string;
@@ -531,6 +531,7 @@ export declare const STATION_TOOLS: ({
531
531
  name: string;
532
532
  description: string;
533
533
  inputSchema: {
534
+ required?: undefined;
534
535
  type: string;
535
536
  properties: {
536
537
  playlist?: undefined;
@@ -615,12 +616,12 @@ export declare const STATION_TOOLS: ({
615
616
  id?: undefined;
616
617
  private?: undefined;
617
618
  };
618
- required?: undefined;
619
619
  };
620
620
  } | {
621
621
  name: string;
622
622
  description: string;
623
623
  inputSchema: {
624
+ required?: undefined;
624
625
  type: string;
625
626
  properties: {
626
627
  playlist?: undefined;
@@ -718,12 +719,12 @@ export declare const STATION_TOOLS: ({
718
719
  id?: undefined;
719
720
  private?: undefined;
720
721
  };
721
- required?: undefined;
722
722
  };
723
723
  } | {
724
724
  name: string;
725
725
  description: string;
726
726
  inputSchema: {
727
+ required?: undefined;
727
728
  type: string;
728
729
  properties: {
729
730
  playlist?: undefined;
@@ -862,12 +863,12 @@ export declare const STATION_TOOLS: ({
862
863
  id?: undefined;
863
864
  private?: undefined;
864
865
  };
865
- required?: undefined;
866
866
  };
867
867
  } | {
868
868
  name: string;
869
869
  description: string;
870
870
  inputSchema: {
871
+ required?: undefined;
871
872
  type: string;
872
873
  properties: {
873
874
  playlist?: undefined;
@@ -960,12 +961,12 @@ export declare const STATION_TOOLS: ({
960
961
  license?: undefined;
961
962
  private?: undefined;
962
963
  };
963
- required?: undefined;
964
964
  };
965
965
  } | {
966
966
  name: string;
967
967
  description: string;
968
968
  inputSchema: {
969
+ required?: undefined;
969
970
  type: string;
970
971
  properties: {
971
972
  playlist?: undefined;
@@ -1060,7 +1061,6 @@ export declare const STATION_TOOLS: ({
1060
1061
  };
1061
1062
  private?: undefined;
1062
1063
  };
1063
- required?: undefined;
1064
1064
  };
1065
1065
  } | {
1066
1066
  name: string;
@@ -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 [--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 [--reset-day 21] the day the monthly budget renews (the provider's billing day)\n [--veo|--no-veo] [--veo-shots 4] filmed Veo shots of the band in each album's single (nixamp.com caps the month)\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 | C0MPUTE_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 [--reset-day 21] the day the monthly budget renews (the provider's billing day)\n [--veo|--no-veo] [--veo-shots 4] filmed Veo shots of the band in each album's single (nixamp.com caps the month)\n nixamp station label adopt [--keys FILE] [--off] albums already in the label's folder (copied from another\n server) back on the label; FILE keeps old keys: {\"artists\": {NAME: KEY},\n \"albums\": {\"ARTISTKEY/TITLE\": KEY}}\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.";
@@ -32,7 +32,7 @@ export const USAGE = `nixamp station — make a collection into a station
32
32
  nixamp station ai remove KEY [--files]
33
33
  nixamp station ai budget SECONDS seconds of music a month across AI stations (0: no cap)
34
34
  nixamp station ai resume after the provider ran out of credit
35
- nixamp station ai key NAME ELEVENLABS_API_KEY | GEMINI_API_KEY | OPENAI_API_KEY | ANTHROPIC_API_KEY,
35
+ nixamp station ai key NAME ELEVENLABS_API_KEY | C0MPUTE_API_KEY | GEMINI_API_KEY | OPENAI_API_KEY | ANTHROPIC_API_KEY,
36
36
  NIXAMP_OPENAI_URL | NIXAMP_OPENAI_MODEL (an OpenAI-compatible stand-in),
37
37
  NIXAMP_MUSIC_MODEL (music_v1|music_v2|music_v2_5), from stdin
38
38
 
@@ -51,6 +51,9 @@ export const USAGE = `nixamp station — make a collection into a station
51
51
  [--videos|--no-videos] a music video per song and a TV station of them
52
52
  [--reset-day 21] the day the monthly budget renews (the provider's billing day)
53
53
  [--veo|--no-veo] [--veo-shots 4] filmed Veo shots of the band in each album's single (nixamp.com caps the month)
54
+ nixamp station label adopt [--keys FILE] [--off] albums already in the label's folder (copied from another
55
+ server) back on the label; FILE keeps old keys: {"artists": {NAME: KEY},
56
+ "albums": {"ARTISTKEY/TITLE": KEY}}
54
57
  nixamp station label run record whatever is due now
55
58
  nixamp station label resume after the provider ran out of credit
56
59
 
@@ -0,0 +1,2 @@
1
+ /** Add the shared footer to Nixamp pages while avoiding a second copy in its app shell. */
2
+ export declare function withUniversalFooter(page: string): string;
@@ -0,0 +1,39 @@
1
+ import { footerHtml, footerHtmlSync } from "@profullstack/footer";
2
+ const OPTIONS = {
3
+ site: "https://nixamp.com/",
4
+ since: 2026,
5
+ tagline: "MIT License",
6
+ links: [
7
+ { label: "Code", href: "https://github.com/profullstack/nixamp" },
8
+ { label: "r/nixamp", href: "https://www.reddit.com/r/nixamp" },
9
+ ],
10
+ builtIndieHref: "https://built.indie.pit.world/for?site=https://nixamp.com/",
11
+ pwamartHref: "https://pwamart.com/for?site=https://nixamp.com/",
12
+ };
13
+ const REFRESH_MS = 15 * 60 * 1000;
14
+ let html = footerHtmlSync(OPTIONS);
15
+ let fetchedAt = 0;
16
+ let inflight = null;
17
+ function refresh() {
18
+ inflight ??= footerHtml(OPTIONS)
19
+ .then((value) => {
20
+ html = value;
21
+ fetchedAt = Date.now();
22
+ })
23
+ .catch(() => {
24
+ fetchedAt = Date.now() - REFRESH_MS + 60_000;
25
+ })
26
+ .finally(() => {
27
+ inflight = null;
28
+ });
29
+ return inflight;
30
+ }
31
+ /** Add the shared footer to Nixamp pages while avoiding a second copy in its app shell. */
32
+ export function withUniversalFooter(page) {
33
+ if (Date.now() - fetchedAt > REFRESH_MS)
34
+ void refresh();
35
+ if (page.includes("data-pfs-footer="))
36
+ return page;
37
+ const at = page.lastIndexOf("</body>");
38
+ return at < 0 ? page : page.slice(0, at) + html + page.slice(at);
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nixamp",
3
- "version": "0.78.5",
3
+ "version": "0.80.1",
4
4
  "description": "It really whips the terminal's ass. A Winamp-shaped audio player for your terminal.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,15 +45,17 @@
45
45
  "pack:cli": "bun scripts/pack-cli.ts"
46
46
  },
47
47
  "dependencies": {
48
+ "@profullstack/ai-providers": "0.1.2",
48
49
  "@profullstack/auth-system": "^0.8.3",
50
+ "@profullstack/footer": "latest",
49
51
  "@profullstack/group-call": "^0.1.0",
50
52
  "@profullstack/hqtui": "^0.11.1",
51
53
  "@profullstack/live-translate": "^0.3.0",
52
54
  "@profullstack/notifications": "^0.1.4",
53
- "@profullstack/profile": "0.1.6",
55
+ "@profullstack/profile": "0.1.8",
54
56
  "@profullstack/synconfig": "^0.1.3",
55
- "@profullstack/throttle": "^0.4.0",
56
- "@profullstack/x402-gateway": "^0.15.2",
57
+ "@profullstack/throttle": "^0.4.1",
58
+ "@profullstack/x402-gateway": "^0.16.1",
57
59
  "acme-client": "5.4.0",
58
60
  "pg": "^8.23.1"
59
61
  },
@@ -86,6 +88,6 @@
86
88
  "overrides": {
87
89
  "https-proxy-agent": "7.0.6",
88
90
  "tslib": "2.8.1",
89
- "@profullstack/x402-gateway": "^0.15.2"
91
+ "@profullstack/x402-gateway": "^0.16.1"
90
92
  }
91
93
  }
package/src/ai-keys.ts CHANGED
@@ -11,7 +11,7 @@ import { dirname, join } from "node:path";
11
11
  import { stateDir } from "./daemon.ts";
12
12
 
13
13
  export const AI_KEY_NAMES = [
14
- "ELEVENLABS_API_KEY", "GEMINI_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY",
14
+ "ELEVENLABS_API_KEY", "C0MPUTE_API_KEY", "GEMINI_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY",
15
15
  // Any OpenAI-compatible endpoint in OpenAI's place (Cloudflare Workers AI, Ollama, ...) and its model.
16
16
  "NIXAMP_OPENAI_URL", "NIXAMP_OPENAI_MODEL",
17
17
  // The label's songwriter on that endpoint (album plans, lyrics, new artists), e.g. @cf/openai/gpt-oss-120b; NIXAMP_OPENAI_MODEL when unset.