fluncle 0.66.0 → 0.68.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 +107 -12
- package/package.json +1 -1
package/bin/fluncle.mjs
CHANGED
|
@@ -423,7 +423,7 @@ function parseVersion(version) {
|
|
|
423
423
|
var currentVersion;
|
|
424
424
|
var init_version = __esm(() => {
|
|
425
425
|
init_output();
|
|
426
|
-
currentVersion = "0.
|
|
426
|
+
currentVersion = "0.68.0".trim() ? "0.68.0".trim() : "0.1.0";
|
|
427
427
|
});
|
|
428
428
|
|
|
429
429
|
// src/update-notifier.ts
|
|
@@ -2138,10 +2138,13 @@ function parseVersion2(version) {
|
|
|
2138
2138
|
var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
|
|
2139
2139
|
var init_version2 = __esm(() => {
|
|
2140
2140
|
init_output();
|
|
2141
|
-
currentVersion2 = "0.
|
|
2141
|
+
currentVersion2 = "0.68.0".trim() ? "0.68.0".trim() : "0.1.0";
|
|
2142
2142
|
});
|
|
2143
2143
|
|
|
2144
2144
|
// ../../packages/registry/src/index.ts
|
|
2145
|
+
function liveSurfaces() {
|
|
2146
|
+
return SURFACES.filter((surface) => surface.pending !== true);
|
|
2147
|
+
}
|
|
2145
2148
|
var SITE = "https://www.fluncle.com", PROBE_CADENCE_MS, PROBE_TIMEOUT_MS, MINUTE_MS, SURFACES;
|
|
2146
2149
|
var init_src = __esm(() => {
|
|
2147
2150
|
PROBE_CADENCE_MS = 10 * 60 * 1000;
|
|
@@ -2664,6 +2667,18 @@ var init_src = __esm(() => {
|
|
|
2664
2667
|
operatorNotes: "Authenticated admin/agent tier. The enrichment crons drive a subset of these.",
|
|
2665
2668
|
weights: { cli: "hidden" }
|
|
2666
2669
|
},
|
|
2670
|
+
{
|
|
2671
|
+
exposedContent: [
|
|
2672
|
+
"Fluncle Lens — the browser extension that finds fluncle:// coordinates on any page and links each to its /log/<coord> finding (with a hover card from the public API)"
|
|
2673
|
+
],
|
|
2674
|
+
kind: "extension",
|
|
2675
|
+
name: "extension.lens",
|
|
2676
|
+
operatorNotes: "Fluncle Lens (apps/extension), MV3. PENDING Chrome Web Store review — DARK until approval, then flip `pending` + set the assigned listing URL. The placeholder `url` is the store search for the listing; probeConfig is the post-approval /status check.",
|
|
2677
|
+
pending: true,
|
|
2678
|
+
probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
|
|
2679
|
+
url: "https://chromewebstore.google.com/search/Fluncle%20Lens",
|
|
2680
|
+
weights: { web: "secondary" }
|
|
2681
|
+
},
|
|
2667
2682
|
{
|
|
2668
2683
|
command: "fluncle admin tracks enrich --queue",
|
|
2669
2684
|
exposedContent: [
|
|
@@ -2820,7 +2835,7 @@ var init_status = __esm(() => {
|
|
|
2820
2835
|
init_api();
|
|
2821
2836
|
serviceLabels = (() => {
|
|
2822
2837
|
const labels = new Map;
|
|
2823
|
-
for (const surface of
|
|
2838
|
+
for (const surface of liveSurfaces()) {
|
|
2824
2839
|
const match = surface.operatorNotes?.match(/service `([a-z0-9-]+)`/);
|
|
2825
2840
|
const id = match?.[1];
|
|
2826
2841
|
const label = surface.exposedContent[0];
|
|
@@ -3044,7 +3059,7 @@ __export(exports_admin_tracks, {
|
|
|
3044
3059
|
backfillDiscogsCommand: () => backfillDiscogsCommand
|
|
3045
3060
|
});
|
|
3046
3061
|
async function fetchAdminTracks(options) {
|
|
3047
|
-
const { hasContext, hasNote, hasObservation, hasVideo, max, order, status } = options;
|
|
3062
|
+
const { hasContext, hasNote, hasObservation, hasVideo, max, order, retryEmptyContext, status } = options;
|
|
3048
3063
|
const results = [];
|
|
3049
3064
|
let cursor;
|
|
3050
3065
|
do {
|
|
@@ -3055,6 +3070,9 @@ async function fetchAdminTracks(options) {
|
|
|
3055
3070
|
if (hasContext !== undefined) {
|
|
3056
3071
|
params.set("hasContext", String(hasContext));
|
|
3057
3072
|
}
|
|
3073
|
+
if (retryEmptyContext) {
|
|
3074
|
+
params.set("retryEmptyContext", "true");
|
|
3075
|
+
}
|
|
3058
3076
|
if (hasNote !== undefined) {
|
|
3059
3077
|
params.set("hasNote", String(hasNote));
|
|
3060
3078
|
}
|
|
@@ -3094,8 +3112,8 @@ async function queueCommand(limit, filters = {}) {
|
|
|
3094
3112
|
async function enrichQueueCommand(limit) {
|
|
3095
3113
|
return fetchAdminTracks({ max: limit, order: "asc", status: "queue" });
|
|
3096
3114
|
}
|
|
3097
|
-
async function contextQueueCommand(limit) {
|
|
3098
|
-
return fetchAdminTracks({ hasContext: false, max: limit, order: "asc" });
|
|
3115
|
+
async function contextQueueCommand(limit, retryEmptyContext = false) {
|
|
3116
|
+
return fetchAdminTracks({ hasContext: false, max: limit, order: "asc", retryEmptyContext });
|
|
3099
3117
|
}
|
|
3100
3118
|
async function observeQueueCommand(limit) {
|
|
3101
3119
|
return fetchAdminTracks({
|
|
@@ -3144,6 +3162,73 @@ var init_admin_tracks = __esm(() => {
|
|
|
3144
3162
|
init_recent2();
|
|
3145
3163
|
});
|
|
3146
3164
|
|
|
3165
|
+
// src/version-match.ts
|
|
3166
|
+
function isRemix(title) {
|
|
3167
|
+
return REMIX_MARKER.test(title);
|
|
3168
|
+
}
|
|
3169
|
+
function stripVersionSuffix(title) {
|
|
3170
|
+
const parts = title.split(/\s+-\s+/);
|
|
3171
|
+
if (parts.length > 1 && VERSION_MARKER.test(parts[parts.length - 1] ?? "")) {
|
|
3172
|
+
return parts.slice(0, -1).join(" - ").trim();
|
|
3173
|
+
}
|
|
3174
|
+
return title.trim();
|
|
3175
|
+
}
|
|
3176
|
+
function tokenize(value) {
|
|
3177
|
+
return value.toLowerCase().normalize("NFKD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, " ").trim().split(" ").filter(Boolean);
|
|
3178
|
+
}
|
|
3179
|
+
function versionTokens(title) {
|
|
3180
|
+
const parts = title.split(/\s+-\s+/);
|
|
3181
|
+
const tail = parts.length > 1 ? parts[parts.length - 1] ?? "" : "";
|
|
3182
|
+
if (parts.length > 1 && VERSION_MARKER.test(tail)) {
|
|
3183
|
+
return new Set(tokenize(tail));
|
|
3184
|
+
}
|
|
3185
|
+
const bracketed = /[([]([^)\]]*?)[)\]]/.exec(title);
|
|
3186
|
+
if (bracketed?.[1] && VERSION_MARKER.test(bracketed[1])) {
|
|
3187
|
+
return new Set(tokenize(bracketed[1]));
|
|
3188
|
+
}
|
|
3189
|
+
return new Set;
|
|
3190
|
+
}
|
|
3191
|
+
function versionMatches(findingTitle, candidateTitle) {
|
|
3192
|
+
const findingIsRemix = isRemix(findingTitle);
|
|
3193
|
+
const candidateIsRemix = isRemix(candidateTitle);
|
|
3194
|
+
if (findingIsRemix) {
|
|
3195
|
+
if (!candidateIsRemix) {
|
|
3196
|
+
return false;
|
|
3197
|
+
}
|
|
3198
|
+
const want = [...versionTokens(findingTitle)].filter((t) => !VERSION_STOPWORDS.has(t));
|
|
3199
|
+
if (want.length === 0) {
|
|
3200
|
+
return true;
|
|
3201
|
+
}
|
|
3202
|
+
const have = versionTokens(candidateTitle);
|
|
3203
|
+
for (const token of want) {
|
|
3204
|
+
if (!have.has(token)) {
|
|
3205
|
+
return false;
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
return true;
|
|
3209
|
+
}
|
|
3210
|
+
return !candidateIsRemix;
|
|
3211
|
+
}
|
|
3212
|
+
function baseTitleMatches(findingTitle, candidateTitle) {
|
|
3213
|
+
const want = new Set(tokenize(stripVersionSuffix(findingTitle)));
|
|
3214
|
+
const have = new Set(tokenize(stripVersionSuffix(candidateTitle)));
|
|
3215
|
+
if (want.size === 0) {
|
|
3216
|
+
return false;
|
|
3217
|
+
}
|
|
3218
|
+
for (const token of want) {
|
|
3219
|
+
if (!have.has(token)) {
|
|
3220
|
+
return false;
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
return true;
|
|
3224
|
+
}
|
|
3225
|
+
var VERSION_MARKER, REMIX_MARKER, VERSION_STOPWORDS;
|
|
3226
|
+
var init_version_match = __esm(() => {
|
|
3227
|
+
VERSION_MARKER = /\b(mix|edit|version|remix|dub|vip|bootleg|rework|re-?edit|flip|refix|remaster(?:ed)?|instrumental)\b/i;
|
|
3228
|
+
REMIX_MARKER = /\b(remix|bootleg|vip|rework|re-?edit|flip|refix)\b/i;
|
|
3229
|
+
VERSION_STOPWORDS = new Set(["mix", "the", "and", "feat", "ft", "edit", "version", "remix"]);
|
|
3230
|
+
});
|
|
3231
|
+
|
|
3147
3232
|
// src/commands/preview-archive.ts
|
|
3148
3233
|
var exports_preview_archive = {};
|
|
3149
3234
|
__export(exports_preview_archive, {
|
|
@@ -3247,16 +3332,22 @@ async function resolveDeezerByIsrc(isrc) {
|
|
|
3247
3332
|
return;
|
|
3248
3333
|
}
|
|
3249
3334
|
}
|
|
3335
|
+
function durationAgrees(findingMs, candidateSeconds) {
|
|
3336
|
+
if (!findingMs || !candidateSeconds) {
|
|
3337
|
+
return true;
|
|
3338
|
+
}
|
|
3339
|
+
return Math.abs(candidateSeconds - findingMs / 1000) <= DURATION_TOLERANCE_S;
|
|
3340
|
+
}
|
|
3250
3341
|
async function resolveDeezerSearch(track) {
|
|
3251
3342
|
const artist = track.artists[0]?.trim();
|
|
3252
3343
|
if (!artist || !track.title.trim()) {
|
|
3253
3344
|
return;
|
|
3254
3345
|
}
|
|
3255
3346
|
try {
|
|
3256
|
-
const query = `artist:"${artist}" track:"${track.title.trim()}"`;
|
|
3347
|
+
const query = `artist:"${artist}" track:"${stripVersionSuffix(track.title.trim())}"`;
|
|
3257
3348
|
const response = await fetch(`https://api.deezer.com/search?q=${encodeURIComponent(query)}`);
|
|
3258
3349
|
const body = await response.json();
|
|
3259
|
-
const hit = (body.data ?? []).find((item) => item.preview);
|
|
3350
|
+
const hit = (body.data ?? []).find((item) => item.preview && versionMatches(track.title, item.title ?? "") && baseTitleMatches(track.title, item.title ?? "") && durationAgrees(track.durationMs, item.duration));
|
|
3260
3351
|
return downloadPreview(hit?.preview, "deezer:search");
|
|
3261
3352
|
} catch {
|
|
3262
3353
|
return;
|
|
@@ -3270,7 +3361,7 @@ async function resolveItunes(track) {
|
|
|
3270
3361
|
try {
|
|
3271
3362
|
const response = await fetch(`https://itunes.apple.com/search?term=${encodeURIComponent(`${artist} ${track.title}`)}&media=music&limit=10`);
|
|
3272
3363
|
const body = await response.json();
|
|
3273
|
-
const hit = (body.results ?? []).find((item) => item.previewUrl && normalize(item.artistName ?? "").includes(normalize(artist)));
|
|
3364
|
+
const hit = (body.results ?? []).find((item) => item.previewUrl && normalize(item.artistName ?? "").includes(normalize(artist)) && versionMatches(track.title, item.trackName ?? "") && baseTitleMatches(track.title, item.trackName ?? "") && durationAgrees(track.durationMs, item.trackTimeMillis ? item.trackTimeMillis / 1000 : undefined));
|
|
3274
3365
|
return downloadPreview(hit?.previewUrl, "itunes");
|
|
3275
3366
|
} catch {
|
|
3276
3367
|
return;
|
|
@@ -3332,8 +3423,10 @@ function extensionForMime(mime) {
|
|
|
3332
3423
|
function normalize(value) {
|
|
3333
3424
|
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3334
3425
|
}
|
|
3426
|
+
var DURATION_TOLERANCE_S = 5;
|
|
3335
3427
|
var init_preview_archive = __esm(() => {
|
|
3336
3428
|
init_api();
|
|
3429
|
+
init_version_match();
|
|
3337
3430
|
});
|
|
3338
3431
|
|
|
3339
3432
|
// src/commands/mixtape-youtube.ts
|
|
@@ -6471,7 +6564,7 @@ function addAdminCommands(program2) {
|
|
|
6471
6564
|
const { trackObserveCommand: trackObserveCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
|
|
6472
6565
|
await runTrackObserve(idOrLogId, options, trackObserveCommand2);
|
|
6473
6566
|
});
|
|
6474
|
-
adminTrack.command("context").description("Gather the field notes for a finding (facts only; observe speaks from them)").argument("[idOrLogId]").option("--queue", "Show the context worklist (findings missing field notes), oldest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--query <text>", "Override the fact-search query (else the Worker builds one)").option("--refresh", "Re-run the fetch even if a note exists (backfill/sharpen)", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
|
|
6567
|
+
adminTrack.command("context").description("Gather the field notes for a finding (facts only; observe speaks from them)").argument("[idOrLogId]").option("--queue", "Show the context worklist (findings missing field notes), oldest first", false).option("--retry-empty", "With --queue: also re-pick finds confirmed empty last pass (widen the net)", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--query <text>", "Override the fact-search query (else the Worker builds one)").option("--refresh", "Re-run the fetch even if a note exists (backfill/sharpen)", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
|
|
6475
6568
|
if (options.queue) {
|
|
6476
6569
|
const { contextQueueCommand: contextQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
6477
6570
|
await runAdminContextQueue(options, contextQueueCommand2);
|
|
@@ -7281,7 +7374,8 @@ async function runAdminQueue(options, queueCommand2) {
|
|
|
7281
7374
|
}
|
|
7282
7375
|
async function runAdminContextQueue(options, contextQueueCommand2) {
|
|
7283
7376
|
const limit = parseListLimit(options.limit);
|
|
7284
|
-
const
|
|
7377
|
+
const retryEmpty = options.retryEmpty === true;
|
|
7378
|
+
const tracks = await contextQueueCommand2(limit, retryEmpty);
|
|
7285
7379
|
if (options.json) {
|
|
7286
7380
|
printJson({
|
|
7287
7381
|
ok: true,
|
|
@@ -7295,7 +7389,8 @@ async function runAdminContextQueue(options, contextQueueCommand2) {
|
|
|
7295
7389
|
}
|
|
7296
7390
|
const { trackRows: trackRows2 } = await Promise.resolve().then(() => (init_format2(), exports_format));
|
|
7297
7391
|
const noun = tracks.length === 1 ? "finding" : "findings";
|
|
7298
|
-
|
|
7392
|
+
const scope = retryEmpty ? " (incl. empty retries)" : "";
|
|
7393
|
+
console.log(`${tracks.length} ${noun} missing field notes${scope}, oldest first:`);
|
|
7299
7394
|
console.log(trackRows2(tracks).join(`
|
|
7300
7395
|
`));
|
|
7301
7396
|
}
|
package/package.json
CHANGED