fluncle 0.183.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.
- package/bin/fluncle.mjs +305 -59
- 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.
|
|
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
|
-
|
|
1073
|
-
artistsGetCommand: () => artistsGetCommand
|
|
1137
|
+
artistsCommand: () => artistsCommand
|
|
1074
1138
|
});
|
|
1075
|
-
async function
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
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 ??
|
|
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
|
|
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.
|
|
2445
|
+
currentVersion2 = "0.185.0".trim() ? "0.185.0".trim() : "0.1.0";
|
|
2264
2446
|
});
|
|
2265
2447
|
|
|
2266
2448
|
// ../../packages/registry/src/index.ts
|
|
@@ -2421,6 +2603,7 @@ var init_src = __esm(() => {
|
|
|
2421
2603
|
{
|
|
2422
2604
|
discoveryUrl: `${SITE}/llms.txt`,
|
|
2423
2605
|
exposedContent: [
|
|
2606
|
+
"/artists — one A–Z index of every drum & bass artist Fluncle holds",
|
|
2424
2607
|
"/artist/:slug — one artist's page: its findings, its identity links, and the rest of its catalogue"
|
|
2425
2608
|
],
|
|
2426
2609
|
kind: "web_route",
|
|
@@ -2433,12 +2616,12 @@ var init_src = __esm(() => {
|
|
|
2433
2616
|
{
|
|
2434
2617
|
discoveryUrl: `${SITE}/llms.txt`,
|
|
2435
2618
|
exposedContent: [
|
|
2436
|
-
"/labels — every record label Fluncle
|
|
2619
|
+
"/labels — one A–Z index of every drum & bass record label Fluncle holds",
|
|
2437
2620
|
"/label/:slug — one label: its findings, the artists on it, and the rest of its catalogue"
|
|
2438
2621
|
],
|
|
2439
2622
|
kind: "web_route",
|
|
2440
2623
|
name: "web.labels",
|
|
2441
|
-
operatorNotes: "The label half of the graph (log ↔ artist ↔ label ↔ album). The INDEX is probeable (always 200); the slug page is not, so the probe targets /labels. A label below the renderable-track floor renders noindex + stays out of the sitemap (the ARTIST_INDEX_MIN_FINDINGS precedent). The page is BLIND to the label's crawl seed_state — that is crawl scope, never storage (docs/label-entity.md); the operator's ruling station is /admin/labels.",
|
|
2624
|
+
operatorNotes: "The label half of the graph (log ↔ artist ↔ label ↔ album). The INDEX is probeable (always 200); the slug page is not, so the probe targets /labels. /labels is ONE unified A–Z index of every label Fluncle holds (certified lit, catalogue unlit), paged behind ?page=N. A label below the renderable-track floor renders noindex + stays out of the sitemap (the ARTIST_INDEX_MIN_FINDINGS precedent). The page is BLIND to the label's crawl seed_state — that is crawl scope, never storage (docs/label-entity.md); the operator's ruling station is /admin/labels.",
|
|
2442
2625
|
probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
|
|
2443
2626
|
route: "/labels",
|
|
2444
2627
|
url: `${SITE}/labels`,
|
|
@@ -2447,12 +2630,12 @@ var init_src = __esm(() => {
|
|
|
2447
2630
|
{
|
|
2448
2631
|
discoveryUrl: `${SITE}/llms.txt`,
|
|
2449
2632
|
exposedContent: [
|
|
2450
|
-
"/albums — every
|
|
2633
|
+
"/albums — one alphabetical index of every drum & bass album Fluncle holds",
|
|
2451
2634
|
"/album/:slug — one record: its findings, its artists, its label, and the rest of its tracklist"
|
|
2452
2635
|
],
|
|
2453
2636
|
kind: "web_route",
|
|
2454
2637
|
name: "web.albums",
|
|
2455
|
-
operatorNotes: "The album half of the graph, and the node that closes it: the album page carries the album → label edge (a link, plus `albumRelease.recordLabel` in its MusicAlbum JSON-LD). The INDEX is probeable (always 200); the slug page is not.
|
|
2638
|
+
operatorNotes: "The album half of the graph, and the node that closes it: the album page carries the album → label edge (a link, plus `albumRelease.recordLabel` in its MusicAlbum JSON-LD). The INDEX is probeable (always 200); the slug page is not. /albums is ONE unified alphabetical index of every record Fluncle holds (certified lit, catalogue unlit), paged behind ?page=N; a crawl-minted findings-free album is PUBLIC on its content — a row renders its page, a below-floor page renders noindex + stays out of the sitemap, like a discovered label (docs/album-entity.md).",
|
|
2456
2639
|
probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
|
|
2457
2640
|
route: "/albums",
|
|
2458
2641
|
url: `${SITE}/albums`,
|
|
@@ -2632,8 +2815,22 @@ var init_src = __esm(() => {
|
|
|
2632
2815
|
apiFormat: "application/json",
|
|
2633
2816
|
discoveryUrl: `${SITE}/api/v1/openapi.json`,
|
|
2634
2817
|
exposedContent: [
|
|
2635
|
-
"every
|
|
2636
|
-
"/api/v1/
|
|
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"
|
|
2637
2834
|
],
|
|
2638
2835
|
kind: "api",
|
|
2639
2836
|
name: "api.artists",
|
|
@@ -2642,6 +2839,20 @@ var init_src = __esm(() => {
|
|
|
2642
2839
|
url: `${SITE}/api/v1/artists`,
|
|
2643
2840
|
weights: { web: "secondary" }
|
|
2644
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
|
+
},
|
|
2645
2856
|
{
|
|
2646
2857
|
apiFormat: "application/json",
|
|
2647
2858
|
discoveryUrl: `${SITE}/api/v1/openapi.json`,
|
|
@@ -2919,12 +3130,12 @@ var init_src = __esm(() => {
|
|
|
2919
3130
|
{
|
|
2920
3131
|
apiFormat: "application/json+oembed",
|
|
2921
3132
|
exposedContent: [
|
|
2922
|
-
"the oEmbed 1.0 provider — a pasted /log, /
|
|
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/…)",
|
|
2923
3134
|
"html iframes the self-contained /embed/<logId> card; thumbnail is the finding's OG image"
|
|
2924
3135
|
],
|
|
2925
3136
|
kind: "discovery",
|
|
2926
3137
|
name: "discovery.oembed",
|
|
2927
|
-
operatorNotes: 'GET /oembed?url=<a fluncle.com /log|/mixtapes|/artist URL>&format=json.
|
|
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.',
|
|
2928
3139
|
route: "/oembed",
|
|
2929
3140
|
url: `${SITE}/oembed`,
|
|
2930
3141
|
weights: { web: "tertiary" }
|
|
@@ -2993,11 +3204,32 @@ var init_src = __esm(() => {
|
|
|
2993
3204
|
},
|
|
2994
3205
|
{
|
|
2995
3206
|
command: "fluncle artists",
|
|
2996
|
-
exposedContent: ["every artist
|
|
3207
|
+
exposedContent: ["every artist Fluncle holds, A to Z (bare `slug` looks one up)"],
|
|
2997
3208
|
kind: "cli",
|
|
2998
3209
|
name: "cli.artists",
|
|
2999
3210
|
weights: { cli: "secondary" }
|
|
3000
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
|
+
},
|
|
3001
3233
|
{
|
|
3002
3234
|
command: "fluncle open",
|
|
3003
3235
|
exposedContent: ["pick a track, open it in Spotify"],
|
|
@@ -6780,7 +7012,7 @@ __export(exports_format, {
|
|
|
6780
7012
|
artistTitle: () => artistTitle2
|
|
6781
7013
|
});
|
|
6782
7014
|
function coordinate3(track) {
|
|
6783
|
-
return track.logId ??
|
|
7015
|
+
return track.logId ?? COORD_FALLBACK4;
|
|
6784
7016
|
}
|
|
6785
7017
|
function artistTitle2(track) {
|
|
6786
7018
|
return `${track.artists.join(", ")} — ${track.title}`;
|
|
@@ -6811,7 +7043,7 @@ function vehicleRows(rows) {
|
|
|
6811
7043
|
return Math.max(width, coordinate3(row).length);
|
|
6812
7044
|
}, 0);
|
|
6813
7045
|
return rows.map((row) => {
|
|
6814
|
-
return `${coordinate3(row).padEnd(coordWidth)} ${foundDate(row.addedAt)} ${row.vehicle ??
|
|
7046
|
+
return `${coordinate3(row).padEnd(coordWidth)} ${foundDate(row.addedAt)} ${row.vehicle ?? COORD_FALLBACK4} · ${row.register ?? COORD_FALLBACK4}`;
|
|
6815
7047
|
});
|
|
6816
7048
|
}
|
|
6817
7049
|
function trackDetailLines(track) {
|
|
@@ -6828,7 +7060,7 @@ function trackDetailLines(track) {
|
|
|
6828
7060
|
}
|
|
6829
7061
|
return lines;
|
|
6830
7062
|
}
|
|
6831
|
-
var
|
|
7063
|
+
var COORD_FALLBACK4 = "—";
|
|
6832
7064
|
var init_format2 = __esm(() => {
|
|
6833
7065
|
init_util();
|
|
6834
7066
|
});
|
|
@@ -9025,9 +9257,25 @@ function addListenCommands(program2) {
|
|
|
9025
9257
|
view: parseFreshView(options.view)
|
|
9026
9258
|
});
|
|
9027
9259
|
});
|
|
9028
|
-
program2.command("
|
|
9029
|
-
const {
|
|
9030
|
-
await
|
|
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
|
+
});
|
|
9031
9279
|
});
|
|
9032
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) => {
|
|
9033
9281
|
const { galaxiesListCommand: galaxiesListCommand2, galaxyGetCommand: galaxyGetCommand2 } = await Promise.resolve().then(() => (init_galaxies(), exports_galaxies));
|
|
@@ -11613,33 +11861,6 @@ async function runRecent(options, recentCommand3) {
|
|
|
11613
11861
|
console.log(trackRows2(tracks).join(`
|
|
11614
11862
|
`));
|
|
11615
11863
|
}
|
|
11616
|
-
async function runArtists(slug, options, commands) {
|
|
11617
|
-
if (slug) {
|
|
11618
|
-
const artist = await commands.artistsGetCommand(slug);
|
|
11619
|
-
if (options.json) {
|
|
11620
|
-
printJson({ artist, ok: true });
|
|
11621
|
-
return;
|
|
11622
|
-
}
|
|
11623
|
-
console.log(`${artist.name} (${artist.slug})`);
|
|
11624
|
-
console.log(`Findings: ${artist.findingCount}`);
|
|
11625
|
-
if (artist.spotifyUrl) {
|
|
11626
|
-
console.log(`Spotify: ${artist.spotifyUrl}`);
|
|
11627
|
-
}
|
|
11628
|
-
return;
|
|
11629
|
-
}
|
|
11630
|
-
const artists = await commands.artistsListCommand();
|
|
11631
|
-
if (options.json) {
|
|
11632
|
-
printJson({ artists, ok: true });
|
|
11633
|
-
return;
|
|
11634
|
-
}
|
|
11635
|
-
if (artists.length === 0) {
|
|
11636
|
-
console.log("No artists in the archive yet.");
|
|
11637
|
-
return;
|
|
11638
|
-
}
|
|
11639
|
-
for (const a of artists) {
|
|
11640
|
-
console.log(`${a.name.padEnd(40)} ${String(a.findingCount).padStart(3)} finding${a.findingCount === 1 ? "" : "s"}`);
|
|
11641
|
-
}
|
|
11642
|
-
}
|
|
11643
11864
|
async function runGalaxies(slug, options, commands) {
|
|
11644
11865
|
if (slug) {
|
|
11645
11866
|
const { findings, galaxy } = await commands.galaxyGetCommand(slug);
|
|
@@ -11715,7 +11936,7 @@ async function runCatalogueList(options, catalogueListCommand2) {
|
|
|
11715
11936
|
const dupWhy = dup ? `already in the archive \u2014 ${dup.logId ? `${dup.logId} ` : ""}${dup.artists.join(", ")} \u2014 ${dup.title}` : null;
|
|
11716
11937
|
if (options.lens === "capture") {
|
|
11717
11938
|
const reason = track.captureReason;
|
|
11718
|
-
const why2 = dupWhy ?? (reason?.kind === "artist" ? `${reason.name} is already in the archive` : reason?.kind === "label" ? `${reason.name} already carries a finding` : reason?.kind === "seed-label" ? `${reason.name} is a label the crawler digs from` : reason?.kind === "skipped-label" ? `${reason.name} is not your lane \u2014 ranked last, kept anyway` : "nothing ties it to the archive yet");
|
|
11939
|
+
const why2 = dupWhy ?? (reason?.kind === "artist" ? `${reason.name} is already in the archive` : reason?.kind === "label" ? `${reason.name} already carries a finding` : reason?.kind === "seed-label" ? `${reason.name} is a label the crawler digs from` : reason?.kind === "skipped-label" ? `${reason.name} is not your lane \u2014 ranked last, kept anyway` : reason?.kind === "unauthorized" ? "no artist here has earned the spend yet \u2014 held back, kept anyway" : "nothing ties it to the archive yet");
|
|
11719
11940
|
console.log(`${String(track.capturePriority ?? 0)} ${identity.padEnd(52)} ${why2}`);
|
|
11720
11941
|
continue;
|
|
11721
11942
|
}
|
|
@@ -11859,6 +12080,23 @@ function parseListLimit(value) {
|
|
|
11859
12080
|
}
|
|
11860
12081
|
return limit;
|
|
11861
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
|
+
}
|
|
11862
12100
|
function parseFreshView(value) {
|
|
11863
12101
|
const view = value ?? "all";
|
|
11864
12102
|
if (view !== "all" && view !== "tracks" && view !== "albums") {
|
|
@@ -12424,6 +12662,7 @@ var stringOptions = new Set([
|
|
|
12424
12662
|
"--model",
|
|
12425
12663
|
"--note",
|
|
12426
12664
|
"--order",
|
|
12665
|
+
"--page",
|
|
12427
12666
|
"--parent-id",
|
|
12428
12667
|
"--plate",
|
|
12429
12668
|
"--plate-background",
|
|
@@ -12471,6 +12710,13 @@ Listen:
|
|
|
12471
12710
|
fluncle random [--json] The archive throws one back
|
|
12472
12711
|
fluncle subscribe [email] Fresh bangers, every Friday
|
|
12473
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
|
+
|
|
12474
12720
|
Share:
|
|
12475
12721
|
fluncle submit [search-or-spotify-url] Send a track for review
|
|
12476
12722
|
|
package/package.json
CHANGED