fluncle 0.184.0 → 0.185.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/fluncle.mjs +299 -54
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -561,7 +561,7 @@ function parseVersion(version) {
561
561
  var currentVersion;
562
562
  var init_version = __esm(() => {
563
563
  init_output();
564
- currentVersion = "0.184.0".trim() ? "0.184.0".trim() : "0.1.0";
564
+ currentVersion = "0.185.0".trim() ? "0.185.0".trim() : "0.1.0";
565
565
  });
566
566
 
567
567
  // src/update-notifier.ts
@@ -1066,22 +1066,207 @@ var init_fresh = __esm(() => {
1066
1066
  init_output();
1067
1067
  });
1068
1068
 
1069
+ // src/commands/entity-browse.ts
1070
+ function entityRow(item) {
1071
+ const count = `${item.trackCount} track${item.trackCount === 1 ? "" : "s"}`;
1072
+ return `${item.name.padEnd(NAME_WIDTH)} ${count}`;
1073
+ }
1074
+ function printEntityIndex(items, page, noun, command) {
1075
+ if (items.length === 0) {
1076
+ if (page.total === 0) {
1077
+ console.log(`No ${noun.plural} in the archive yet.`);
1078
+ return;
1079
+ }
1080
+ console.log(`Nothing on page ${page.page}. The archive holds ${page.total} ${noun.plural} across ${page.pageCount} pages.`);
1081
+ return;
1082
+ }
1083
+ for (const item of items) {
1084
+ console.log(entityRow(item));
1085
+ }
1086
+ if (page.pageCount > 1) {
1087
+ const more = page.page < page.pageCount ? ` More with: fluncle ${command} --page ${page.page + 1}` : "";
1088
+ console.log("");
1089
+ console.log(`Page ${page.page} of ${page.pageCount}, ${page.total} ${noun.plural}.${more}`);
1090
+ }
1091
+ }
1092
+ function entityDetailLines(name, slug, trackCount, findingCount) {
1093
+ return [`${name} (${slug})`, `Tracks: ${trackCount}`, `Findings: ${findingCount}`];
1094
+ }
1095
+ var NAME_WIDTH = 40;
1096
+
1097
+ // src/commands/albums.ts
1098
+ var exports_albums = {};
1099
+ __export(exports_albums, {
1100
+ albumsCommand: () => albumsCommand
1101
+ });
1102
+ async function albumsCommand({
1103
+ json,
1104
+ page,
1105
+ slug
1106
+ }) {
1107
+ if (slug) {
1108
+ const response2 = await publicApiGet(`/api/v1/albums/${encodeURIComponent(slug)}`);
1109
+ if (json) {
1110
+ printJson2(response2);
1111
+ return;
1112
+ }
1113
+ const { album } = response2;
1114
+ const lines = entityDetailLines(album.name, album.slug, album.trackCount, album.findingCount);
1115
+ if (album.releaseDate) {
1116
+ lines.push(`Released: ${album.releaseDate.slice(0, 10)}`);
1117
+ }
1118
+ console.log(lines.join(`
1119
+ `));
1120
+ return;
1121
+ }
1122
+ const response = await publicApiGet(`/api/v1/albums?page=${page}`);
1123
+ if (json) {
1124
+ printJson2(response);
1125
+ return;
1126
+ }
1127
+ printEntityIndex(response.albums, response, { plural: "albums", singular: "album" }, "albums");
1128
+ }
1129
+ var init_albums = __esm(() => {
1130
+ init_api();
1131
+ init_output();
1132
+ });
1133
+
1069
1134
  // src/commands/artists.ts
1070
1135
  var exports_artists = {};
1071
1136
  __export(exports_artists, {
1072
- artistsListCommand: () => artistsListCommand,
1073
- artistsGetCommand: () => artistsGetCommand
1137
+ artistsCommand: () => artistsCommand
1074
1138
  });
1075
- async function artistsListCommand() {
1076
- const response = await publicApiGet("/api/v1/artists");
1077
- return response.artists;
1078
- }
1079
- async function artistsGetCommand(slug) {
1080
- const response = await publicApiGet(`/api/v1/artists/${encodeURIComponent(slug)}`);
1081
- return response.artist;
1139
+ async function artistsCommand({
1140
+ json,
1141
+ page,
1142
+ slug
1143
+ }) {
1144
+ if (slug) {
1145
+ const response2 = await publicApiGet(`/api/v1/artists/${encodeURIComponent(slug)}`);
1146
+ if (json) {
1147
+ printJson2(response2);
1148
+ return;
1149
+ }
1150
+ const { artist } = response2;
1151
+ const lines = entityDetailLines(artist.name, artist.slug, artist.trackCount, artist.findingCount);
1152
+ if (artist.spotifyUrl) {
1153
+ lines.push(`Spotify: ${artist.spotifyUrl}`);
1154
+ }
1155
+ console.log(lines.join(`
1156
+ `));
1157
+ return;
1158
+ }
1159
+ const response = await publicApiGet(`/api/v1/artists?page=${page}`);
1160
+ if (json) {
1161
+ printJson2(response);
1162
+ return;
1163
+ }
1164
+ printEntityIndex(response.artists, response, { plural: "artists", singular: "artist" }, "artists");
1082
1165
  }
1083
1166
  var init_artists = __esm(() => {
1084
1167
  init_api();
1168
+ init_output();
1169
+ });
1170
+
1171
+ // src/commands/labels.ts
1172
+ var exports_labels = {};
1173
+ __export(exports_labels, {
1174
+ labelsCommand: () => labelsCommand
1175
+ });
1176
+ async function labelsCommand({
1177
+ json,
1178
+ page,
1179
+ slug
1180
+ }) {
1181
+ if (slug) {
1182
+ const response2 = await publicApiGet(`/api/v1/labels/${encodeURIComponent(slug)}`);
1183
+ if (json) {
1184
+ printJson2(response2);
1185
+ return;
1186
+ }
1187
+ const { label } = response2;
1188
+ const lines = entityDetailLines(label.name, label.slug, label.trackCount, label.findingCount);
1189
+ if (label.foundedLocation) {
1190
+ lines.push(`Based: ${label.foundedLocation}`);
1191
+ }
1192
+ if (label.parentLabel) {
1193
+ lines.push(`Imprint of: ${label.parentLabel.name}`);
1194
+ }
1195
+ console.log(lines.join(`
1196
+ `));
1197
+ return;
1198
+ }
1199
+ const response = await publicApiGet(`/api/v1/labels?page=${page}`);
1200
+ if (json) {
1201
+ printJson2(response);
1202
+ return;
1203
+ }
1204
+ printEntityIndex(response.labels, response, { plural: "labels", singular: "label" }, "labels");
1205
+ }
1206
+ var init_labels = __esm(() => {
1207
+ init_api();
1208
+ init_output();
1209
+ });
1210
+
1211
+ // src/links.ts
1212
+ var spotifyPlaylistUrl2 = "https://open.spotify.com/playlist/1m5LADqpLjiBERdtqrIiL0", telegramUrl2 = "https://t.me/fluncle", webBaseUrl = "https://www.fluncle.com";
1213
+
1214
+ // src/commands/search.ts
1215
+ var exports_search = {};
1216
+ __export(exports_search, {
1217
+ searchCommand: () => searchCommand
1218
+ });
1219
+ function entityLine(entity) {
1220
+ const kind = `${entity.kind[0]?.toUpperCase() ?? ""}${entity.kind.slice(1)}`;
1221
+ return `${kind} ${entity.name} ${webBaseUrl}/${entity.kind}/${entity.slug}`;
1222
+ }
1223
+ function trackTable(hits) {
1224
+ const coordWidth = hits.reduce((width, hit) => {
1225
+ return Math.max(width, (hit.logId ?? COORD_FALLBACK2).length);
1226
+ }, 0);
1227
+ return hits.map((hit) => {
1228
+ const coordinate2 = (hit.logId ?? COORD_FALLBACK2).padEnd(coordWidth);
1229
+ return `${coordinate2} ${hit.artists.join(", ")} — ${hit.title}`;
1230
+ }).join(`
1231
+ `);
1232
+ }
1233
+ async function searchCommand({
1234
+ json,
1235
+ limit,
1236
+ query
1237
+ }) {
1238
+ const params = new URLSearchParams({ q: query });
1239
+ if (limit !== undefined) {
1240
+ params.set("limit", String(limit));
1241
+ }
1242
+ const response = await publicApiGet(`/api/v1/search/archive?${params.toString()}`);
1243
+ if (json) {
1244
+ printJson2(response);
1245
+ return;
1246
+ }
1247
+ const blocks = [];
1248
+ if (response.redirect) {
1249
+ blocks.push(`Jump to ${webBaseUrl}${response.redirect}`);
1250
+ }
1251
+ if (response.entities.length > 0) {
1252
+ blocks.push(response.entities.map(entityLine).join(`
1253
+ `));
1254
+ }
1255
+ if (response.results.length > 0) {
1256
+ blocks.push(trackTable(response.results));
1257
+ }
1258
+ if (blocks.length === 0) {
1259
+ console.log(`Nothing in the archive matches "${query}".`);
1260
+ return;
1261
+ }
1262
+ console.log(blocks.join(`
1263
+
1264
+ `));
1265
+ }
1266
+ var COORD_FALLBACK2 = "—";
1267
+ var init_search = __esm(() => {
1268
+ init_api();
1269
+ init_output();
1085
1270
  });
1086
1271
 
1087
1272
  // src/commands/galaxies.ts
@@ -1603,12 +1788,12 @@ var init_mixtapes = __esm(() => {
1603
1788
 
1604
1789
  // src/format.ts
1605
1790
  function coordinate2(track) {
1606
- return track.logId ?? COORD_FALLBACK2;
1791
+ return track.logId ?? COORD_FALLBACK3;
1607
1792
  }
1608
1793
  function artistTitle(track) {
1609
1794
  return `${track.artists.join(", ")} — ${track.title}`;
1610
1795
  }
1611
- var COORD_FALLBACK2 = "—";
1796
+ var COORD_FALLBACK3 = "—";
1612
1797
  var init_format = __esm(() => {
1613
1798
  init_util();
1614
1799
  });
@@ -1715,9 +1900,6 @@ var init_interactive = __esm(() => {
1715
1900
  init_output();
1716
1901
  });
1717
1902
 
1718
- // src/links.ts
1719
- var spotifyPlaylistUrl2 = "https://open.spotify.com/playlist/1m5LADqpLjiBERdtqrIiL0", telegramUrl2 = "https://t.me/fluncle";
1720
-
1721
1903
  // src/open-external.ts
1722
1904
  async function openExternal(target) {
1723
1905
  const command = platformOpenCommand();
@@ -2260,7 +2442,7 @@ function parseVersion2(version) {
2260
2442
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2261
2443
  var init_version2 = __esm(() => {
2262
2444
  init_output();
2263
- currentVersion2 = "0.184.0".trim() ? "0.184.0".trim() : "0.1.0";
2445
+ currentVersion2 = "0.185.0".trim() ? "0.185.0".trim() : "0.1.0";
2264
2446
  });
2265
2447
 
2266
2448
  // ../../packages/registry/src/index.ts
@@ -2633,8 +2815,22 @@ var init_src = __esm(() => {
2633
2815
  apiFormat: "application/json",
2634
2816
  discoveryUrl: `${SITE}/api/v1/openapi.json`,
2635
2817
  exposedContent: [
2636
- "every artist with at least one published finding, finding-count ordered, as JSON",
2637
- "/api/v1/artists/{slug} — one artist's identity and finding count, as JSON"
2818
+ "every album in the archive, alphabetical, paginated, as JSON",
2819
+ "/api/v1/albums/{slug} — one album's identity, cover, and counts, as JSON"
2820
+ ],
2821
+ kind: "api",
2822
+ name: "api.albums",
2823
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2824
+ route: "/api/v1/albums",
2825
+ url: `${SITE}/api/v1/albums`,
2826
+ weights: { web: "secondary" }
2827
+ },
2828
+ {
2829
+ apiFormat: "application/json",
2830
+ discoveryUrl: `${SITE}/api/v1/openapi.json`,
2831
+ exposedContent: [
2832
+ "every artist in the archive, alphabetical, paginated, as JSON",
2833
+ "/api/v1/artists/{slug} — one artist's identity, finding count, and track count, as JSON"
2638
2834
  ],
2639
2835
  kind: "api",
2640
2836
  name: "api.artists",
@@ -2643,6 +2839,20 @@ var init_src = __esm(() => {
2643
2839
  url: `${SITE}/api/v1/artists`,
2644
2840
  weights: { web: "secondary" }
2645
2841
  },
2842
+ {
2843
+ apiFormat: "application/json",
2844
+ discoveryUrl: `${SITE}/api/v1/openapi.json`,
2845
+ exposedContent: [
2846
+ "every label in the archive, alphabetical, paginated, as JSON",
2847
+ "/api/v1/labels/{slug} — one label's identity, lineage, and counts, as JSON"
2848
+ ],
2849
+ kind: "api",
2850
+ name: "api.labels",
2851
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2852
+ route: "/api/v1/labels",
2853
+ url: `${SITE}/api/v1/labels`,
2854
+ weights: { web: "secondary" }
2855
+ },
2646
2856
  {
2647
2857
  apiFormat: "application/json",
2648
2858
  discoveryUrl: `${SITE}/api/v1/openapi.json`,
@@ -2920,12 +3130,12 @@ var init_src = __esm(() => {
2920
3130
  {
2921
3131
  apiFormat: "application/json+oembed",
2922
3132
  exposedContent: [
2923
- "the oEmbed 1.0 provider — a pasted /log, /mixtapes, or /artist link unfurls as a rich finding card (Discord/Notion/WordPress/Ghost/…)",
3133
+ "the oEmbed 1.0 provider — a pasted /log link unfurls as a rich finding card, and a /artist, /label, /album, or /mixtapes link as a link card (Discord/Notion/WordPress/Ghost/…)",
2924
3134
  "html iframes the self-contained /embed/<logId> card; thumbnail is the finding's OG image"
2925
3135
  ],
2926
3136
  kind: "discovery",
2927
3137
  name: "discovery.oembed",
2928
- operatorNotes: 'GET /oembed?url=<a fluncle.com /log|/mixtapes|/artist URL>&format=json. Discovered via the `<link rel="alternate" type="application/json+oembed">` on the /log, mixtape, and /artist heads. XML → 501 (JSON only). No probeConfig — a bare GET without a valid `url` param is a 404, so there is no fixed URL to health-probe (like web.artist). The `rich` html frames /embed/<logId> (apps/web/src/routes/embed.$logId.ts), a standalone dark card served with a permissive `frame-ancestors *` CSP scoped to that route. Source: apps/web/src/routes/oembed.ts + src/lib/oembed.ts.',
3138
+ operatorNotes: 'GET /oembed?url=<a fluncle.com /log|/mixtapes|/artist|/label|/album URL>&format=json — the provider resolves each. The auto-discovery `<link rel="alternate" type="application/json+oembed">` rides the /log, mixtape, and /artist heads today; a consumer unfurls a /label or /album link by hitting the provider directly (those page heads can advertise it later). XML → 501 (JSON only). No probeConfig — a bare GET without a valid `url` param is a 404, so there is no fixed URL to health-probe (like web.artist). The `rich` html frames /embed/<logId> (apps/web/src/routes/embed.$logId.ts), a standalone dark card served with a permissive `frame-ancestors *` CSP scoped to that route. Source: apps/web/src/routes/oembed.ts + src/lib/oembed.ts.',
2929
3139
  route: "/oembed",
2930
3140
  url: `${SITE}/oembed`,
2931
3141
  weights: { web: "tertiary" }
@@ -2994,11 +3204,32 @@ var init_src = __esm(() => {
2994
3204
  },
2995
3205
  {
2996
3206
  command: "fluncle artists",
2997
- exposedContent: ["every artist with at least one published finding (bare `slug` looks one up)"],
3207
+ exposedContent: ["every artist Fluncle holds, A to Z (bare `slug` looks one up)"],
2998
3208
  kind: "cli",
2999
3209
  name: "cli.artists",
3000
3210
  weights: { cli: "secondary" }
3001
3211
  },
3212
+ {
3213
+ command: "fluncle albums",
3214
+ exposedContent: ["every album Fluncle holds, A to Z (bare `slug` looks one up)"],
3215
+ kind: "cli",
3216
+ name: "cli.albums",
3217
+ weights: { cli: "secondary" }
3218
+ },
3219
+ {
3220
+ command: "fluncle labels",
3221
+ exposedContent: ["every label Fluncle holds, A to Z (bare `slug` looks one up)"],
3222
+ kind: "cli",
3223
+ name: "cli.labels",
3224
+ weights: { cli: "secondary" }
3225
+ },
3226
+ {
3227
+ command: "fluncle search",
3228
+ exposedContent: ["search the archive by coordinate, track, artist, label, or album"],
3229
+ kind: "cli",
3230
+ name: "cli.search",
3231
+ weights: { cli: "secondary" }
3232
+ },
3002
3233
  {
3003
3234
  command: "fluncle open",
3004
3235
  exposedContent: ["pick a track, open it in Spotify"],
@@ -6781,7 +7012,7 @@ __export(exports_format, {
6781
7012
  artistTitle: () => artistTitle2
6782
7013
  });
6783
7014
  function coordinate3(track) {
6784
- return track.logId ?? COORD_FALLBACK3;
7015
+ return track.logId ?? COORD_FALLBACK4;
6785
7016
  }
6786
7017
  function artistTitle2(track) {
6787
7018
  return `${track.artists.join(", ")} — ${track.title}`;
@@ -6812,7 +7043,7 @@ function vehicleRows(rows) {
6812
7043
  return Math.max(width, coordinate3(row).length);
6813
7044
  }, 0);
6814
7045
  return rows.map((row) => {
6815
- return `${coordinate3(row).padEnd(coordWidth)} ${foundDate(row.addedAt)} ${row.vehicle ?? COORD_FALLBACK3} · ${row.register ?? COORD_FALLBACK3}`;
7046
+ return `${coordinate3(row).padEnd(coordWidth)} ${foundDate(row.addedAt)} ${row.vehicle ?? COORD_FALLBACK4} · ${row.register ?? COORD_FALLBACK4}`;
6816
7047
  });
6817
7048
  }
6818
7049
  function trackDetailLines(track) {
@@ -6829,7 +7060,7 @@ function trackDetailLines(track) {
6829
7060
  }
6830
7061
  return lines;
6831
7062
  }
6832
- var COORD_FALLBACK3 = "—";
7063
+ var COORD_FALLBACK4 = "—";
6833
7064
  var init_format2 = __esm(() => {
6834
7065
  init_util();
6835
7066
  });
@@ -9026,9 +9257,25 @@ function addListenCommands(program2) {
9026
9257
  view: parseFreshView(options.view)
9027
9258
  });
9028
9259
  });
9029
- program2.command("artists").description("Browse artists in Fluncle's archive").argument("[slug]", "Artist slug (omit for the full list)").option("--json", "Print JSON", false).action(async (slug, options) => {
9030
- const { artistsListCommand: artistsListCommand2, artistsGetCommand: artistsGetCommand2 } = await Promise.resolve().then(() => (init_artists(), exports_artists));
9031
- await runArtists(slug, options, { artistsGetCommand: artistsGetCommand2, artistsListCommand: artistsListCommand2 });
9260
+ program2.command("albums").description("Browse the albums in Fluncle's archive").argument("[slug]", "Album slug (omit for the A-to-Z list)").option("--page <n>", "Which page of the list (default 1)").option("--json", "Print JSON", false).action(async (slug, options) => {
9261
+ const { albumsCommand: albumsCommand2 } = await Promise.resolve().then(() => (init_albums(), exports_albums));
9262
+ await albumsCommand2({ json: options.json, page: parsePage(options.page), slug });
9263
+ });
9264
+ program2.command("artists").description("Browse the artists in Fluncle's archive").argument("[slug]", "Artist slug (omit for the A-to-Z list)").option("--page <n>", "Which page of the list (default 1)").option("--json", "Print JSON", false).action(async (slug, options) => {
9265
+ const { artistsCommand: artistsCommand2 } = await Promise.resolve().then(() => (init_artists(), exports_artists));
9266
+ await artistsCommand2({ json: options.json, page: parsePage(options.page), slug });
9267
+ });
9268
+ program2.command("labels").description("Browse the labels in Fluncle's archive").argument("[slug]", "Label slug (omit for the A-to-Z list)").option("--page <n>", "Which page of the list (default 1)").option("--json", "Print JSON", false).action(async (slug, options) => {
9269
+ const { labelsCommand: labelsCommand2 } = await Promise.resolve().then(() => (init_labels(), exports_labels));
9270
+ await labelsCommand2({ json: options.json, page: parsePage(options.page), slug });
9271
+ });
9272
+ program2.command("search").description("Search the archive for a track, artist, label, or album").argument("<query...>", "What to look for").option("--limit <n>", "Max results to return (1-50)").option("--json", "Print JSON", false).action(async (query, options) => {
9273
+ const { searchCommand: searchCommand2 } = await Promise.resolve().then(() => (init_search(), exports_search));
9274
+ await searchCommand2({
9275
+ json: options.json,
9276
+ limit: parseSearchLimit(options.limit),
9277
+ query: query.join(" ")
9278
+ });
9032
9279
  });
9033
9280
  program2.command("galaxies").description("Wander Fluncle's sonic galaxies").argument("[slug]", "Galaxy slug (omit for the full map)").option("--json", "Print JSON", false).action(async (slug, options) => {
9034
9281
  const { galaxiesListCommand: galaxiesListCommand2, galaxyGetCommand: galaxyGetCommand2 } = await Promise.resolve().then(() => (init_galaxies(), exports_galaxies));
@@ -11614,33 +11861,6 @@ async function runRecent(options, recentCommand3) {
11614
11861
  console.log(trackRows2(tracks).join(`
11615
11862
  `));
11616
11863
  }
11617
- async function runArtists(slug, options, commands) {
11618
- if (slug) {
11619
- const artist = await commands.artistsGetCommand(slug);
11620
- if (options.json) {
11621
- printJson({ artist, ok: true });
11622
- return;
11623
- }
11624
- console.log(`${artist.name} (${artist.slug})`);
11625
- console.log(`Findings: ${artist.findingCount}`);
11626
- if (artist.spotifyUrl) {
11627
- console.log(`Spotify: ${artist.spotifyUrl}`);
11628
- }
11629
- return;
11630
- }
11631
- const artists = await commands.artistsListCommand();
11632
- if (options.json) {
11633
- printJson({ artists, ok: true });
11634
- return;
11635
- }
11636
- if (artists.length === 0) {
11637
- console.log("No artists in the archive yet.");
11638
- return;
11639
- }
11640
- for (const a of artists) {
11641
- console.log(`${a.name.padEnd(40)} ${String(a.findingCount).padStart(3)} finding${a.findingCount === 1 ? "" : "s"}`);
11642
- }
11643
- }
11644
11864
  async function runGalaxies(slug, options, commands) {
11645
11865
  if (slug) {
11646
11866
  const { findings, galaxy } = await commands.galaxyGetCommand(slug);
@@ -11860,6 +12080,23 @@ function parseListLimit(value) {
11860
12080
  }
11861
12081
  return limit;
11862
12082
  }
12083
+ function parsePage(value) {
12084
+ const page = Number.parseInt(value ?? "1", 10);
12085
+ if (!Number.isInteger(page) || page < 1) {
12086
+ throw new Error("Page must be a positive integer");
12087
+ }
12088
+ return page;
12089
+ }
12090
+ function parseSearchLimit(value) {
12091
+ if (value === undefined) {
12092
+ return;
12093
+ }
12094
+ const limit = Number.parseInt(value, 10);
12095
+ if (!Number.isInteger(limit) || limit < 1 || limit > 50) {
12096
+ throw new Error("Limit must be an integer between 1 and 50");
12097
+ }
12098
+ return limit;
12099
+ }
11863
12100
  function parseFreshView(value) {
11864
12101
  const view = value ?? "all";
11865
12102
  if (view !== "all" && view !== "tracks" && view !== "albums") {
@@ -12425,6 +12662,7 @@ var stringOptions = new Set([
12425
12662
  "--model",
12426
12663
  "--note",
12427
12664
  "--order",
12665
+ "--page",
12428
12666
  "--parent-id",
12429
12667
  "--plate",
12430
12668
  "--plate-background",
@@ -12472,6 +12710,13 @@ Listen:
12472
12710
  fluncle random [--json] The archive throws one back
12473
12711
  fluncle subscribe [email] Fresh bangers, every Friday
12474
12712
 
12713
+ Browse:
12714
+ fluncle search <query> [--limit 20] [--json] Search the archive for a track, artist, label, or album
12715
+ fluncle artists [slug] [--page 1] [--json] Artists in the archive, A to Z
12716
+ fluncle albums [slug] [--page 1] [--json] Albums in the archive, A to Z
12717
+ fluncle labels [slug] [--page 1] [--json] Labels in the archive, A to Z
12718
+ fluncle fresh [--limit 10] [--view all] [--json] What just came out, newest release first
12719
+
12475
12720
  Share:
12476
12721
  fluncle submit [search-or-spotify-url] Send a track for review
12477
12722
 
package/package.json CHANGED
@@ -31,5 +31,5 @@
31
31
  "url": "git+https://github.com/mauricekleine/fluncle.git"
32
32
  },
33
33
  "type": "module",
34
- "version": "0.184.0"
34
+ "version": "0.185.0"
35
35
  }