fluncle 0.113.0 → 0.115.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 +179 -3
- package/package.json +1 -1
package/bin/fluncle.mjs
CHANGED
|
@@ -557,7 +557,7 @@ function parseVersion(version) {
|
|
|
557
557
|
var currentVersion;
|
|
558
558
|
var init_version = __esm(() => {
|
|
559
559
|
init_output();
|
|
560
|
-
currentVersion = "0.
|
|
560
|
+
currentVersion = "0.115.0".trim() ? "0.115.0".trim() : "0.1.0";
|
|
561
561
|
});
|
|
562
562
|
|
|
563
563
|
// src/update-notifier.ts
|
|
@@ -1042,6 +1042,48 @@ var init_artists = __esm(() => {
|
|
|
1042
1042
|
init_api();
|
|
1043
1043
|
});
|
|
1044
1044
|
|
|
1045
|
+
// src/commands/galaxies.ts
|
|
1046
|
+
var exports_galaxies = {};
|
|
1047
|
+
__export(exports_galaxies, {
|
|
1048
|
+
galaxyMapWriteCommand: () => galaxyMapWriteCommand,
|
|
1049
|
+
galaxyMapReadCommand: () => galaxyMapReadCommand,
|
|
1050
|
+
galaxyGetCommand: () => galaxyGetCommand,
|
|
1051
|
+
galaxyEmbeddingsCommand: () => galaxyEmbeddingsCommand,
|
|
1052
|
+
galaxiesListCommand: () => galaxiesListCommand
|
|
1053
|
+
});
|
|
1054
|
+
async function galaxiesListCommand() {
|
|
1055
|
+
const response = await publicApiGet("/api/v1/galaxies");
|
|
1056
|
+
return response.galaxies;
|
|
1057
|
+
}
|
|
1058
|
+
async function galaxyGetCommand(slug) {
|
|
1059
|
+
const response = await publicApiGet(`/api/v1/galaxies/${encodeURIComponent(slug)}`);
|
|
1060
|
+
return { findings: response.findings, galaxy: response.galaxy };
|
|
1061
|
+
}
|
|
1062
|
+
async function galaxyMapReadCommand() {
|
|
1063
|
+
const response = await adminApiGet("/api/admin/galaxies");
|
|
1064
|
+
return response.galaxies;
|
|
1065
|
+
}
|
|
1066
|
+
async function galaxyEmbeddingsCommand(options) {
|
|
1067
|
+
const params = new URLSearchParams;
|
|
1068
|
+
if (options.cursor) {
|
|
1069
|
+
params.set("cursor", options.cursor);
|
|
1070
|
+
}
|
|
1071
|
+
if (options.limit) {
|
|
1072
|
+
params.set("limit", options.limit);
|
|
1073
|
+
}
|
|
1074
|
+
const query = params.toString();
|
|
1075
|
+
return adminApiGet(`/api/admin/tracks/embeddings${query ? `?${query}` : ""}`);
|
|
1076
|
+
}
|
|
1077
|
+
async function galaxyMapWriteCommand(clusters) {
|
|
1078
|
+
const response = await adminApiPut("/api/admin/galaxies/map", {
|
|
1079
|
+
clusters
|
|
1080
|
+
});
|
|
1081
|
+
return response.galaxies;
|
|
1082
|
+
}
|
|
1083
|
+
var init_galaxies = __esm(() => {
|
|
1084
|
+
init_api();
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1045
1087
|
// src/commands/mixtape-api.ts
|
|
1046
1088
|
async function mixtapeListCommand() {
|
|
1047
1089
|
const response = await adminApiGet("/api/admin/mixtapes");
|
|
@@ -2209,7 +2251,7 @@ function parseVersion2(version) {
|
|
|
2209
2251
|
var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
|
|
2210
2252
|
var init_version2 = __esm(() => {
|
|
2211
2253
|
init_output();
|
|
2212
|
-
currentVersion2 = "0.
|
|
2254
|
+
currentVersion2 = "0.115.0".trim() ? "0.115.0".trim() : "0.1.0";
|
|
2213
2255
|
});
|
|
2214
2256
|
|
|
2215
2257
|
// ../../packages/registry/src/index.ts
|
|
@@ -2265,6 +2307,18 @@ var init_src = __esm(() => {
|
|
|
2265
2307
|
url: `${SITE}/mixtapes`,
|
|
2266
2308
|
weights: { ssh: "secondary", web: "primary" }
|
|
2267
2309
|
},
|
|
2310
|
+
{
|
|
2311
|
+
exposedContent: [
|
|
2312
|
+
"the mixability set-builder — chain a set from Fluncle's findings that mixes clean, share it as a link"
|
|
2313
|
+
],
|
|
2314
|
+
kind: "web_route",
|
|
2315
|
+
name: "web.mix",
|
|
2316
|
+
operatorNotes: "ADMIN-GATED at launch (RFC mixability-engine, Decision 1): the full /mix set-builder ships behind admin auth until the archive clears the ~250-finding floor. Registered here as `pending` (dark to every public consumer — the dev-row, llms.txt, the sitemap, /status) so going public is one flip: remove `pending` + set a web weight + announce. The `list_mixable_tracks` API op already ships at its final public tier, so the flip needs no rebuild. No probeConfig — the admin gate 302s a bare GET to /admin/login, which would read as a false 'down'.",
|
|
2317
|
+
pending: true,
|
|
2318
|
+
route: "/mix",
|
|
2319
|
+
url: `${SITE}/mix`,
|
|
2320
|
+
weights: { web: "secondary" }
|
|
2321
|
+
},
|
|
2268
2322
|
{
|
|
2269
2323
|
exposedContent: [
|
|
2270
2324
|
"the feed-first Stories surface — full-bleed findings",
|
|
@@ -3258,6 +3312,9 @@ async function trackUpdateCommand(trackId, options) {
|
|
|
3258
3312
|
if (options.embedding !== undefined) {
|
|
3259
3313
|
body.embedding = options.embedding;
|
|
3260
3314
|
}
|
|
3315
|
+
if (options.galaxyId !== undefined) {
|
|
3316
|
+
body.galaxyId = options.galaxyId;
|
|
3317
|
+
}
|
|
3261
3318
|
if (options.note !== undefined) {
|
|
3262
3319
|
body.note = options.note;
|
|
3263
3320
|
}
|
|
@@ -3450,6 +3507,9 @@ async function trackUpdateCommand2(trackId, options) {
|
|
|
3450
3507
|
if (options.embedding !== undefined) {
|
|
3451
3508
|
body.embedding = options.embedding;
|
|
3452
3509
|
}
|
|
3510
|
+
if (options.galaxyId !== undefined) {
|
|
3511
|
+
body.galaxyId = options.galaxyId;
|
|
3512
|
+
}
|
|
3453
3513
|
if (options.note !== undefined) {
|
|
3454
3514
|
body.note = options.note;
|
|
3455
3515
|
}
|
|
@@ -3486,6 +3546,7 @@ __export(exports_admin_tracks, {
|
|
|
3486
3546
|
queueCommand: () => queueCommand,
|
|
3487
3547
|
observeQueueCommand: () => observeQueueCommand,
|
|
3488
3548
|
noteQueueCommand: () => noteQueueCommand,
|
|
3549
|
+
mixableOrderCommand: () => mixableOrderCommand,
|
|
3489
3550
|
listCommand: () => listCommand,
|
|
3490
3551
|
enrichQueueCommand: () => enrichQueueCommand,
|
|
3491
3552
|
embedQueueCommand: () => embedQueueCommand,
|
|
@@ -3665,6 +3726,13 @@ async function vehiclesCommand(limit) {
|
|
|
3665
3726
|
vehicle: track.videoVehicle
|
|
3666
3727
|
}));
|
|
3667
3728
|
}
|
|
3729
|
+
async function mixableOrderCommand(logIds, seed) {
|
|
3730
|
+
const params = new URLSearchParams({ ids: logIds.join(",") });
|
|
3731
|
+
if (seed) {
|
|
3732
|
+
params.set("seed", seed);
|
|
3733
|
+
}
|
|
3734
|
+
return adminApiGet(`/api/admin/tracks/mixable-order?${params.toString()}`);
|
|
3735
|
+
}
|
|
3668
3736
|
var pageSize3 = 48;
|
|
3669
3737
|
var init_admin_tracks = __esm(() => {
|
|
3670
3738
|
init_api();
|
|
@@ -7748,6 +7816,10 @@ function addListenCommands(program2) {
|
|
|
7748
7816
|
const { artistsListCommand: artistsListCommand2, artistsGetCommand: artistsGetCommand2 } = await Promise.resolve().then(() => (init_artists(), exports_artists));
|
|
7749
7817
|
await runArtists(slug, options, { artistsGetCommand: artistsGetCommand2, artistsListCommand: artistsListCommand2 });
|
|
7750
7818
|
});
|
|
7819
|
+
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) => {
|
|
7820
|
+
const { galaxiesListCommand: galaxiesListCommand2, galaxyGetCommand: galaxyGetCommand2 } = await Promise.resolve().then(() => (init_galaxies(), exports_galaxies));
|
|
7821
|
+
await runGalaxies(slug, options, { galaxiesListCommand: galaxiesListCommand2, galaxyGetCommand: galaxyGetCommand2 });
|
|
7822
|
+
});
|
|
7751
7823
|
program2.command("mixtapes").description("Fluncle's checkpoint sets").option("--json", "Print JSON", false).action(async (options) => {
|
|
7752
7824
|
const { mixtapesCommand: mixtapesCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7753
7825
|
await runMixtapes(options, mixtapesCommand2);
|
|
@@ -7877,7 +7949,11 @@ function addAdminCommands(program2) {
|
|
|
7877
7949
|
const { trackGetAdminCommand: trackGetAdminCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
|
|
7878
7950
|
await runTrackGetAdmin(idOrLogId, options, trackGetAdminCommand2);
|
|
7879
7951
|
});
|
|
7880
|
-
adminTrack.command("
|
|
7952
|
+
adminTrack.command("mixable-order").description("Order findings into a smooth proposed mix (2\u201364 Log IDs)").argument("[logIds...]", "The findings to order, by Log ID").option("--seed <logId>", "Pin the first stop").option("--json", "Print JSON", false).allowExcessArguments().action(async (logIds, options) => {
|
|
7953
|
+
const { mixableOrderCommand: mixableOrderCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
7954
|
+
await runMixableOrder(logIds, options, mixableOrderCommand2);
|
|
7955
|
+
});
|
|
7956
|
+
adminTrack.command("update").description("Certify a track into the archive").argument("[trackId]").option("--analyzed-at <iso>", "Analysis-write ISO timestamp (BPM/key provenance)").option("--analyzed-from <class>", "Audio class BPM/key were analyzed from: full or preview").option("--bpm <number>", "Track BPM").option("--bpm-confidence <number>", "Analyzer confidence in the BPM (0..1)").option("--bpm-source <source>", "Where the BPM came from (analyzer bpmSource)").option("--embedding <json>", "MuQ audio embedding as a JSON array of 1024 floats").option("--embedding-file <file>", "Read the MuQ embedding JSON array from a file").option("--features <json>", "Audio feature JSON").option("--galaxy-id <id>", "Sonic galaxy assignment (empty string clears it)").option("--json", "Print JSON", false).option("--key <key>", "Musical key").option("--key-confidence <number>", "Analyzer confidence in the key (0..1)").option("--key-source <source>", "Where the key came from (analyzer keySource)").option("--note <text>", "Operator note").option("--status <status>", "Enrichment status").option("--video-url <url>", "Rendered video URL").allowExcessArguments().action(async (trackId, options) => {
|
|
7881
7957
|
const { trackUpdateCommand: trackUpdateCommand3 } = await Promise.resolve().then(() => (init_track(), exports_track));
|
|
7882
7958
|
await runTrackUpdate(trackId, options, trackUpdateCommand3);
|
|
7883
7959
|
});
|
|
@@ -8119,6 +8195,22 @@ function addAdminCommands(program2) {
|
|
|
8119
8195
|
artists.action(() => {
|
|
8120
8196
|
artists.outputHelp();
|
|
8121
8197
|
});
|
|
8198
|
+
const galaxies = configureCommand(admin.command("galaxies").description("Sonic galaxy map (cluster sweep) commands"));
|
|
8199
|
+
galaxies.action(() => {
|
|
8200
|
+
galaxies.outputHelp();
|
|
8201
|
+
});
|
|
8202
|
+
galaxies.command("map").description("Read the full galaxy map (named + unnamed + retired, with centroids)").option("--json", "Print JSON", false).action(async (options) => {
|
|
8203
|
+
const { galaxyMapReadCommand: galaxyMapReadCommand2 } = await Promise.resolve().then(() => (init_galaxies(), exports_galaxies));
|
|
8204
|
+
await runGalaxyMapRead(options, galaxyMapReadCommand2);
|
|
8205
|
+
});
|
|
8206
|
+
galaxies.command("embeddings").description("Read a cursor page of the embedded corpus (the cluster engine's input)").option("--cursor <cursor>", "Opaque cursor from a prior page's nextCursor").option("--limit <limit>", "Page size (default 200, clamped to 500)").option("--json", "Print JSON", false).action(async (options) => {
|
|
8207
|
+
const { galaxyEmbeddingsCommand: galaxyEmbeddingsCommand2 } = await Promise.resolve().then(() => (init_galaxies(), exports_galaxies));
|
|
8208
|
+
await runGalaxyEmbeddings(options, galaxyEmbeddingsCommand2);
|
|
8209
|
+
});
|
|
8210
|
+
galaxies.command("set-map").description("Write the galaxy map transactionally (mint new ids, upsert centroids, retire)").requiredOption("--file <file>", "Path to the clusters JSON ({ clusters: [...] } or a bare array)").option("--json", "Print JSON", false).action(async (options) => {
|
|
8211
|
+
const { galaxyMapWriteCommand: galaxyMapWriteCommand2 } = await Promise.resolve().then(() => (init_galaxies(), exports_galaxies));
|
|
8212
|
+
await runGalaxyMapWrite(options, galaxyMapWriteCommand2);
|
|
8213
|
+
});
|
|
8122
8214
|
const backfill = configureCommand(admin.command("backfills").description("Backfill operator-only archives"));
|
|
8123
8215
|
backfill.action(() => {
|
|
8124
8216
|
backfill.outputHelp();
|
|
@@ -8684,6 +8776,24 @@ async function runTrackGet(idOrLogId, options, trackGetCommand2) {
|
|
|
8684
8776
|
console.log(`Log: ${t.logPageUrl}`);
|
|
8685
8777
|
}
|
|
8686
8778
|
}
|
|
8779
|
+
async function runMixableOrder(logIds, options, mixableOrderCommand2) {
|
|
8780
|
+
if (logIds.length < 2) {
|
|
8781
|
+
throw new Error("Give 2 or more Log IDs. Usage: fluncle admin tracks mixable-order <logId...> [--seed <logId>] [--json]");
|
|
8782
|
+
}
|
|
8783
|
+
const result = await mixableOrderCommand2(logIds, options.seed);
|
|
8784
|
+
if (options.json) {
|
|
8785
|
+
printJson(result);
|
|
8786
|
+
return;
|
|
8787
|
+
}
|
|
8788
|
+
console.log(`Proposed mix \u2014 a smooth chain (${result.algorithm}), not an energy-shaped set. Copy into Rekordbox as advisory.
|
|
8789
|
+
`);
|
|
8790
|
+
result.order.forEach((stop, index) => {
|
|
8791
|
+
const into = index === 0 ? "start" : stop.flagged ? "sparse join" : stop.transitionReason?.relationship.replace(/_/g, " ") ?? "\u2014";
|
|
8792
|
+
const meta = [stop.key, stop.bpm ? `${Math.round(stop.bpm)} bpm` : undefined].filter(Boolean).join(" \xB7 ");
|
|
8793
|
+
console.log(`${String(index + 1).padStart(2)}. ${stop.logId} ${stop.artists.join(", ")} \u2014 ${stop.title}`);
|
|
8794
|
+
console.log(` ${[into, meta].filter(Boolean).join(" \xB7 ")}`);
|
|
8795
|
+
});
|
|
8796
|
+
}
|
|
8687
8797
|
async function runTrackGetAdmin(idOrLogId, options, trackGetAdminCommand2) {
|
|
8688
8798
|
if (!idOrLogId) {
|
|
8689
8799
|
throw new Error("Missing id. Usage: fluncle admin tracks get <track_id|log_id> [--json]");
|
|
@@ -8750,6 +8860,7 @@ async function runTrackUpdate(trackId, options, trackUpdateCommand3) {
|
|
|
8750
8860
|
bpmSource: options.bpmSource,
|
|
8751
8861
|
embedding,
|
|
8752
8862
|
features: options.features,
|
|
8863
|
+
galaxyId: options.galaxyId,
|
|
8753
8864
|
key: options.key,
|
|
8754
8865
|
keyConfidence,
|
|
8755
8866
|
keySource: options.keySource,
|
|
@@ -9113,6 +9224,71 @@ async function runArtists(slug, options, commands) {
|
|
|
9113
9224
|
console.log(`${a.name.padEnd(40)} ${String(a.findingCount).padStart(3)} finding${a.findingCount === 1 ? "" : "s"}`);
|
|
9114
9225
|
}
|
|
9115
9226
|
}
|
|
9227
|
+
async function runGalaxies(slug, options, commands) {
|
|
9228
|
+
if (slug) {
|
|
9229
|
+
const { findings, galaxy } = await commands.galaxyGetCommand(slug);
|
|
9230
|
+
if (options.json) {
|
|
9231
|
+
printJson({ findings, galaxy, ok: true });
|
|
9232
|
+
return;
|
|
9233
|
+
}
|
|
9234
|
+
console.log(`${galaxy.name} (${galaxy.slug})`);
|
|
9235
|
+
console.log(`${galaxy.memberCount} finding${galaxy.memberCount === 1 ? "" : "s"}`);
|
|
9236
|
+
for (const finding of findings) {
|
|
9237
|
+
const coordinate3 = finding.logId ? `${finding.logId} ` : "";
|
|
9238
|
+
console.log(` ${coordinate3}${finding.artists.join(", ")} \u2014 ${finding.title}`);
|
|
9239
|
+
}
|
|
9240
|
+
return;
|
|
9241
|
+
}
|
|
9242
|
+
const galaxies = await commands.galaxiesListCommand();
|
|
9243
|
+
if (options.json) {
|
|
9244
|
+
printJson({ galaxies, ok: true });
|
|
9245
|
+
return;
|
|
9246
|
+
}
|
|
9247
|
+
if (galaxies.length === 0) {
|
|
9248
|
+
console.log("No galaxies named yet. Quiet map tonight.");
|
|
9249
|
+
return;
|
|
9250
|
+
}
|
|
9251
|
+
for (const galaxy of galaxies) {
|
|
9252
|
+
console.log(`${galaxy.name.padEnd(40)} ${String(galaxy.memberCount).padStart(3)} finding${galaxy.memberCount === 1 ? "" : "s"}`);
|
|
9253
|
+
}
|
|
9254
|
+
}
|
|
9255
|
+
async function runGalaxyMapRead(options, galaxyMapReadCommand2) {
|
|
9256
|
+
const galaxies = await galaxyMapReadCommand2();
|
|
9257
|
+
if (options.json) {
|
|
9258
|
+
printJson({ galaxies, ok: true });
|
|
9259
|
+
return;
|
|
9260
|
+
}
|
|
9261
|
+
if (galaxies.length === 0) {
|
|
9262
|
+
console.log("No galaxies on the map yet.");
|
|
9263
|
+
return;
|
|
9264
|
+
}
|
|
9265
|
+
for (const galaxy of galaxies) {
|
|
9266
|
+
const name = galaxy.name ?? "(unnamed)";
|
|
9267
|
+
const retired = galaxy.retiredAt ? " [retired]" : "";
|
|
9268
|
+
console.log(`${galaxy.id} ${galaxy.handle.padEnd(30)} ${name.padEnd(24)} ${String(galaxy.memberCount).padStart(4)} members${retired}`);
|
|
9269
|
+
}
|
|
9270
|
+
}
|
|
9271
|
+
async function runGalaxyEmbeddings(options, galaxyEmbeddingsCommand2) {
|
|
9272
|
+
const result = await galaxyEmbeddingsCommand2({ cursor: options.cursor, limit: options.limit });
|
|
9273
|
+
if (options.json) {
|
|
9274
|
+
printJson(result);
|
|
9275
|
+
return;
|
|
9276
|
+
}
|
|
9277
|
+
console.log(`${result.embeddings.length} embedding(s)${result.nextCursor ? ` \xB7 nextCursor: ${result.nextCursor}` : " \xB7 end of corpus"}`);
|
|
9278
|
+
}
|
|
9279
|
+
async function runGalaxyMapWrite(options, galaxyMapWriteCommand2) {
|
|
9280
|
+
const raw = JSON.parse(readFileSync6(options.file, "utf8"));
|
|
9281
|
+
const clusters = Array.isArray(raw) || raw === null ? raw : raw.clusters;
|
|
9282
|
+
if (!Array.isArray(clusters)) {
|
|
9283
|
+
throw new Error("Invalid --file: expected { clusters: [...] } or a JSON array of cluster rows");
|
|
9284
|
+
}
|
|
9285
|
+
const galaxies = await galaxyMapWriteCommand2(clusters);
|
|
9286
|
+
if (options.json) {
|
|
9287
|
+
printJson({ galaxies, ok: true });
|
|
9288
|
+
return;
|
|
9289
|
+
}
|
|
9290
|
+
console.log(`Wrote ${clusters.length} cluster(s); the map now holds ${galaxies.length} galaxy(ies).`);
|
|
9291
|
+
}
|
|
9116
9292
|
async function runMixtapes(options, mixtapesCommand2) {
|
|
9117
9293
|
const mixtapes = await mixtapesCommand2();
|
|
9118
9294
|
if (options.json) {
|
package/package.json
CHANGED