fluncle 0.155.0 → 0.157.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 +116 -5
  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.155.0".trim() ? "0.155.0".trim() : "0.1.0";
564
+ currentVersion = "0.157.0".trim() ? "0.157.0".trim() : "0.1.0";
565
565
  });
566
566
 
567
567
  // src/update-notifier.ts
@@ -2189,7 +2189,7 @@ function parseVersion2(version) {
2189
2189
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2190
2190
  var init_version2 = __esm(() => {
2191
2191
  init_output();
2192
- currentVersion2 = "0.155.0".trim() ? "0.155.0".trim() : "0.1.0";
2192
+ currentVersion2 = "0.157.0".trim() ? "0.157.0".trim() : "0.1.0";
2193
2193
  });
2194
2194
 
2195
2195
  // ../../packages/registry/src/index.ts
@@ -2913,6 +2913,8 @@ var init_src = __esm(() => {
2913
2913
  name: "cron.enrich",
2914
2914
  operatorNotes: "every 5m. Pure compute, zero LLM tokens. Source: docs/agents/hermes/scripts/enrich-sweep.*",
2915
2915
  probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-enrich", kind: "cron" },
2916
+ statusDescription: "BPM, key, and the spectral fingerprint",
2917
+ title: "Audio enrichment",
2916
2918
  weights: { status: "hidden" }
2917
2919
  },
2918
2920
  {
@@ -2924,6 +2926,8 @@ var init_src = __esm(() => {
2924
2926
  name: "cron.embed",
2925
2927
  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.",
2926
2928
  probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-embed", kind: "cron" },
2929
+ statusDescription: "MuQ vectors for sonic similarity",
2930
+ title: "Audio embeddings",
2927
2931
  weights: { status: "hidden" }
2928
2932
  },
2929
2933
  {
@@ -2935,6 +2939,8 @@ var init_src = __esm(() => {
2935
2939
  name: "cron.cluster",
2936
2940
  operatorNotes: "nightly (02:20 Amsterdam), run by a rave-02 HOST systemd timer (docs/agents/hermes/cluster-timer/). Assignment-ONLY + idempotent (a no-op on an unchanged corpus): assign each finding to its nearest stored centroid, recompute centroids as members' means, retire an emptied galaxy, consume an operator split_requested_at (a k=2 fit). Zero LLM tokens; sub-second CPU. A full k=9 fit is an OPERATOR act (--cold-start / --remint), never scheduled. Source: docs/agents/hermes/scripts/cluster-sweep.* + cluster.py. See docs/agents/cluster-engine.md.",
2937
2941
  probeConfig: { cadenceMs: 24 * 60 * MINUTE_MS, cronName: "fluncle-cluster", kind: "cron" },
2942
+ statusDescription: "groups the archive into sonic galaxies",
2943
+ title: "Sonic galaxies",
2938
2944
  weights: { status: "hidden" }
2939
2945
  },
2940
2946
  {
@@ -2946,6 +2952,8 @@ var init_src = __esm(() => {
2946
2952
  name: "cron.crawl",
2947
2953
  operatorNotes: "every 10m, run by a rave-02 HOST systemd timer (docs/agents/hermes/crawl-timer/). METADATA ONLY — it writes a `tracks` row with no `findings` row, so it certifies nothing (no Log ID, no note, no video, no public surface) and it captures no audio. Worker-paced (the box holds no MusicBrainz budget): one bounded pass per tick over the durable `crawl_frontier`, so a crawl is a marathon the SCHEDULE finishes and a reboot mid-label costs one node, not one crawl. The boundary gate is the operator's seed-label allowlist + graph distance (hop 0-2), never genre inference; a label the walk discovers enters `undecided` and is NOT crawled until he rules on it. Zero LLM tokens. Source: docs/agents/hermes/scripts/crawl-sweep.*. See docs/catalogue-crawler.md.",
2948
2954
  probeConfig: { cadenceMs: 10 * MINUTE_MS, cronName: "fluncle-crawl", kind: "cron" },
2955
+ statusDescription: "charts new tracks from the wider label graph",
2956
+ title: "Track crawler",
2949
2957
  weights: { status: "hidden" }
2950
2958
  },
2951
2959
  {
@@ -2957,6 +2965,8 @@ var init_src = __esm(() => {
2957
2965
  name: "cron.rank",
2958
2966
  operatorNotes: "every 30m, run by a rave-02 HOST systemd timer (docs/agents/hermes/rank-timer/). THE EAR's schedule, and it lands with the crawler on purpose: a timer ranking an empty table would be a /status row that means nothing, and the crawler is what creates rows. All the vector arithmetic runs in SQL inside the Worker; the sweep DRAINS (it loops while `remaining > 0` up to a tick budget), so a crawl that just landed 700 rows is ranked by the next tick. Self-healing: staleness is a fingerprint of the finding corpus, so logging or embedding a finding re-ranks the catalogue with no invalidation call. Writes DERIVED columns on catalogue rows only — it cannot certify. Zero LLM tokens. Source: docs/agents/hermes/scripts/rank-sweep.*. See docs/the-ear.md.",
2959
2967
  probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-rank", kind: "cron" },
2968
+ statusDescription: "ranks unvisited tracks by nearness to the archive",
2969
+ title: "Track ranking",
2960
2970
  weights: { status: "hidden" }
2961
2971
  },
2962
2972
  {
@@ -2968,6 +2978,8 @@ var init_src = __esm(() => {
2968
2978
  name: "cron.label-images",
2969
2979
  operatorNotes: "every 60m, run by a rave-02 HOST systemd timer (docs/agents/hermes/label-images-timer/). The DURABLE other half of the label entity: the crawler MINTS new labels every few minutes (each `image_state='pending'`) and the one-shot operator backfill only seeded the labels that already existed, so this cron is what gives every freshly-minted label its OWN logo instead of a borrowed album cover. METADATA ONLY — a label logo is internal, reversible, nominative-use trademark (the album-art posture); it certifies nothing and publishes nothing (agent tier, the `backfill_discogs` precedent). Worker-paced (the box holds no Discogs key / MusicBrainz budget): one bounded batch per tick walks each label's MB identity → its curated Discogs/Wikidata url-rels → downloads the logo once into R2, up the ladder Discogs → Wikidata → none (the freshest-cover floor). The `labels` row carries the durable reliability state (image_state/image_attempted_at/image_failures), so a resolved/none label is terminal and a vendor throttle just circuit-breaks and resumes next tick. Zero LLM tokens. Source: docs/agents/hermes/scripts/label-images-sweep.*. See docs/label-entity.md.",
2970
2980
  probeConfig: { cadenceMs: 60 * MINUTE_MS, cronName: "fluncle-label-images", kind: "cron" },
2981
+ statusDescription: "resolves each label's own mark",
2982
+ title: "Label logos",
2971
2983
  weights: { status: "hidden" }
2972
2984
  },
2973
2985
  {
@@ -2979,6 +2991,8 @@ var init_src = __esm(() => {
2979
2991
  name: "cron.cover-masters",
2980
2992
  operatorNotes: "every 60m, run by a rave-02 HOST systemd timer (docs/agents/hermes/cover-masters-timer/). The DURABLE other half of the album/artist cover (RFC musickit-second-authority U3b): the publish path + catalogue crawl MINT albums/artists (each `image_state='pending'`) and this cron gives each its OWN ≤1200²-capped cover derivative in R2 (found.fluncle.com, `albums/<slug>.<ext>` / `artists/<slug>.<ext>`) instead of hotlinking a third party — the label-logo posture, two entities over. IMAGE ONLY — a downscaled display derivative, reversible, the REF-05-conscious 1200 line (docs/album-artwork.md); it certifies nothing and publishes nothing (agent tier, the `backfill_label_images` precedent). Worker-paced: one bounded batch of albums (Apple template → Cover Art Archive → Spotify floor), then one of artists (Spotify floor), per tick; every rung requests a ≤1200 rendition and a byte read enforces the cap before the R2 put, so no un-downscaled original is ever stored. The `albums`/`artists` row carries the durable reliability state (image_state/image_attempted_at/image_failures), so a resolved/none entity is terminal. Served via Cloudflare Images `/cdn-cgi/image/…` (decision B). Zero LLM tokens. Source: docs/agents/hermes/scripts/cover-masters-sweep.*. See docs/album-artwork.md.",
2981
2993
  probeConfig: { cadenceMs: 60 * MINUTE_MS, cronName: "fluncle-cover-masters", kind: "cron" },
2994
+ statusDescription: "owns each album and artist its cover master",
2995
+ title: "Cover masters",
2982
2996
  weights: { status: "hidden" }
2983
2997
  },
2984
2998
  {
@@ -2990,6 +3004,8 @@ var init_src = __esm(() => {
2990
3004
  name: "cron.capture",
2991
3005
  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.",
2992
3006
  probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-capture", kind: "cron" },
3007
+ statusDescription: "captures each finding's full song once",
3008
+ title: "Full-song capture",
2993
3009
  weights: { status: "hidden" }
2994
3010
  },
2995
3011
  {
@@ -3001,6 +3017,8 @@ var init_src = __esm(() => {
3001
3017
  name: "cron.verify-captures",
3002
3018
  operatorNotes: "every 30m, run by a rave-02 HOST systemd timer (docs/agents/hermes/verify-captures-timer/). The HISTORIC half of the capture verification gate (docs/the-ear.md § Wrong audio): the capture sweep verifies every NEW download at ingest, and this sweep walks every capture that landed before the gate existed and gives each the same Chromaprint check against the track's ISRC-resolved official preview. The box only MEASURES (fpcalc + the sliding-window match) and reports a plain verdict; the Worker ROUTES it — match/no-preview stamp `capture_verification`, a CATALOGUE mismatch quarantines for re-capture, a FINDING mismatch only raises the `capture-suspect` /admin attention item (a machine never rewinds a public finding; the operator rules with flag_wrong_audio). Resumable by construction (a stamped row leaves the worklist); degrades honestly without fpcalc (pre-rebake: `fpcalc_missing`, nothing stamped). Zero LLM tokens. Source: docs/agents/hermes/scripts/verify-captures.* + fingerprint-match.ts. See docs/the-ear.md.",
3003
3019
  probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-verify-captures", kind: "cron" },
3020
+ statusDescription: "checks each captured song against its official preview",
3021
+ title: "Capture verification",
3004
3022
  weights: { status: "hidden" }
3005
3023
  },
3006
3024
  {
@@ -3012,6 +3030,8 @@ var init_src = __esm(() => {
3012
3030
  name: "cron.context-note",
3013
3031
  operatorNotes: "every 5m. --no-agent trigger; the Worker does the Firecrawl + Haiku distill. Zero on-box tokens.",
3014
3032
  probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-context-note", kind: "cron" },
3033
+ statusDescription: "distills the facts behind each finding",
3034
+ title: "Context notes",
3015
3035
  weights: { status: "hidden" }
3016
3036
  },
3017
3037
  {
@@ -3023,6 +3043,34 @@ var init_src = __esm(() => {
3023
3043
  name: "cron.note",
3024
3044
  operatorNotes: "every 10m. Hybrid --no-agent; one claude -p authors the line. Never clobbers an operator note.",
3025
3045
  probeConfig: { cadenceMs: 10 * MINUTE_MS, cronName: "fluncle-note", kind: "cron" },
3046
+ statusDescription: "writes each finding's editorial note",
3047
+ title: "Editorial notes",
3048
+ weights: { status: "hidden" }
3049
+ },
3050
+ {
3051
+ command: "fluncle admin artists describe --queue",
3052
+ exposedContent: [
3053
+ "auto-author the /artist/<slug> voiced bio, fill-empty-only (hybrid: one claude -p call)"
3054
+ ],
3055
+ kind: "cron",
3056
+ name: "cron.artist-bio",
3057
+ operatorNotes: "every 30m. Hybrid --no-agent; one claude -p authors the paragraph, its grounding assembled WORKER-side (Firecrawl facts + the artist's finding titles) via the draft-bio op. Never clobbers an operator bio. Live. Source: docs/agents/hermes/scripts/{entity-bio-sweep.ts,artist-bio-sweep.sh}.",
3058
+ probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-artist-bio", kind: "cron" },
3059
+ statusDescription: "writes each artist's voiced bio",
3060
+ title: "Artist bios",
3061
+ weights: { status: "hidden" }
3062
+ },
3063
+ {
3064
+ command: "fluncle admin labels describe --queue",
3065
+ exposedContent: [
3066
+ "auto-author the /label/<slug> voiced bio, fill-empty-only (hybrid: one claude -p call)"
3067
+ ],
3068
+ kind: "cron",
3069
+ name: "cron.label-bio",
3070
+ operatorNotes: "every 30m. Hybrid --no-agent; one claude -p authors the paragraph, its grounding assembled WORKER-side (Firecrawl facts + the label's finding titles) via the draft-bio op. Never clobbers an operator bio. Live. Source: docs/agents/hermes/scripts/{entity-bio-sweep.ts,label-bio-sweep.sh}.",
3071
+ probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-label-bio", kind: "cron" },
3072
+ statusDescription: "writes each label's voiced bio",
3073
+ title: "Label bios",
3026
3074
  weights: { status: "hidden" }
3027
3075
  },
3028
3076
  {
@@ -3034,6 +3082,8 @@ var init_src = __esm(() => {
3034
3082
  name: "cron.triage",
3035
3083
  operatorNotes: "every 15m. Hybrid --no-agent; a deterministic archive dedupe + DnB-plausibility heuristic feeds one claude -p phrasing, length-gated. Writes the verdict onto a PENDING submission so it lands in the /admin attention queue already assessed; approve/reject stays operator tier. Source: docs/agents/hermes/scripts/triage-sweep.{sh,ts}.",
3036
3084
  probeConfig: { cadenceMs: 15 * MINUTE_MS, cronName: "fluncle-triage", kind: "cron" },
3085
+ statusDescription: "pre-chews each crew submission's verdict",
3086
+ title: "Submission triage",
3037
3087
  weights: { status: "hidden" }
3038
3088
  },
3039
3089
  {
@@ -3050,6 +3100,8 @@ var init_src = __esm(() => {
3050
3100
  kind: "cron",
3051
3101
  schedule: { time: "00:40", tz: "Europe/Amsterdam" }
3052
3102
  },
3103
+ statusDescription: "writes each sector-day up in the Logbook",
3104
+ title: "Logbook author",
3053
3105
  weights: { status: "hidden" }
3054
3106
  },
3055
3107
  {
@@ -3061,6 +3113,8 @@ var init_src = __esm(() => {
3061
3113
  name: "cron.observation",
3062
3114
  operatorNotes: "every 60m. Hybrid --no-agent; one claude -p authors the script, the Worker voice-gates + renders.",
3063
3115
  probeConfig: { cadenceMs: 60 * MINUTE_MS, cronName: "fluncle-observation", kind: "cron" },
3116
+ statusDescription: "Fluncle's spoken field observations",
3117
+ title: "Audio observations",
3064
3118
  weights: { status: "hidden" }
3065
3119
  },
3066
3120
  {
@@ -3070,6 +3124,8 @@ var init_src = __esm(() => {
3070
3124
  name: "cron.backfill",
3071
3125
  operatorNotes: "every 30m. Pure HTTP driving, zero LLM tokens. Agent tier.",
3072
3126
  probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-backfill", kind: "cron" },
3127
+ statusDescription: "repairs Discogs ids and Last.fm loves",
3128
+ title: "Metadata backfill",
3073
3129
  weights: { status: "hidden" }
3074
3130
  },
3075
3131
  {
@@ -3080,6 +3136,8 @@ var init_src = __esm(() => {
3080
3136
  name: "cron.artist-sweep",
3081
3137
  operatorNotes: "every 60m. --no-agent trigger; the Worker does the MB walk + Firecrawl /v2/extract + YouTube channel resolution. Zero on-box tokens. MB rows land as status=auto (trusted); Firecrawl rows as status=candidate (operator-confirm before public). Source: docs/agents/hermes/scripts/artist-sweep.*",
3082
3138
  probeConfig: { cadenceMs: 60 * MINUTE_MS, cronName: "fluncle-artist-sweep", kind: "cron" },
3139
+ statusDescription: "resolves each artist's socials and identity links",
3140
+ title: "Artist resolution",
3083
3141
  weights: { status: "hidden" }
3084
3142
  },
3085
3143
  {
@@ -3091,6 +3149,8 @@ var init_src = __esm(() => {
3091
3149
  name: "cron.social-capture",
3092
3150
  operatorNotes: "every 10m. Pure HTTP trigger, zero LLM tokens. Agent tier (fills the public URL only — publishes nothing). The box's baked CLI predates the `--capture` verb, so the cron curls POST /api/admin/social/posts/capture directly; the Worker polls Postiz and writes back. Source: docs/agents/hermes/scripts/social-capture-sweep.sh. Probed on /status as cron.social-capture.",
3093
3151
  probeConfig: { cadenceMs: 10 * MINUTE_MS, cronName: "fluncle-social-capture", kind: "cron" },
3152
+ statusDescription: "the live YouTube and TikTok URLs for each posted video",
3153
+ title: "Social links",
3094
3154
  weights: { status: "hidden" }
3095
3155
  },
3096
3156
  {
@@ -3101,6 +3161,8 @@ var init_src = __esm(() => {
3101
3161
  name: "cron.studio-clip",
3102
3162
  operatorNotes: "every 15m. Pure-trigger, zero LLM tokens: a deterministic ffmpeg cut driven by the fluncle CLI (`admin clips list` → `admin clips cut`), agent-scoped (list_clips + the agent-tier presign_clip_upload / finalize_clip_cut). Cuts the operator-framed 9:16 clips from the /admin/studio editor (keyed by Log ID) out of the set video → ships `<clipId>/footage.mp4` to R2 for the /admin/clips library + the clip drip-feed. The box runs the standalone bun BINARY (the npm thin client can't spawn ffmpeg). Source: docs/agents/hermes/scripts/clip-sweep.sh. Probed on /status as cron.studio-clip.",
3103
3163
  probeConfig: { cadenceMs: 15 * MINUTE_MS, cronName: "fluncle-studio-clip", kind: "cron" },
3164
+ statusDescription: "cuts set videos into 9:16 clips",
3165
+ title: "Studio clips",
3104
3166
  weights: { status: "hidden" }
3105
3167
  },
3106
3168
  {
@@ -3112,6 +3174,8 @@ var init_src = __esm(() => {
3112
3174
  name: "cron.clip-drip",
3113
3175
  operatorNotes: "every 20m. Pure HTTP trigger, zero LLM tokens. Admin tier (needs the Worker's Postiz key, which the box never sees — the box only triggers; the `finalize_clip_cut` / `record_health` precedent). The Worker checks the global kill switch FIRST (paused → no-op), then posts due clips bounded by a per-tick cap AND a rolling-24h IG cap. Every clip auto-enters the schedule at a jittered ~23-25h after the queue tail. The operator pauses/resumes with `fluncle admin clips drip-pause` / `drip-resume`. Source: docs/agents/hermes/scripts/clip-drip-sweep.sh. Probed on /status as cron.clip-drip.",
3114
3176
  probeConfig: { cadenceMs: 20 * MINUTE_MS, cronName: "fluncle-clip-drip", kind: "cron" },
3177
+ statusDescription: "drip-feeds set clips to Instagram",
3178
+ title: "Clip drip-feed",
3115
3179
  weights: { status: "hidden" }
3116
3180
  },
3117
3181
  {
@@ -3123,6 +3187,8 @@ var init_src = __esm(() => {
3123
3187
  name: "cron.publish-advance",
3124
3188
  operatorNotes: "every 30m. Pure HTTP trigger, zero LLM tokens. The last autonomy gap: render finishes → this pushes, with no operator beat between. Admin tier (needs the Worker's Postiz key, which the box never sees — the box only triggers; the `drip_clips` / `capture_post_urls` precedent). SHIPS DARK: the Worker's kill switch is DEFAULT-DENY (only an explicit `false` in the `publish_advance_paused` setting runs it), so the timer ticks and posts nothing until `fluncle admin publish resume`. The Worker reads the switch FIRST, then advances at most ONE ready finding — both masters finalized, 15m settled, the whole bundle served on R2, the (track, platform) row CLAIMED atomically before any Postiz call, a rolling-24h cap of 6 pushes. A failed push is left `failed` for the operator and never auto-retried. Source: docs/agents/hermes/scripts/publish-advance-sweep.sh. Probed on /status as cron.publish-advance.",
3125
3189
  probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-publish-advance", kind: "cron" },
3190
+ statusDescription: "advances the publish queue on his own clock",
3191
+ title: "Publish advance",
3126
3192
  weights: { status: "hidden" }
3127
3193
  },
3128
3194
  {
@@ -3134,6 +3200,8 @@ var init_src = __esm(() => {
3134
3200
  name: "cron.render",
3135
3201
  operatorNotes: "every 60m. A conductor: triggers a detached @fluncle-video render on a scale-to-zero box.ascii box (rave-03). Never posts to social (operator-tier 403). Probed on /status as service `cron.render` (its own last-run freshness); the box's reachability is the SEPARATE `render-box` probe (the conductor state file).",
3136
3202
  probeConfig: { cadenceMs: 60 * MINUTE_MS, cronName: "fluncle-render", kind: "cron" },
3203
+ statusDescription: "the conductor's last run",
3204
+ title: "Render cron",
3137
3205
  weights: { status: "hidden" }
3138
3206
  },
3139
3207
  {
@@ -3144,6 +3212,8 @@ var init_src = __esm(() => {
3144
3212
  name: "cron.healthcheck",
3145
3213
  operatorNotes: "every 10m, run by a rave-02 host systemd timer (docs/agents/hermes/healthcheck-timer/) — decoupled from the Hermes cron gateway so the prober isn't starved by the scheduler it monitors. Pure probing, zero LLM tokens. POSTs to the agent-tier record_health op that /status reads.",
3146
3214
  probeConfig: { cadenceMs: 10 * MINUTE_MS, cronName: "fluncle-healthcheck", kind: "cron" },
3215
+ statusDescription: "the prober behind this very page",
3216
+ title: "Healthcheck prober",
3147
3217
  weights: { status: "hidden" }
3148
3218
  },
3149
3219
  {
@@ -3160,6 +3230,8 @@ var init_src = __esm(() => {
3160
3230
  kind: "cron",
3161
3231
  schedule: { time: "15:00", tz: "Europe/Amsterdam", weekday: 5 }
3162
3232
  },
3233
+ statusDescription: "drafts the Friday edition",
3234
+ title: "Weekly newsletter",
3163
3235
  weights: { status: "secondary" }
3164
3236
  },
3165
3237
  {
@@ -3175,6 +3247,8 @@ var init_src = __esm(() => {
3175
3247
  kind: "cron",
3176
3248
  schedule: { time: "03:00", tz: "Europe/Amsterdam" }
3177
3249
  },
3250
+ statusDescription: "a daily off-site snapshot of the archive",
3251
+ title: "Database backup",
3178
3252
  weights: { status: "secondary" }
3179
3253
  },
3180
3254
  {
@@ -3191,6 +3265,8 @@ var init_src = __esm(() => {
3191
3265
  kind: "cron",
3192
3266
  schedule: { time: "04:00", tz: "Europe/Amsterdam" }
3193
3267
  },
3268
+ statusDescription: "counts the crew and how far the probes reached",
3269
+ title: "Reach snapshot",
3194
3270
  weights: { status: "secondary" }
3195
3271
  },
3196
3272
  {
@@ -3206,6 +3282,8 @@ var init_src = __esm(() => {
3206
3282
  kind: "cron",
3207
3283
  schedule: { time: "01:00", tz: "Europe/Amsterdam" }
3208
3284
  },
3285
+ statusDescription: "nightly one-domain codebase audit → a PR",
3286
+ title: "Nightly audit",
3209
3287
  weights: { status: "secondary" }
3210
3288
  },
3211
3289
  {
@@ -3221,6 +3299,8 @@ var init_src = __esm(() => {
3221
3299
  kind: "cron",
3222
3300
  schedule: { time: "05:00", tz: "Europe/Amsterdam" }
3223
3301
  },
3302
+ statusDescription: "reviews + merges the nightly audit PR",
3303
+ title: "Audit reviewer",
3224
3304
  weights: { status: "secondary" }
3225
3305
  }
3226
3306
  ];
@@ -5810,7 +5890,7 @@ __export(exports_admin_catalogue, {
5810
5890
  async function catalogueRankCommand(options) {
5811
5891
  const limit = options.limit ? Number.parseInt(options.limit, 10) : undefined;
5812
5892
  const response = await adminApiPost("/api/admin/catalogue/rank", limit ? { limit } : {});
5813
- return response.summary;
5893
+ return { summary: response.summary, telescope: response.telescope };
5814
5894
  }
5815
5895
  async function catalogueListCommand(options) {
5816
5896
  const params = new URLSearchParams;
@@ -5896,6 +5976,7 @@ var exports_admin_artists = {};
5896
5976
  __export(exports_admin_artists, {
5897
5977
  resolveArtistCommand: () => resolveArtistCommand,
5898
5978
  listArtistsCommand: () => listArtistsCommand,
5979
+ draftArtistBioCommand: () => draftArtistBioCommand,
5899
5980
  describeArtistCommand: () => describeArtistCommand,
5900
5981
  buildBioBody: () => buildBioBody,
5901
5982
  backfillArtistsCommand: () => backfillArtistsCommand,
@@ -5915,6 +5996,9 @@ function buildBioBody(options) {
5915
5996
  async function describeArtistCommand(slug, options) {
5916
5997
  return adminApiPost(`/api/admin/artists/${encodeURIComponent(slug)}/bio`, buildBioBody(options));
5917
5998
  }
5999
+ async function draftArtistBioCommand(slug) {
6000
+ return adminApiGet(`/api/admin/artists/${encodeURIComponent(slug)}/bio-draft`);
6001
+ }
5918
6002
  async function artistsBioQueueCommand(limit) {
5919
6003
  const response = await adminApiGet(`/api/admin/artists/bio-queue?limit=${limit}`);
5920
6004
  return response.artists;
@@ -5966,12 +6050,16 @@ var init_admin_artists2 = __esm(() => {
5966
6050
  var exports_admin_labels = {};
5967
6051
  __export(exports_admin_labels, {
5968
6052
  labelsBioQueueCommand: () => labelsBioQueueCommand,
6053
+ draftLabelBioCommand: () => draftLabelBioCommand,
5969
6054
  describeLabelCommand: () => describeLabelCommand,
5970
6055
  backfillLabelImagesCommand: () => backfillLabelImagesCommand
5971
6056
  });
5972
6057
  async function describeLabelCommand(slug, options) {
5973
6058
  return adminApiPost(`/api/admin/labels/${encodeURIComponent(slug)}/bio`, buildBioBody2(options));
5974
6059
  }
6060
+ async function draftLabelBioCommand(slug) {
6061
+ return adminApiGet(`/api/admin/labels/${encodeURIComponent(slug)}/bio-draft`);
6062
+ }
5975
6063
  async function labelsBioQueueCommand(limit) {
5976
6064
  const response = await adminApiGet(`/api/admin/labels/bio-queue?limit=${limit}`);
5977
6065
  return response.labels;
@@ -9175,6 +9263,10 @@ function addAdminCommands(program2) {
9175
9263
  const { describeArtistCommand: describeArtistCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
9176
9264
  await runEntityDescribe("artist", slug, options, describeArtistCommand2);
9177
9265
  });
9266
+ artists.command("draft-bio").description("Assemble the Worker-grounded bio prompt for an artist (the sweep's trigger)").argument("<slug>").option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
9267
+ const { draftArtistBioCommand: draftArtistBioCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
9268
+ await runEntityBioDraft("artist", slug, options, draftArtistBioCommand2);
9269
+ });
9178
9270
  const labels = configureCommand(admin.command("labels").description("Label entity commands (the voiced bio)"));
9179
9271
  labels.action(() => {
9180
9272
  labels.outputHelp();
@@ -9188,6 +9280,10 @@ function addAdminCommands(program2) {
9188
9280
  const { describeLabelCommand: describeLabelCommand2 } = await Promise.resolve().then(() => (init_admin_labels(), exports_admin_labels));
9189
9281
  await runEntityDescribe("label", slug, options, describeLabelCommand2);
9190
9282
  });
9283
+ labels.command("draft-bio").description("Assemble the Worker-grounded bio prompt for a label (the sweep's trigger)").argument("<slug>").option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
9284
+ const { draftLabelBioCommand: draftLabelBioCommand2 } = await Promise.resolve().then(() => (init_admin_labels(), exports_admin_labels));
9285
+ await runEntityBioDraft("label", slug, options, draftLabelBioCommand2);
9286
+ });
9191
9287
  const migrations = configureCommand(admin.command("migrations").description("One-off operator data migrations"));
9192
9288
  migrations.action(() => {
9193
9289
  migrations.outputHelp();
@@ -9352,6 +9448,18 @@ async function runEntityBioQueue(kind, options, queueCommand2) {
9352
9448
  console.log(` ${item.slug} ${item.name}`);
9353
9449
  }
9354
9450
  }
9451
+ async function runEntityBioDraft(kind, slug, options, draftCommand) {
9452
+ const draft = await draftCommand(slug);
9453
+ if (options.json) {
9454
+ printJson(draft);
9455
+ return;
9456
+ }
9457
+ if (!draft.found) {
9458
+ console.log(`No ${kind} with slug ${slug}.`);
9459
+ return;
9460
+ }
9461
+ console.log(`Drafted a bio prompt for ${draft.name} (${draft.findingCount} finding(s), facts: ${draft.hasFacts ? "yes" : "none"}, prompt v${draft.promptVersion}).`);
9462
+ }
9355
9463
  async function runTrackSimilar(idOrLogId, options, trackSimilarCommand2) {
9356
9464
  if (!idOrLogId) {
9357
9465
  throw new Error("Missing id. Usage: fluncle tracks similar <track_id|log_id> [--limit <n>] [--json]");
@@ -10841,14 +10949,17 @@ async function runGalaxies(slug, options, commands) {
10841
10949
  }
10842
10950
  }
10843
10951
  async function runCatalogueRank(options, catalogueRankCommand2) {
10844
- const summary = await catalogueRankCommand2({ limit: options.limit });
10952
+ const { summary, telescope } = await catalogueRankCommand2({ limit: options.limit });
10845
10953
  if (options.json) {
10846
- printJson({ ok: true, summary });
10954
+ printJson({ ok: true, summary, telescope });
10847
10955
  return;
10848
10956
  }
10849
10957
  const quarantine = summary.quarantined > 0 ? ` Quarantined ${summary.quarantined} as wrong audio.` : "";
10850
10958
  const deduped = summary.catalogueDuplicates > 0 ? ` Marked ${summary.catalogueDuplicates} as catalogue duplicates.` : "";
10851
10959
  console.log(`Ranked ${summary.scored} against ${summary.embeddedFindings} embedded findings; prioritized ${summary.prioritized} for capture.${quarantine}${deduped} ${summary.remaining} still stale.`);
10960
+ if (telescope) {
10961
+ console.log(telescope.ok ? `Telescope: ${telescope.size} aboard${telescope.changed ? " (updated)" : ""}.` : `Telescope: sync failed \u2014 ${telescope.reason}`);
10962
+ }
10852
10963
  }
10853
10964
  async function runCatalogueList(options, catalogueListCommand2) {
10854
10965
  const { summary, tracks } = await catalogueListCommand2({
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.155.0"
34
+ "version": "0.157.0"
35
35
  }