fluncle 0.109.0 → 0.111.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 +332 -11
- 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.111.0".trim() ? "0.111.0".trim() : "0.1.0";
|
|
561
561
|
});
|
|
562
562
|
|
|
563
563
|
// src/update-notifier.ts
|
|
@@ -961,6 +961,7 @@ function mapTrack(track) {
|
|
|
961
961
|
addedToSpotify: track.addedToSpotify,
|
|
962
962
|
album: track.album,
|
|
963
963
|
albumImageUrl: track.albumImageUrl,
|
|
964
|
+
analyzedFrom: track.analyzedFrom,
|
|
964
965
|
artists: track.artists,
|
|
965
966
|
bpm: track.bpm,
|
|
966
967
|
durationMs: track.durationMs,
|
|
@@ -1673,6 +1674,7 @@ function mapTrack2(track) {
|
|
|
1673
1674
|
addedToSpotify: track.addedToSpotify,
|
|
1674
1675
|
album: track.album,
|
|
1675
1676
|
albumImageUrl: track.albumImageUrl,
|
|
1677
|
+
analyzedFrom: track.analyzedFrom,
|
|
1676
1678
|
artists: track.artists,
|
|
1677
1679
|
bpm: track.bpm,
|
|
1678
1680
|
durationMs: track.durationMs,
|
|
@@ -2207,7 +2209,7 @@ function parseVersion2(version) {
|
|
|
2207
2209
|
var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
|
|
2208
2210
|
var init_version2 = __esm(() => {
|
|
2209
2211
|
init_output();
|
|
2210
|
-
currentVersion2 = "0.
|
|
2212
|
+
currentVersion2 = "0.111.0".trim() ? "0.111.0".trim() : "0.1.0";
|
|
2211
2213
|
});
|
|
2212
2214
|
|
|
2213
2215
|
// ../../packages/registry/src/index.ts
|
|
@@ -2650,11 +2652,26 @@ var init_src = __esm(() => {
|
|
|
2650
2652
|
url: `${SITE}/.well-known/agent-skills/index.json`,
|
|
2651
2653
|
weights: { web: "tertiary" }
|
|
2652
2654
|
},
|
|
2655
|
+
{
|
|
2656
|
+
apiFormat: "application/json+oembed",
|
|
2657
|
+
exposedContent: [
|
|
2658
|
+
"the oEmbed 1.0 provider — a pasted /log, /mixtapes, or /artist link unfurls as a rich finding card (Discord/Notion/WordPress/Ghost/…)",
|
|
2659
|
+
"html iframes the self-contained /embed/<logId> card; thumbnail is the finding's OG image"
|
|
2660
|
+
],
|
|
2661
|
+
kind: "discovery",
|
|
2662
|
+
name: "discovery.oembed",
|
|
2663
|
+
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.',
|
|
2664
|
+
route: "/oembed",
|
|
2665
|
+
url: `${SITE}/oembed`,
|
|
2666
|
+
weights: { web: "tertiary" }
|
|
2667
|
+
},
|
|
2653
2668
|
{
|
|
2654
2669
|
apiFormat: "application/json",
|
|
2655
2670
|
discoveryUrl: `${SITE}/.well-known/mcp/server-card.json`,
|
|
2656
2671
|
exposedContent: [
|
|
2657
|
-
"the archive as MCP tools (Streamable HTTP, no auth): list_tracks, get_random_track, get_status, search_tracks, submit_track, subscribe_newsletter"
|
|
2672
|
+
"the archive as MCP tools (Streamable HTTP, no auth): list_tracks, get_track, get_random_track, get_status, search_tracks, submit_track, subscribe_newsletter",
|
|
2673
|
+
"the archive as MCP resources: each finding/mixtape at fluncle://finding/<logId> or fluncle://mixtape/<logId> (its public record)",
|
|
2674
|
+
"Fluncle-voiced MCP prompts: recommend_finding, walk_recent_night, decode_coordinate"
|
|
2658
2675
|
],
|
|
2659
2676
|
kind: "mcp",
|
|
2660
2677
|
name: "mcp.server",
|
|
@@ -2679,7 +2696,7 @@ var init_src = __esm(() => {
|
|
|
2679
2696
|
{
|
|
2680
2697
|
command: "ssh rave.fluncle.com",
|
|
2681
2698
|
exposedContent: [
|
|
2682
|
-
"the rave terminal TUI:
|
|
2699
|
+
"the rave terminal TUI: Latest bangers, Mixtape archive, Submit, Subscribe, Install CLI, About",
|
|
2683
2700
|
"deep-register one-shots: `ssh rave.fluncle.com latest|random`"
|
|
2684
2701
|
],
|
|
2685
2702
|
kind: "ssh",
|
|
@@ -2797,7 +2814,7 @@ var init_src = __esm(() => {
|
|
|
2797
2814
|
],
|
|
2798
2815
|
kind: "cron",
|
|
2799
2816
|
name: "cron.embed",
|
|
2800
|
-
operatorNotes: "every 5m. On-box MuQ (torch, ~16s/track), zero LLM tokens. Writes the vector via the agent-tier update_track. Source: docs/agents/hermes/scripts/embed-sweep.* + embed-track.py. See docs/
|
|
2817
|
+
operatorNotes: "every 5m. On-box MuQ (torch, ~16s/track), zero LLM tokens. Writes the vector via the agent-tier update_track. Source: docs/agents/hermes/scripts/embed-sweep.* + embed-track.py. See docs/track-lifecycle.md.",
|
|
2801
2818
|
probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-embed", kind: "cron" },
|
|
2802
2819
|
weights: { status: "hidden" }
|
|
2803
2820
|
},
|
|
@@ -2808,7 +2825,7 @@ var init_src = __esm(() => {
|
|
|
2808
2825
|
],
|
|
2809
2826
|
kind: "cron",
|
|
2810
2827
|
name: "cron.capture",
|
|
2811
|
-
operatorNotes: "every 5m, run by a rave-02 HOST systemd timer (docs/agents/hermes/capture-timer/) — NOT a gateway cron: a proxied yt-dlp fetch has an unbounded tail that would starve the 5-min enrich/context/note sweeps on the shared serial runner. A NON-BLOCKING side-channel (never gates enrich/embed). Runs yt-dlp through a residential proxy on a per-track sticky session, duration-guards the match, stores the full song in the PRIVATE fluncle-source-audio bucket (S3-direct), and writes back via the agent-tier update_track (with per-finding backoff). yt-dlp + ffprobe are a box deploy prereq. Newest-first so a fresh add jumps the backfill. Source: docs/agents/hermes/scripts/capture-sweep.*. See docs/
|
|
2828
|
+
operatorNotes: "every 5m, run by a rave-02 HOST systemd timer (docs/agents/hermes/capture-timer/) — NOT a gateway cron: a proxied yt-dlp fetch has an unbounded tail that would starve the 5-min enrich/context/note sweeps on the shared serial runner. A NON-BLOCKING side-channel (never gates enrich/embed). Runs yt-dlp through a residential proxy on a per-track sticky session, duration-guards the match, stores the full song in the PRIVATE fluncle-source-audio bucket (S3-direct), and writes back via the agent-tier update_track (with per-finding backoff). yt-dlp + ffprobe are a box deploy prereq. Newest-first so a fresh add jumps the backfill. Source: docs/agents/hermes/scripts/capture-sweep.*. See docs/track-lifecycle.md.",
|
|
2812
2829
|
probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-capture", kind: "cron" },
|
|
2813
2830
|
weights: { status: "hidden" }
|
|
2814
2831
|
},
|
|
@@ -3216,6 +3233,24 @@ async function trackUpdateCommand(trackId, options) {
|
|
|
3216
3233
|
if (options.note !== undefined) {
|
|
3217
3234
|
body.note = options.note;
|
|
3218
3235
|
}
|
|
3236
|
+
if (options.bpmSource !== undefined) {
|
|
3237
|
+
body.bpmSource = options.bpmSource;
|
|
3238
|
+
}
|
|
3239
|
+
if (options.bpmConfidence !== undefined) {
|
|
3240
|
+
body.bpmConfidence = options.bpmConfidence;
|
|
3241
|
+
}
|
|
3242
|
+
if (options.keySource !== undefined) {
|
|
3243
|
+
body.keySource = options.keySource;
|
|
3244
|
+
}
|
|
3245
|
+
if (options.keyConfidence !== undefined) {
|
|
3246
|
+
body.keyConfidence = options.keyConfidence;
|
|
3247
|
+
}
|
|
3248
|
+
if (options.analyzedFrom !== undefined) {
|
|
3249
|
+
body.analyzedFrom = options.analyzedFrom;
|
|
3250
|
+
}
|
|
3251
|
+
if (options.analyzedAt !== undefined) {
|
|
3252
|
+
body.analyzedAt = options.analyzedAt;
|
|
3253
|
+
}
|
|
3219
3254
|
return adminApiPatch(`/api/admin/tracks/${encodeURIComponent(trackId)}`, body);
|
|
3220
3255
|
}
|
|
3221
3256
|
async function trackRequeueVideoCommand(idOrLogId) {
|
|
@@ -3326,10 +3361,60 @@ var init_add = __esm(() => {
|
|
|
3326
3361
|
init_api();
|
|
3327
3362
|
});
|
|
3328
3363
|
|
|
3364
|
+
// src/commands/track.ts
|
|
3365
|
+
async function trackUpdateCommand2(trackId, options) {
|
|
3366
|
+
const body = {};
|
|
3367
|
+
if (options.bpm !== undefined) {
|
|
3368
|
+
body.bpm = options.bpm;
|
|
3369
|
+
}
|
|
3370
|
+
if (options.key !== undefined) {
|
|
3371
|
+
body.key = options.key;
|
|
3372
|
+
}
|
|
3373
|
+
if (options.videoUrl !== undefined) {
|
|
3374
|
+
body.videoUrl = options.videoUrl;
|
|
3375
|
+
}
|
|
3376
|
+
if (options.status !== undefined) {
|
|
3377
|
+
body.enrichmentStatus = options.status;
|
|
3378
|
+
}
|
|
3379
|
+
if (options.features !== undefined) {
|
|
3380
|
+
body.features = options.features;
|
|
3381
|
+
}
|
|
3382
|
+
if (options.embedding !== undefined) {
|
|
3383
|
+
body.embedding = options.embedding;
|
|
3384
|
+
}
|
|
3385
|
+
if (options.note !== undefined) {
|
|
3386
|
+
body.note = options.note;
|
|
3387
|
+
}
|
|
3388
|
+
if (options.bpmSource !== undefined) {
|
|
3389
|
+
body.bpmSource = options.bpmSource;
|
|
3390
|
+
}
|
|
3391
|
+
if (options.bpmConfidence !== undefined) {
|
|
3392
|
+
body.bpmConfidence = options.bpmConfidence;
|
|
3393
|
+
}
|
|
3394
|
+
if (options.keySource !== undefined) {
|
|
3395
|
+
body.keySource = options.keySource;
|
|
3396
|
+
}
|
|
3397
|
+
if (options.keyConfidence !== undefined) {
|
|
3398
|
+
body.keyConfidence = options.keyConfidence;
|
|
3399
|
+
}
|
|
3400
|
+
if (options.analyzedFrom !== undefined) {
|
|
3401
|
+
body.analyzedFrom = options.analyzedFrom;
|
|
3402
|
+
}
|
|
3403
|
+
if (options.analyzedAt !== undefined) {
|
|
3404
|
+
body.analyzedAt = options.analyzedAt;
|
|
3405
|
+
}
|
|
3406
|
+
return adminApiPatch(`/api/admin/tracks/${encodeURIComponent(trackId)}`, body);
|
|
3407
|
+
}
|
|
3408
|
+
var init_track2 = __esm(() => {
|
|
3409
|
+
init_api();
|
|
3410
|
+
init_output();
|
|
3411
|
+
});
|
|
3412
|
+
|
|
3329
3413
|
// src/commands/admin-tracks.ts
|
|
3330
3414
|
var exports_admin_tracks = {};
|
|
3331
3415
|
__export(exports_admin_tracks, {
|
|
3332
3416
|
vehiclesCommand: () => vehiclesCommand,
|
|
3417
|
+
requeueAnalysisCommand: () => requeueAnalysisCommand,
|
|
3333
3418
|
queueCommand: () => queueCommand,
|
|
3334
3419
|
observeQueueCommand: () => observeQueueCommand,
|
|
3335
3420
|
noteQueueCommand: () => noteQueueCommand,
|
|
@@ -3429,6 +3514,44 @@ async function embedQueueCommand(limit) {
|
|
|
3429
3514
|
async function captureQueueCommand(limit) {
|
|
3430
3515
|
return fetchAdminTracks({ captureQueue: true, max: limit, order: "desc" });
|
|
3431
3516
|
}
|
|
3517
|
+
async function requeueAnalysisCommand(options) {
|
|
3518
|
+
const findings = await fetchAdminTracks({ max: options.max, order: "asc" });
|
|
3519
|
+
const stale = findings.filter((track) => track.analyzedFrom !== "full");
|
|
3520
|
+
const rows = stale.map((track) => ({
|
|
3521
|
+
analyzedFrom: track.analyzedFrom,
|
|
3522
|
+
bpm: track.bpm,
|
|
3523
|
+
hasSourceAudio: Boolean(track.sourceAudioKey),
|
|
3524
|
+
key: track.key,
|
|
3525
|
+
logId: track.logId,
|
|
3526
|
+
title: track.title,
|
|
3527
|
+
trackId: track.trackId
|
|
3528
|
+
}));
|
|
3529
|
+
const withSourceAudio = rows.filter((row) => row.hasSourceAudio);
|
|
3530
|
+
const withoutSourceAudio = rows.filter((row) => !row.hasSourceAudio);
|
|
3531
|
+
const requeued = [];
|
|
3532
|
+
const failed = [];
|
|
3533
|
+
if (options.apply) {
|
|
3534
|
+
for (const row of rows) {
|
|
3535
|
+
try {
|
|
3536
|
+
await trackUpdateCommand2(row.trackId, { status: "pending" });
|
|
3537
|
+
requeued.push(row.trackId);
|
|
3538
|
+
} catch (error) {
|
|
3539
|
+
failed.push({
|
|
3540
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3541
|
+
trackId: row.trackId
|
|
3542
|
+
});
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
return {
|
|
3547
|
+
applied: options.apply,
|
|
3548
|
+
failed,
|
|
3549
|
+
requeued,
|
|
3550
|
+
scanned: findings.length,
|
|
3551
|
+
withSourceAudio,
|
|
3552
|
+
withoutSourceAudio
|
|
3553
|
+
};
|
|
3554
|
+
}
|
|
3432
3555
|
async function contextQueueCommand(limit, retryEmptyContext = false) {
|
|
3433
3556
|
return fetchAdminTracks({ hasContext: false, max: limit, order: "asc", retryEmptyContext });
|
|
3434
3557
|
}
|
|
@@ -3478,6 +3601,7 @@ var pageSize3 = 48;
|
|
|
3478
3601
|
var init_admin_tracks = __esm(() => {
|
|
3479
3602
|
init_api();
|
|
3480
3603
|
init_recent2();
|
|
3604
|
+
init_track2();
|
|
3481
3605
|
});
|
|
3482
3606
|
|
|
3483
3607
|
// src/commands/preview-archive.ts
|
|
@@ -5013,6 +5137,26 @@ var init_admin_artists = __esm(() => {
|
|
|
5013
5137
|
init_api();
|
|
5014
5138
|
});
|
|
5015
5139
|
|
|
5140
|
+
// src/commands/preview-migration.ts
|
|
5141
|
+
var exports_preview_migration = {};
|
|
5142
|
+
__export(exports_preview_migration, {
|
|
5143
|
+
migratePreviewArchiveCommand: () => migratePreviewArchiveCommand
|
|
5144
|
+
});
|
|
5145
|
+
async function migratePreviewArchiveCommand(args) {
|
|
5146
|
+
const params = new URLSearchParams({
|
|
5147
|
+
dryRun: String(args.dryRun),
|
|
5148
|
+
limit: String(args.limit),
|
|
5149
|
+
mode: args.mode
|
|
5150
|
+
});
|
|
5151
|
+
if (args.cursor) {
|
|
5152
|
+
params.set("cursor", args.cursor);
|
|
5153
|
+
}
|
|
5154
|
+
return adminApiPost(`/api/admin/migrations/preview-archive?${params.toString()}`);
|
|
5155
|
+
}
|
|
5156
|
+
var init_preview_migration = __esm(() => {
|
|
5157
|
+
init_api();
|
|
5158
|
+
});
|
|
5159
|
+
|
|
5016
5160
|
// src/interactive.ts
|
|
5017
5161
|
var exports_interactive = {};
|
|
5018
5162
|
__export(exports_interactive, {
|
|
@@ -7591,6 +7735,10 @@ function addAdminCommands(program2) {
|
|
|
7591
7735
|
const { captureQueueCommand: captureQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
7592
7736
|
await runAdminCaptureQueue(options, captureQueueCommand2);
|
|
7593
7737
|
});
|
|
7738
|
+
adminTracks.command("requeue-analysis").description("Re-queue findings whose BPM/key are preview-grade (dry-run; --apply to flip)").option("--apply", "Actually flip statuses (default is a dry-run preview)", false).option("--limit <limit>", "Cap the archive walk (default: the whole archive)").option("--json", "Print JSON", false).action(async (options) => {
|
|
7739
|
+
const { requeueAnalysisCommand: requeueAnalysisCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
7740
|
+
await runAdminRequeueAnalysis(options, requeueAnalysisCommand2);
|
|
7741
|
+
});
|
|
7594
7742
|
adminTracks.command("vehicles").description("Recent video vehicles, newest first (the style ledger for diversity)").option("--limit <limit>", "Number of vehicles to show", "10").option("--json", "Print JSON", false).action(async (options) => {
|
|
7595
7743
|
const { vehiclesCommand: vehiclesCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
7596
7744
|
await runAdminVehicles(options, vehiclesCommand2);
|
|
@@ -7600,9 +7748,9 @@ function addAdminCommands(program2) {
|
|
|
7600
7748
|
const { trackGetAdminCommand: trackGetAdminCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
|
|
7601
7749
|
await runTrackGetAdmin(idOrLogId, options, trackGetAdminCommand2);
|
|
7602
7750
|
});
|
|
7603
|
-
adminTrack.command("update").description("Certify a track into the archive").argument("[trackId]").option("--bpm <number>", "Track BPM").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("--json", "Print JSON", false).option("--key <key>", "Musical key").option("--note <text>", "Operator note").option("--status <status>", "Enrichment status").option("--video-url <url>", "Rendered video URL").allowExcessArguments().action(async (trackId, options) => {
|
|
7604
|
-
const { trackUpdateCommand:
|
|
7605
|
-
await runTrackUpdate(trackId, options,
|
|
7751
|
+
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("--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) => {
|
|
7752
|
+
const { trackUpdateCommand: trackUpdateCommand3 } = await Promise.resolve().then(() => (init_track(), exports_track));
|
|
7753
|
+
await runTrackUpdate(trackId, options, trackUpdateCommand3);
|
|
7606
7754
|
});
|
|
7607
7755
|
adminTrack.command("video").description("Upload a track's video bundle to R2 and link it").argument("[idOrLogId]").option("--allow-partial", "Allow an intentionally partial upload (skip the re-render-contract check; e.g. poster-only)", false).option("--composition <file>", "Composition source file").option("--cover <file>", "Cover image").option("--dir <dir>", "Bundle directory").option("--footage <file>", "Video footage (square crop source)").option("--footage-landscape <file>", "Clean landscape cut (optional escape hatch)").option("--footage-landscape-social <file>", "Landscape cut with baked text (optional)").option("--footage-notext <file>", "Portrait cut without the type layer (optional)").option("--footage-social <file>", "Portrait social cut (baked text)").option("--intent <file>", "Render-intent JSON (optional)").option("--json", "Print JSON", false).option("--metrics <file>", "Gate metrics JSON (optional)").option("--model <model>", "Authoring AI model (<provider>/<model>)").option("--note <file>", "Note file").option("--plate <file>", "Plate-lane photographic plate (plate.png; uploadable pre-render)").option("--plate-background <file>", "The plate's subject-removed background (plate.background.png, optional)").option("--poster <file>", "Poster image").option("--props <file>", "Render props JSON").option("--reasoning <level>", "Authoring model reasoning effort (e.g. high)").option("--render <file>", "Render metadata JSON").option("--scene <file>", "Scene replay manifest JSON (fluncle.scene/1, optional)").allowExcessArguments().action(async (idOrLogId, options) => {
|
|
7608
7756
|
const { trackVideoCommand: trackVideoCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
|
|
@@ -7844,6 +7992,14 @@ function addAdminCommands(program2) {
|
|
|
7844
7992
|
const { resolveArtistCommand: resolveArtistCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
|
|
7845
7993
|
await runArtistResolve(artistId, options, resolveArtistCommand2);
|
|
7846
7994
|
});
|
|
7995
|
+
const migrations = configureCommand(admin.command("migrations").description("One-off operator data migrations"));
|
|
7996
|
+
migrations.action(() => {
|
|
7997
|
+
migrations.outputHelp();
|
|
7998
|
+
});
|
|
7999
|
+
migrations.command("preview-archive").description("Move archived 30s previews from the public bucket to the private one (REF-05)").option("--execute", "Actually perform the migration (default is a dry run)", false).option("--delete-public", "Delete phase: sweep the whole public analysis/previews/ prefix (run only after a full copy)", false).option("--verify", "Read-only: count the objects still under the public analysis/previews/ prefix", false).option("--limit <limit>", "Max findings/objects to process per pass", "50").option("--json", "Print JSON", false).action(async (options) => {
|
|
8000
|
+
const { migratePreviewArchiveCommand: migratePreviewArchiveCommand2 } = await Promise.resolve().then(() => (init_preview_migration(), exports_preview_migration));
|
|
8001
|
+
await runMigratePreviewArchive(options, migratePreviewArchiveCommand2);
|
|
8002
|
+
});
|
|
7847
8003
|
}
|
|
7848
8004
|
async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCommand2) {
|
|
7849
8005
|
if (!idOrLogId || !options.file || !options.source || !options.mime) {
|
|
@@ -7936,6 +8092,88 @@ async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
|
|
|
7936
8092
|
console.log(`Authored the note for ${result.logId}:`);
|
|
7937
8093
|
console.log(` ${result.note}`);
|
|
7938
8094
|
}
|
|
8095
|
+
async function runMigratePreviewArchive(options, migratePreviewArchiveCommand2) {
|
|
8096
|
+
const limit = options.limit === undefined ? 50 : Number.parseInt(options.limit, 10);
|
|
8097
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
8098
|
+
throw new Error("Limit must be a positive integer");
|
|
8099
|
+
}
|
|
8100
|
+
const mode = options.verify ? "verify" : options.deletePublic ? "delete" : "copy";
|
|
8101
|
+
const dryRun = mode === "verify" ? true : !options.execute;
|
|
8102
|
+
if (mode === "verify") {
|
|
8103
|
+
const result = await migratePreviewArchiveCommand2({ dryRun: true, limit, mode });
|
|
8104
|
+
if (options.json) {
|
|
8105
|
+
printJson({ mode, ok: true, remaining: result.remaining, sampleKeys: result.sampleKeys });
|
|
8106
|
+
return;
|
|
8107
|
+
}
|
|
8108
|
+
console.log(`${result.remaining} object(s) remain under the public analysis/previews/ prefix.`);
|
|
8109
|
+
for (const key of result.sampleKeys) {
|
|
8110
|
+
console.log(` ${key}`);
|
|
8111
|
+
}
|
|
8112
|
+
return;
|
|
8113
|
+
}
|
|
8114
|
+
const copied = [];
|
|
8115
|
+
const deleted = [];
|
|
8116
|
+
const skipped = [];
|
|
8117
|
+
const failed = [];
|
|
8118
|
+
let cursor;
|
|
8119
|
+
let remaining = 0;
|
|
8120
|
+
let blocked = null;
|
|
8121
|
+
for (;; ) {
|
|
8122
|
+
const result = await migratePreviewArchiveCommand2({ cursor, dryRun, limit, mode });
|
|
8123
|
+
if (result.blocked) {
|
|
8124
|
+
blocked = result.blocked;
|
|
8125
|
+
remaining = result.remaining;
|
|
8126
|
+
break;
|
|
8127
|
+
}
|
|
8128
|
+
copied.push(...result.copied);
|
|
8129
|
+
deleted.push(...result.deleted);
|
|
8130
|
+
skipped.push(...result.skipped);
|
|
8131
|
+
failed.push(...result.failed);
|
|
8132
|
+
remaining = result.remaining;
|
|
8133
|
+
if (!options.json) {
|
|
8134
|
+
const verb = dryRun ? "would" : "did";
|
|
8135
|
+
console.log(` \u2026${mode} pass (${verb}): +${result.copiedCount} copied, +${result.deletedCount} deleted, ${result.skippedCount} skipped, ${result.failedCount} failed; ${result.remaining} remaining`);
|
|
8136
|
+
}
|
|
8137
|
+
if (result.nextCursor === null) {
|
|
8138
|
+
break;
|
|
8139
|
+
}
|
|
8140
|
+
cursor = result.nextCursor;
|
|
8141
|
+
}
|
|
8142
|
+
if (options.json) {
|
|
8143
|
+
printJson({
|
|
8144
|
+
blocked,
|
|
8145
|
+
copied,
|
|
8146
|
+
copiedCount: copied.length,
|
|
8147
|
+
deleted,
|
|
8148
|
+
deletedCount: deleted.length,
|
|
8149
|
+
dryRun,
|
|
8150
|
+
failed,
|
|
8151
|
+
failedCount: failed.length,
|
|
8152
|
+
mode,
|
|
8153
|
+
ok: true,
|
|
8154
|
+
remaining,
|
|
8155
|
+
skipped,
|
|
8156
|
+
skippedCount: skipped.length
|
|
8157
|
+
});
|
|
8158
|
+
return;
|
|
8159
|
+
}
|
|
8160
|
+
if (blocked) {
|
|
8161
|
+
console.log(`REFUSED (${blocked}): ${remaining} legacy row(s) still need copying first.`);
|
|
8162
|
+
return;
|
|
8163
|
+
}
|
|
8164
|
+
const done = mode === "delete" ? deleted.length : copied.length;
|
|
8165
|
+
const noun = mode === "delete" ? "public object(s) deleted" : "preview(s) copied to the private bucket";
|
|
8166
|
+
const prefix = dryRun ? "DRY RUN \u2014 would have: " : "";
|
|
8167
|
+
console.log(`${prefix}${done} ${noun}; ${skipped.length} skipped; ${failed.length} failed; ${remaining} still under the prefix.`);
|
|
8168
|
+
for (const item of skipped) {
|
|
8169
|
+
if (item.reason === "private_copy_absent") {
|
|
8170
|
+
console.log(` LEFT (no private copy) ${item.trackId}`);
|
|
8171
|
+
}
|
|
8172
|
+
}
|
|
8173
|
+
for (const item of failed) {
|
|
8174
|
+
console.log(` FAILED ${item.trackId}: ${item.error}`);
|
|
8175
|
+
}
|
|
8176
|
+
}
|
|
7939
8177
|
async function runPreviewArchiveBackfill(options, previewArchiveBackfillCommand2) {
|
|
7940
8178
|
const limit = options.limit === undefined ? undefined : Number.parseInt(options.limit, 10);
|
|
7941
8179
|
if (limit !== undefined && (!Number.isInteger(limit) || limit < 1)) {
|
|
@@ -8327,7 +8565,7 @@ function parseEmbeddingArg(raw) {
|
|
|
8327
8565
|
}
|
|
8328
8566
|
return parsed;
|
|
8329
8567
|
}
|
|
8330
|
-
async function runTrackUpdate(trackId, options,
|
|
8568
|
+
async function runTrackUpdate(trackId, options, trackUpdateCommand3) {
|
|
8331
8569
|
if (!trackId) {
|
|
8332
8570
|
throw new Error("Missing track id. Usage: fluncle admin tracks update <track_id>");
|
|
8333
8571
|
}
|
|
@@ -8335,13 +8573,30 @@ async function runTrackUpdate(trackId, options, trackUpdateCommand2) {
|
|
|
8335
8573
|
if (bpm !== undefined && !Number.isFinite(bpm)) {
|
|
8336
8574
|
throw new Error(`Invalid --bpm: ${options.bpm}`);
|
|
8337
8575
|
}
|
|
8576
|
+
const bpmConfidence = options.bpmConfidence === undefined ? undefined : Number(options.bpmConfidence);
|
|
8577
|
+
if (bpmConfidence !== undefined && !Number.isFinite(bpmConfidence)) {
|
|
8578
|
+
throw new Error(`Invalid --bpm-confidence: ${options.bpmConfidence}`);
|
|
8579
|
+
}
|
|
8580
|
+
const keyConfidence = options.keyConfidence === undefined ? undefined : Number(options.keyConfidence);
|
|
8581
|
+
if (keyConfidence !== undefined && !Number.isFinite(keyConfidence)) {
|
|
8582
|
+
throw new Error(`Invalid --key-confidence: ${options.keyConfidence}`);
|
|
8583
|
+
}
|
|
8584
|
+
if (options.analyzedFrom !== undefined && options.analyzedFrom !== "full" && options.analyzedFrom !== "preview") {
|
|
8585
|
+
throw new Error(`Invalid --analyzed-from: ${options.analyzedFrom} (expected full or preview)`);
|
|
8586
|
+
}
|
|
8338
8587
|
const embeddingRaw = options.embeddingFile ? readFileSync5(options.embeddingFile, "utf8") : options.embedding;
|
|
8339
8588
|
const embedding = parseEmbeddingArg(embeddingRaw);
|
|
8340
|
-
const result = await
|
|
8589
|
+
const result = await trackUpdateCommand3(trackId, {
|
|
8590
|
+
analyzedAt: options.analyzedAt,
|
|
8591
|
+
analyzedFrom: options.analyzedFrom,
|
|
8341
8592
|
bpm,
|
|
8593
|
+
bpmConfidence,
|
|
8594
|
+
bpmSource: options.bpmSource,
|
|
8342
8595
|
embedding,
|
|
8343
8596
|
features: options.features,
|
|
8344
8597
|
key: options.key,
|
|
8598
|
+
keyConfidence,
|
|
8599
|
+
keySource: options.keySource,
|
|
8345
8600
|
note: options.note,
|
|
8346
8601
|
status: options.status,
|
|
8347
8602
|
videoUrl: options.videoUrl
|
|
@@ -8869,6 +9124,66 @@ async function runAdminCaptureQueue(options, captureQueueCommand2) {
|
|
|
8869
9124
|
console.log(trackRows2(tracks).join(`
|
|
8870
9125
|
`));
|
|
8871
9126
|
}
|
|
9127
|
+
var REQUEUE_ANALYSIS_ARCHIVE_CAP = 1e6;
|
|
9128
|
+
function parseRequeueAnalysisLimit(value) {
|
|
9129
|
+
if (value === undefined) {
|
|
9130
|
+
return REQUEUE_ANALYSIS_ARCHIVE_CAP;
|
|
9131
|
+
}
|
|
9132
|
+
const limit = Number.parseInt(value, 10);
|
|
9133
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
9134
|
+
throw new Error("Limit must be a positive integer");
|
|
9135
|
+
}
|
|
9136
|
+
return limit;
|
|
9137
|
+
}
|
|
9138
|
+
async function runAdminRequeueAnalysis(options, requeueAnalysisCommand2) {
|
|
9139
|
+
const max = parseRequeueAnalysisLimit(options.limit);
|
|
9140
|
+
const result = await requeueAnalysisCommand2({ apply: options.apply === true, max });
|
|
9141
|
+
if (options.json) {
|
|
9142
|
+
printJson({ ok: true, ...result });
|
|
9143
|
+
return;
|
|
9144
|
+
}
|
|
9145
|
+
const { coordinate: coordinate3 } = await Promise.resolve().then(() => (init_format2(), exports_format));
|
|
9146
|
+
const staleCount = result.withSourceAudio.length + result.withoutSourceAudio.length;
|
|
9147
|
+
if (staleCount === 0) {
|
|
9148
|
+
console.log("Nothing to re-queue. Every finding's BPM/key was analyzed from full audio.");
|
|
9149
|
+
return;
|
|
9150
|
+
}
|
|
9151
|
+
const describe = (row) => {
|
|
9152
|
+
const coord = coordinate3({ logId: row.logId });
|
|
9153
|
+
const bpm = row.bpm ? `${row.bpm} bpm` : "no bpm";
|
|
9154
|
+
const key = row.key ?? "no key";
|
|
9155
|
+
const from = row.analyzedFrom ?? "legacy/null";
|
|
9156
|
+
return ` ${coord} ${row.trackId} ${row.title} \u2014 ${bpm}, ${key} (from ${from})`;
|
|
9157
|
+
};
|
|
9158
|
+
console.log(`${staleCount} finding${staleCount === 1 ? "" : "s"} with preview-grade BPM/key (scanned ${result.scanned}):`);
|
|
9159
|
+
if (result.withSourceAudio.length > 0) {
|
|
9160
|
+
console.log(`
|
|
9161
|
+
Captured full song on file \u2014 re-derives from full audio (${result.withSourceAudio.length}):`);
|
|
9162
|
+
console.log(result.withSourceAudio.map(describe).join(`
|
|
9163
|
+
`));
|
|
9164
|
+
}
|
|
9165
|
+
if (result.withoutSourceAudio.length > 0) {
|
|
9166
|
+
console.log(`
|
|
9167
|
+
No captured full song \u2014 re-derives from the 30s preview, still an upgrade with the fixed estimator (${result.withoutSourceAudio.length}):`);
|
|
9168
|
+
console.log(result.withoutSourceAudio.map(describe).join(`
|
|
9169
|
+
`));
|
|
9170
|
+
}
|
|
9171
|
+
console.log(`
|
|
9172
|
+
Caveat: keys backfilled from Rekordbox are indistinguishable from DSP keys (no legacy` + " provenance) and a re-enrichment may overwrite them. This dry-run diff is the gate \u2014" + " review it before applying.");
|
|
9173
|
+
if (!result.applied) {
|
|
9174
|
+
console.log(`
|
|
9175
|
+
Dry-run \u2014 nothing flipped. Re-run with --apply to re-queue all ${staleCount}.`);
|
|
9176
|
+
return;
|
|
9177
|
+
}
|
|
9178
|
+
console.log(`
|
|
9179
|
+
Applied \u2014 re-queued ${result.requeued.length} of ${staleCount}.`);
|
|
9180
|
+
if (result.failed.length > 0) {
|
|
9181
|
+
console.log(`${result.failed.length} failed:`);
|
|
9182
|
+
console.log(result.failed.map((entry) => ` ${entry.trackId}: ${entry.error}`).join(`
|
|
9183
|
+
`));
|
|
9184
|
+
process.exitCode = 1;
|
|
9185
|
+
}
|
|
9186
|
+
}
|
|
8872
9187
|
async function runAdminVehicles(options, vehiclesCommand2) {
|
|
8873
9188
|
const limit = parseListLimit(options.limit);
|
|
8874
9189
|
const vehicles = await vehiclesCommand2(limit);
|
|
@@ -9093,7 +9408,11 @@ function normalizeCommanderError(error) {
|
|
|
9093
9408
|
return new Error(message);
|
|
9094
9409
|
}
|
|
9095
9410
|
var stringOptions = new Set([
|
|
9411
|
+
"--analyzed-at",
|
|
9412
|
+
"--analyzed-from",
|
|
9096
9413
|
"--bpm",
|
|
9414
|
+
"--bpm-confidence",
|
|
9415
|
+
"--bpm-source",
|
|
9097
9416
|
"--composition",
|
|
9098
9417
|
"--cover",
|
|
9099
9418
|
"--dir",
|
|
@@ -9108,6 +9427,8 @@ var stringOptions = new Set([
|
|
|
9108
9427
|
"--from",
|
|
9109
9428
|
"--intent",
|
|
9110
9429
|
"--key",
|
|
9430
|
+
"--key-confidence",
|
|
9431
|
+
"--key-source",
|
|
9111
9432
|
"--limit",
|
|
9112
9433
|
"--metrics",
|
|
9113
9434
|
"--mime",
|
package/package.json
CHANGED