fluncle 0.61.0 → 0.63.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 +707 -7
  2. 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.61.0".trim() ? "0.61.0".trim() : "0.1.0";
426
+ currentVersion = "0.63.0".trim() ? "0.63.0".trim() : "0.1.0";
427
427
  });
428
428
 
429
429
  // src/update-notifier.ts
@@ -1914,7 +1914,692 @@ function parseVersion2(version) {
1914
1914
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
1915
1915
  var init_version2 = __esm(() => {
1916
1916
  init_output();
1917
- currentVersion2 = "0.61.0".trim() ? "0.61.0".trim() : "0.1.0";
1917
+ currentVersion2 = "0.63.0".trim() ? "0.63.0".trim() : "0.1.0";
1918
+ });
1919
+
1920
+ // ../../packages/registry/src/index.ts
1921
+ var SITE = "https://www.fluncle.com", PROBE_CADENCE_MS, PROBE_TIMEOUT_MS, MINUTE_MS, SURFACES;
1922
+ var init_src = __esm(() => {
1923
+ PROBE_CADENCE_MS = 10 * 60 * 1000;
1924
+ PROBE_TIMEOUT_MS = 10 * 1000;
1925
+ MINUTE_MS = 60 * 1000;
1926
+ SURFACES = [
1927
+ {
1928
+ exposedContent: ["the archive — every certified finding, newest first, cover-led"],
1929
+ kind: "web_route",
1930
+ name: "web.home",
1931
+ operatorNotes: "The Worker root. galaxy./radio. rewrite their root to /galaxy and /radio.",
1932
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
1933
+ route: "/",
1934
+ url: `${SITE}/`,
1935
+ weight: "primary"
1936
+ },
1937
+ {
1938
+ exposedContent: [
1939
+ "the log index — every finding's coordinate page",
1940
+ "/log/:logId — one finding's permanent home (the Log ID resolves here)"
1941
+ ],
1942
+ kind: "web_route",
1943
+ name: "web.log",
1944
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
1945
+ route: "/log",
1946
+ url: `${SITE}/log`,
1947
+ weight: "primary"
1948
+ },
1949
+ {
1950
+ exposedContent: ["Fluncle's own DJ mixtapes — each a checkpoint set with an F-marked Log ID"],
1951
+ kind: "web_route",
1952
+ name: "web.mixtapes",
1953
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
1954
+ route: "/mixtapes",
1955
+ url: `${SITE}/mixtapes`,
1956
+ weight: "primary"
1957
+ },
1958
+ {
1959
+ exposedContent: [
1960
+ "the feed-first Stories surface — full-bleed findings",
1961
+ "/stories/:logId — one finding as a Story"
1962
+ ],
1963
+ kind: "web_route",
1964
+ name: "web.stories",
1965
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
1966
+ route: "/stories",
1967
+ url: `${SITE}/stories`,
1968
+ weight: "secondary"
1969
+ },
1970
+ {
1971
+ exposedContent: ["who Fluncle is, what the Galaxy is, how to read a Log ID"],
1972
+ kind: "web_route",
1973
+ name: "web.about",
1974
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
1975
+ route: "/about",
1976
+ url: `${SITE}/about`,
1977
+ weight: "secondary"
1978
+ },
1979
+ {
1980
+ exposedContent: [
1981
+ "the newsletter archive — every sent edition",
1982
+ "/newsletter/:number — one past edition rendered on the web"
1983
+ ],
1984
+ kind: "web_route",
1985
+ name: "web.newsletter",
1986
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
1987
+ route: "/newsletter",
1988
+ url: `${SITE}/newsletter`,
1989
+ weight: "secondary"
1990
+ },
1991
+ {
1992
+ exposedContent: [
1993
+ "the Fumadocs developer docs",
1994
+ "/docs/api — the embedded Scalar API reference"
1995
+ ],
1996
+ kind: "web_route",
1997
+ name: "web.docs",
1998
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
1999
+ route: "/docs",
2000
+ url: `${SITE}/docs`,
2001
+ weight: "secondary"
2002
+ },
2003
+ {
2004
+ exposedContent: ["the public service-health dashboard — uptime per service, recent events"],
2005
+ kind: "web_route",
2006
+ name: "web.status",
2007
+ operatorNotes: "The future status.fluncle.com host points here. The fluncle-healthcheck cron POSTs its snapshots to the agent-tier record_health op that this page reads.",
2008
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2009
+ route: "/status",
2010
+ url: `${SITE}/status`,
2011
+ weight: "secondary"
2012
+ },
2013
+ {
2014
+ exposedContent: ["the Galaxy game — the 8-bit fly-to-every-banger arcade front door"],
2015
+ kind: "web_route",
2016
+ name: "web.galaxy",
2017
+ operatorNotes: "Reachable at galaxy.fluncle.com, whose root the router rewrites to /galaxy.",
2018
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2019
+ route: "/galaxy",
2020
+ url: `${SITE}/galaxy`,
2021
+ weight: "primary"
2022
+ },
2023
+ {
2024
+ exposedContent: [
2025
+ "the cycling observation station — Fluncle's spoken field observations on a loop"
2026
+ ],
2027
+ kind: "web_route",
2028
+ name: "web.radio",
2029
+ operatorNotes: "Reachable at radio.fluncle.com, whose root the router rewrites to /radio.",
2030
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2031
+ route: "/radio",
2032
+ url: `${SITE}/radio`,
2033
+ weight: "secondary"
2034
+ },
2035
+ {
2036
+ exposedContent: ["the privacy policy"],
2037
+ kind: "web_route",
2038
+ name: "web.privacy",
2039
+ route: "/privacy",
2040
+ url: `${SITE}/privacy`,
2041
+ weight: "tertiary"
2042
+ },
2043
+ {
2044
+ exposedContent: ["the Galaxy game's front door (root rewrites to /galaxy)"],
2045
+ kind: "subdomain",
2046
+ name: "subdomain.galaxy",
2047
+ operatorNotes: "Isomorphic host-rewrite in apps/web router (input/output) so SSR + hydration agree.",
2048
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2049
+ subdomain: "galaxy.fluncle.com",
2050
+ url: "https://galaxy.fluncle.com",
2051
+ weight: "primary"
2052
+ },
2053
+ {
2054
+ exposedContent: ["the observation station (root rewrites to /radio)"],
2055
+ kind: "subdomain",
2056
+ name: "subdomain.radio",
2057
+ operatorNotes: "Isomorphic host-rewrite in apps/web router so SSR + hydration agree.",
2058
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2059
+ subdomain: "radio.fluncle.com",
2060
+ url: "https://radio.fluncle.com",
2061
+ weight: "secondary"
2062
+ },
2063
+ {
2064
+ exposedContent: [
2065
+ "the R2 media zone — each finding's video bundle + mixtape audio",
2066
+ "the /cdn-cgi/media transform base (same zone, no cross-origin)"
2067
+ ],
2068
+ kind: "subdomain",
2069
+ name: "subdomain.found",
2070
+ operatorNotes: "FOUND_BASE in apps/web/src/lib/media.ts. Probed on /status as service `r2`.",
2071
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2072
+ subdomain: "found.fluncle.com",
2073
+ url: "https://found.fluncle.com",
2074
+ weight: "tertiary"
2075
+ },
2076
+ {
2077
+ exposedContent: ["the delegated DNS zone's host label (see the dns surface for the resolver)"],
2078
+ kind: "subdomain",
2079
+ name: "subdomain.dig",
2080
+ operatorNotes: "The zone is served by apps/dns; see the `dns.zone` surface.",
2081
+ subdomain: "dig.fluncle.com",
2082
+ url: "https://dig.fluncle.com",
2083
+ weight: "tertiary"
2084
+ },
2085
+ {
2086
+ exposedContent: ["the planned status host — points at /status"],
2087
+ kind: "subdomain",
2088
+ name: "subdomain.status",
2089
+ operatorNotes: "PLANNED. Not yet wired; the page already lives at www.fluncle.com/status.",
2090
+ subdomain: "status.fluncle.com",
2091
+ url: "https://status.fluncle.com",
2092
+ weight: "tertiary"
2093
+ },
2094
+ {
2095
+ exposedContent: [
2096
+ "the Tor onion mirror of www.fluncle.com — the archive, API, RSS, and MCP over Tor"
2097
+ ],
2098
+ kind: "subdomain",
2099
+ name: "subdomain.onion",
2100
+ operatorNotes: "An onionspray mirror. Advertised via Tor Browser's Onion-Location pill. Probed on /status as service `onion`. Runbook: docs/tor.md.",
2101
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2102
+ subdomain: "p53pc2uzfu2tnih4cd6wd42ok6zup2uttj6xdmjdccy5kqo33fyppkqd.onion",
2103
+ url: "http://p53pc2uzfu2tnih4cd6wd42ok6zup2uttj6xdmjdccy5kqo33fyppkqd.onion",
2104
+ weight: "tertiary"
2105
+ },
2106
+ {
2107
+ apiFormat: "application/json",
2108
+ discoveryUrl: `${SITE}/api/v1/openapi.json`,
2109
+ exposedContent: ["the archive as JSON, cursor-paginated (limit max 48, cursor)"],
2110
+ kind: "api",
2111
+ name: "api.tracks",
2112
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2113
+ route: "/api/v1/tracks",
2114
+ url: `${SITE}/api/v1/tracks`,
2115
+ weight: "primary"
2116
+ },
2117
+ {
2118
+ apiFormat: "application/json",
2119
+ discoveryUrl: `${SITE}/api/v1/openapi.json`,
2120
+ exposedContent: ["one finding or mixtape by Spotify id or Log ID"],
2121
+ kind: "api",
2122
+ name: "api.track",
2123
+ route: "/api/v1/tracks/:idOrLogId",
2124
+ url: `${SITE}/api/v1/tracks/:idOrLogId`,
2125
+ weight: "secondary"
2126
+ },
2127
+ {
2128
+ apiFormat: "application/json",
2129
+ discoveryUrl: `${SITE}/api/v1/openapi.json`,
2130
+ exposedContent: ["one finding at random"],
2131
+ kind: "api",
2132
+ name: "api.tracks.random",
2133
+ route: "/api/v1/tracks/random",
2134
+ url: `${SITE}/api/v1/tracks/random`,
2135
+ weight: "secondary"
2136
+ },
2137
+ {
2138
+ apiFormat: "application/json",
2139
+ discoveryUrl: `${SITE}/api/v1/openapi.json`,
2140
+ exposedContent: ["published mixtapes as JSON"],
2141
+ kind: "api",
2142
+ name: "api.mixtapes",
2143
+ route: "/api/v1/mixtapes",
2144
+ url: `${SITE}/api/v1/mixtapes`,
2145
+ weight: "secondary"
2146
+ },
2147
+ {
2148
+ apiFormat: "application/json",
2149
+ discoveryUrl: `${SITE}/api/v1/openapi.json`,
2150
+ exposedContent: ["Spotify search candidates for submitting a track"],
2151
+ kind: "api",
2152
+ name: "api.search",
2153
+ route: "/api/v1/search",
2154
+ url: `${SITE}/api/v1/search`,
2155
+ weight: "secondary"
2156
+ },
2157
+ {
2158
+ apiFormat: "application/json",
2159
+ discoveryUrl: `${SITE}/api/v1/openapi.json`,
2160
+ exposedContent: ["submit a track for review (POST)"],
2161
+ kind: "api",
2162
+ name: "api.submissions",
2163
+ route: "/api/v1/submissions",
2164
+ url: `${SITE}/api/v1/submissions`,
2165
+ weight: "secondary"
2166
+ },
2167
+ {
2168
+ apiFormat: "application/json",
2169
+ discoveryUrl: `${SITE}/api/v1/openapi.json`,
2170
+ exposedContent: ["subscribe to the newsletter (POST); the editions archive"],
2171
+ kind: "api",
2172
+ name: "api.newsletter",
2173
+ route: "/api/v1/newsletter",
2174
+ url: `${SITE}/api/v1/newsletter`,
2175
+ weight: "secondary"
2176
+ },
2177
+ {
2178
+ apiFormat: "application/json",
2179
+ discoveryUrl: `${SITE}/api/v1/openapi.json`,
2180
+ exposedContent: ["the Stories payload as JSON"],
2181
+ kind: "api",
2182
+ name: "api.stories",
2183
+ route: "/api/v1/stories",
2184
+ url: `${SITE}/api/v1/stories`,
2185
+ weight: "tertiary"
2186
+ },
2187
+ {
2188
+ apiFormat: "application/json",
2189
+ discoveryUrl: `${SITE}/api/v1/openapi.json`,
2190
+ exposedContent: ["the radio shared-clock now-playing slot"],
2191
+ kind: "api",
2192
+ name: "api.radio.now-playing",
2193
+ route: "/api/v1/radio/now-playing",
2194
+ url: `${SITE}/api/v1/radio/now-playing`,
2195
+ weight: "tertiary"
2196
+ },
2197
+ {
2198
+ apiFormat: "application/json",
2199
+ exposedContent: ["the liveness probe — the canonical web health check"],
2200
+ kind: "api",
2201
+ name: "api.health",
2202
+ operatorNotes: "Linked as the `status` relation from /.well-known/api-catalog. Service `web` on /status.",
2203
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2204
+ route: "/api/health",
2205
+ url: `${SITE}/api/health`,
2206
+ weight: "tertiary"
2207
+ },
2208
+ {
2209
+ apiFormat: "application/rss+xml",
2210
+ exposedContent: ["the 25 most recent findings and mixtapes"],
2211
+ kind: "feed",
2212
+ name: "feed.rss",
2213
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2214
+ route: "/rss.xml",
2215
+ url: `${SITE}/rss.xml`,
2216
+ weight: "primary"
2217
+ },
2218
+ {
2219
+ apiFormat: "application/atom+xml",
2220
+ exposedContent: ["the recent findings and mixtapes as an Atom feed"],
2221
+ kind: "feed",
2222
+ name: "feed.atom",
2223
+ route: "/atom.xml",
2224
+ url: `${SITE}/atom.xml`,
2225
+ weight: "secondary"
2226
+ },
2227
+ {
2228
+ apiFormat: "application/feed+json",
2229
+ exposedContent: ["the recent findings and mixtapes as a JSON Feed"],
2230
+ kind: "feed",
2231
+ name: "feed.json",
2232
+ route: "/feed.json",
2233
+ url: `${SITE}/feed.json`,
2234
+ weight: "secondary"
2235
+ },
2236
+ {
2237
+ apiFormat: "application/rss+xml",
2238
+ exposedContent: ["the mixtapes as a podcast feed (episode audio on found.fluncle.com)"],
2239
+ kind: "feed",
2240
+ name: "feed.podcast",
2241
+ route: "/podcast.xml",
2242
+ url: `${SITE}/podcast.xml`,
2243
+ weight: "secondary"
2244
+ },
2245
+ {
2246
+ apiFormat: "text/calendar",
2247
+ exposedContent: ["planned events as an iCalendar feed (Twitch-linked VEVENTs)"],
2248
+ kind: "feed",
2249
+ name: "feed.calendar",
2250
+ route: "/calendar.ics",
2251
+ url: `${SITE}/calendar.ics`,
2252
+ weight: "tertiary"
2253
+ },
2254
+ {
2255
+ apiFormat: "application/xml",
2256
+ exposedContent: ["the XML sitemap of every public page"],
2257
+ kind: "discovery",
2258
+ name: "discovery.sitemap",
2259
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2260
+ route: "/sitemap.xml",
2261
+ url: `${SITE}/sitemap.xml`,
2262
+ weight: "secondary"
2263
+ },
2264
+ {
2265
+ apiFormat: "text/plain",
2266
+ exposedContent: [
2267
+ "the crawl policy + Content-Signal (search/AI-input/AI-train all yes) + sitemap link"
2268
+ ],
2269
+ kind: "discovery",
2270
+ name: "discovery.robots",
2271
+ operatorNotes: "Cloudflare's managed robots.txt can prepend directives; this file is the origin's intent.",
2272
+ route: "/robots.txt",
2273
+ url: `${SITE}/robots.txt`,
2274
+ weight: "tertiary"
2275
+ },
2276
+ {
2277
+ apiFormat: "text/markdown",
2278
+ discoveryUrl: `${SITE}/.well-known/api-catalog`,
2279
+ exposedContent: ["the plain-language map of the Galaxy for LLMs"],
2280
+ kind: "discovery",
2281
+ name: "discovery.llms",
2282
+ route: "/llms.txt",
2283
+ url: `${SITE}/llms.txt`,
2284
+ weight: "primary"
2285
+ },
2286
+ {
2287
+ apiFormat: "text/markdown",
2288
+ exposedContent: ["the entire archive in one ingestible markdown document, every finding"],
2289
+ kind: "discovery",
2290
+ name: "discovery.llms-full",
2291
+ route: "/llms-full.txt",
2292
+ url: `${SITE}/llms-full.txt`,
2293
+ weight: "secondary"
2294
+ },
2295
+ {
2296
+ apiFormat: "application/openapi+json",
2297
+ exposedContent: ["the public API as an OpenAPI 3.1 document (admin paths excluded)"],
2298
+ kind: "discovery",
2299
+ name: "discovery.openapi",
2300
+ route: "/api/v1/openapi.json",
2301
+ url: `${SITE}/api/v1/openapi.json`,
2302
+ weight: "secondary"
2303
+ },
2304
+ {
2305
+ apiFormat: "application/json",
2306
+ exposedContent: ["the SEP-2127 discovery card for the MCP endpoint"],
2307
+ kind: "discovery",
2308
+ name: "discovery.mcp-server-card",
2309
+ route: "/.well-known/mcp/server-card.json",
2310
+ url: `${SITE}/.well-known/mcp/server-card.json`,
2311
+ weight: "tertiary"
2312
+ },
2313
+ {
2314
+ apiFormat: "application/linkset+json",
2315
+ exposedContent: ["the RFC 9727 linkset pointing at the machine-readable surfaces"],
2316
+ kind: "discovery",
2317
+ name: "discovery.api-catalog",
2318
+ route: "/.well-known/api-catalog",
2319
+ url: `${SITE}/.well-known/api-catalog`,
2320
+ weight: "tertiary"
2321
+ },
2322
+ {
2323
+ apiFormat: "application/json",
2324
+ exposedContent: ["the fluncle-api agent skill index (with the SKILL.md digest)"],
2325
+ kind: "discovery",
2326
+ name: "discovery.agent-skills",
2327
+ route: "/.well-known/agent-skills/index.json",
2328
+ url: `${SITE}/.well-known/agent-skills/index.json`,
2329
+ weight: "tertiary"
2330
+ },
2331
+ {
2332
+ apiFormat: "application/json",
2333
+ discoveryUrl: `${SITE}/.well-known/mcp/server-card.json`,
2334
+ exposedContent: [
2335
+ "the archive as MCP tools (Streamable HTTP, no auth): list_tracks, get_random_track, search_tracks, submit_track, subscribe_newsletter"
2336
+ ],
2337
+ kind: "mcp",
2338
+ name: "mcp.server",
2339
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2340
+ route: "/mcp",
2341
+ url: `${SITE}/mcp`,
2342
+ weight: "primary"
2343
+ },
2344
+ {
2345
+ command: "dig TXT 004.7.2I.dig.fluncle.com",
2346
+ exposedContent: [
2347
+ "a finding's coordinate as a TXT record (e.g. 004.7.2I.dig.fluncle.com)",
2348
+ "the special labels `random` and `latest`"
2349
+ ],
2350
+ kind: "dns",
2351
+ name: "dns.zone",
2352
+ operatorNotes: "apps/dns — a tiny authoritative server for dig.fluncle.com, reads the public API and renders a finding as TXT. Not recursive (out-of-zone is REFUSED). Probed on /status as service `dns`.",
2353
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2354
+ subdomain: "dig.fluncle.com",
2355
+ weight: "tertiary"
2356
+ },
2357
+ {
2358
+ command: "ssh rave.fluncle.com",
2359
+ exposedContent: [
2360
+ "the rave terminal TUI: Enter the Galaxy, Latest bangers, Mixtape archive, Submit, Subscribe, Install CLI, About",
2361
+ "deep-register one-shots: `ssh rave.fluncle.com latest|random`"
2362
+ ],
2363
+ kind: "ssh",
2364
+ name: "ssh.rave",
2365
+ operatorNotes: "apps/ssh (Go Wish/Bubble Tea). Probed on /status as service `ssh`.",
2366
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2367
+ subdomain: "rave.fluncle.com",
2368
+ weight: "primary"
2369
+ },
2370
+ {
2371
+ command: "fluncle recent",
2372
+ exposedContent: ["the latest bangers, newest first (alias `list`)"],
2373
+ kind: "cli",
2374
+ name: "cli.recent",
2375
+ weight: "primary"
2376
+ },
2377
+ {
2378
+ command: "fluncle mixtapes",
2379
+ exposedContent: ["Fluncle's checkpoint sets"],
2380
+ kind: "cli",
2381
+ name: "cli.mixtapes",
2382
+ weight: "secondary"
2383
+ },
2384
+ {
2385
+ command: "fluncle open",
2386
+ exposedContent: ["pick a track, open it in Spotify"],
2387
+ kind: "cli",
2388
+ name: "cli.open",
2389
+ weight: "secondary"
2390
+ },
2391
+ {
2392
+ command: "fluncle random",
2393
+ exposedContent: ["the archive throws one back"],
2394
+ kind: "cli",
2395
+ name: "cli.random",
2396
+ weight: "secondary"
2397
+ },
2398
+ {
2399
+ command: "fluncle subscribe",
2400
+ exposedContent: ["subscribe to the Friday newsletter"],
2401
+ kind: "cli",
2402
+ name: "cli.subscribe",
2403
+ weight: "secondary"
2404
+ },
2405
+ {
2406
+ command: "fluncle submit",
2407
+ exposedContent: ["send a track for review"],
2408
+ kind: "cli",
2409
+ name: "cli.submit",
2410
+ weight: "secondary"
2411
+ },
2412
+ {
2413
+ command: "fluncle tracks get",
2414
+ exposedContent: ["look up one finding by id or Log ID (group alias `track`)"],
2415
+ kind: "cli",
2416
+ name: "cli.tracks-get",
2417
+ weight: "tertiary"
2418
+ },
2419
+ {
2420
+ command: "fluncle about",
2421
+ exposedContent: ["Fluncle, and where to find him"],
2422
+ kind: "cli",
2423
+ name: "cli.about",
2424
+ weight: "tertiary"
2425
+ },
2426
+ {
2427
+ command: "fluncle version",
2428
+ exposedContent: ["print or check the version (--check hits the latest GitHub release)"],
2429
+ kind: "cli",
2430
+ name: "cli.version",
2431
+ weight: "tertiary"
2432
+ },
2433
+ {
2434
+ command: "fluncle admin",
2435
+ exposedContent: [
2436
+ "the operator/agent command group (hidden): tracks publish|update|enrich|video|draft|social|preview|observe|context|note, mixtapes create|update|members|publish|delete, newsletter draft|update|send|list, backfills, auth"
2437
+ ],
2438
+ kind: "cli",
2439
+ name: "cli.admin",
2440
+ operatorNotes: "Authenticated admin/agent tier. The enrichment crons drive a subset of these.",
2441
+ weight: "hidden"
2442
+ },
2443
+ {
2444
+ command: "fluncle admin tracks enrich --queue",
2445
+ exposedContent: [
2446
+ "BPM / key / spectral analysis on the box, write-back (--no-agent, on-box DSP)"
2447
+ ],
2448
+ kind: "cron",
2449
+ name: "cron.enrich",
2450
+ operatorNotes: "every 5m. Pure compute, zero LLM tokens. Source: docs/agents/hermes/scripts/enrich-sweep.*",
2451
+ probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-enrich", kind: "cron" },
2452
+ weight: "hidden"
2453
+ },
2454
+ {
2455
+ command: "fluncle admin tracks context --queue",
2456
+ exposedContent: [
2457
+ "Firecrawl facts → distilled context_note + a Texture: line (Worker-side Haiku)"
2458
+ ],
2459
+ kind: "cron",
2460
+ name: "cron.context-note",
2461
+ operatorNotes: "every 5m. --no-agent trigger; the Worker does the Firecrawl + Haiku distill. Zero on-box tokens.",
2462
+ probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-context-note", kind: "cron" },
2463
+ weight: "hidden"
2464
+ },
2465
+ {
2466
+ command: "fluncle admin tracks note --queue",
2467
+ exposedContent: [
2468
+ "auto-author the editorial /log note, fill-empty-only (hybrid: one claude -p call)"
2469
+ ],
2470
+ kind: "cron",
2471
+ name: "cron.note",
2472
+ operatorNotes: "every 10m. Hybrid --no-agent; one claude -p authors the line. Never clobbers an operator note.",
2473
+ probeConfig: { cadenceMs: 10 * MINUTE_MS, cronName: "fluncle-note", kind: "cron" },
2474
+ weight: "hidden"
2475
+ },
2476
+ {
2477
+ command: "fluncle admin tracks observe --queue",
2478
+ exposedContent: [
2479
+ "author the recovered-audio script → Worker Cartesia render (hybrid: one claude -p call)"
2480
+ ],
2481
+ kind: "cron",
2482
+ name: "cron.observation",
2483
+ operatorNotes: "every 60m. Hybrid --no-agent; one claude -p authors the script, the Worker voice-gates + renders.",
2484
+ probeConfig: { cadenceMs: 60 * MINUTE_MS, cronName: "fluncle-observation", kind: "cron" },
2485
+ weight: "hidden"
2486
+ },
2487
+ {
2488
+ command: "fluncle admin backfills discogs && fluncle admin backfills lastfm",
2489
+ exposedContent: ["Discogs id + Last.fm love catalogue repair (--no-agent, Worker HTTP)"],
2490
+ kind: "cron",
2491
+ name: "cron.backfill",
2492
+ operatorNotes: "every 30m. Pure HTTP driving, zero LLM tokens. Agent tier.",
2493
+ probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-backfill", kind: "cron" },
2494
+ weight: "hidden"
2495
+ },
2496
+ {
2497
+ command: "fluncle admin tracks queue",
2498
+ exposedContent: [
2499
+ "wake the rave-03 render box → render + ship one finding's video → park (conductor)"
2500
+ ],
2501
+ kind: "cron",
2502
+ name: "cron.render",
2503
+ 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 `render-box`.",
2504
+ probeConfig: { cadenceMs: 60 * MINUTE_MS, cronName: "fluncle-render", kind: "cron" },
2505
+ weight: "hidden"
2506
+ },
2507
+ {
2508
+ exposedContent: [
2509
+ "probe each service → Discord-ping on a status flip → POST the /status snapshot (--no-agent)"
2510
+ ],
2511
+ kind: "cron",
2512
+ name: "cron.healthcheck",
2513
+ operatorNotes: "every 10m. Pure probing, zero LLM tokens. POSTs to the agent-tier record_health op that /status reads.",
2514
+ probeConfig: { cadenceMs: 10 * MINUTE_MS, cronName: "fluncle-healthcheck", kind: "cron" },
2515
+ weight: "hidden"
2516
+ },
2517
+ {
2518
+ command: "fluncle admin newsletter draft",
2519
+ exposedContent: [
2520
+ "draft + persist the weekly edition, then offer the operator a Discord Send button (the only agent cron)"
2521
+ ],
2522
+ kind: "cron",
2523
+ name: "cron.newsletter",
2524
+ operatorNotes: "Fri 15:00 Amsterdam (cron `0 15 * * 5`, box pinned Europe/Amsterdam for DST). The only full-agent cron. Send is operator-gated (agent token 403s send_edition). Source: docs/agents/hermes/cron/jobs.json.",
2525
+ probeConfig: {
2526
+ cadenceMs: 7 * 24 * 60 * MINUTE_MS,
2527
+ cronName: "fluncle-newsletter",
2528
+ kind: "cron"
2529
+ },
2530
+ weight: "secondary"
2531
+ }
2532
+ ];
2533
+ });
2534
+
2535
+ // src/commands/status.ts
2536
+ var exports_status = {};
2537
+ __export(exports_status, {
2538
+ statusLines: () => statusLines,
2539
+ statusCommand: () => statusCommand
2540
+ });
2541
+ async function statusCommand() {
2542
+ return publicApiGet("/api/status");
2543
+ }
2544
+ function ago(seconds) {
2545
+ if (seconds < 60) {
2546
+ return `${seconds}s ago`;
2547
+ }
2548
+ if (seconds < 3600) {
2549
+ return `${Math.round(seconds / 60)}m ago`;
2550
+ }
2551
+ if (seconds < 86400) {
2552
+ return `${Math.round(seconds / 3600)}h ago`;
2553
+ }
2554
+ return `${Math.round(seconds / 86400)}d ago`;
2555
+ }
2556
+ function statusLines(snapshot) {
2557
+ const { secondsSinceFreshestReport, services } = snapshot;
2558
+ if (services.length === 0) {
2559
+ return ["No service reports yet. The healthcheck hasn't called in."];
2560
+ }
2561
+ const down = services.filter((service) => service.status === "down").length;
2562
+ const degraded = services.filter((service) => service.status === "degraded").length;
2563
+ const headline = down > 0 ? `${down} service${down === 1 ? "" : "s"} down.` : degraded > 0 ? `${degraded} service${degraded === 1 ? "" : "s"} limping. The rest holds.` : "All services up. The Galaxy holds.";
2564
+ const nameWidth = services.reduce((width, service) => Math.max(width, service.service.length), 0);
2565
+ const stateWidth = services.reduce((width, service) => Math.max(width, service.status.length), 0);
2566
+ const lines = [headline, ""];
2567
+ for (const service of services) {
2568
+ const note = service.message ?? serviceLabels.get(service.service) ?? "";
2569
+ const row = [
2570
+ statusMarks[service.status],
2571
+ service.service.padEnd(nameWidth),
2572
+ service.status.padEnd(stateWidth),
2573
+ note
2574
+ ].join(" ").trimEnd();
2575
+ lines.push(row);
2576
+ }
2577
+ if (secondsSinceFreshestReport !== null) {
2578
+ lines.push("", `Last checked ${ago(secondsSinceFreshestReport)}.`);
2579
+ }
2580
+ return lines;
2581
+ }
2582
+ var serviceLabels, statusMarks;
2583
+ var init_status = __esm(() => {
2584
+ init_src();
2585
+ init_api();
2586
+ serviceLabels = (() => {
2587
+ const labels = new Map;
2588
+ for (const surface of SURFACES) {
2589
+ const match = surface.operatorNotes?.match(/service `([a-z0-9-]+)`/);
2590
+ const id = match?.[1];
2591
+ const label = surface.exposedContent[0];
2592
+ if (id !== undefined && label !== undefined && !labels.has(id)) {
2593
+ labels.set(id, label);
2594
+ }
2595
+ }
2596
+ return labels;
2597
+ })();
2598
+ statusMarks = {
2599
+ degraded: "~",
2600
+ down: "x",
2601
+ ok: "+"
2602
+ };
1918
2603
  });
1919
2604
 
1920
2605
  // src/commands/track.ts
@@ -2071,7 +2756,6 @@ var init_track = __esm(() => {
2071
2756
  VIDEO_FIELDS = [
2072
2757
  { field: "footage", option: "footage" },
2073
2758
  { field: "footage-social", option: "footageSocial" },
2074
- { field: "footage-silent", option: "footageSilent" },
2075
2759
  { field: "poster", option: "poster" },
2076
2760
  { field: "cover", option: "cover" },
2077
2761
  { field: "note", option: "note" },
@@ -5432,6 +6116,10 @@ function addMetaCommands(program2) {
5432
6116
  json: options.json
5433
6117
  });
5434
6118
  });
6119
+ program2.command("status").description("How Fluncle's services are holding up").option("--json", "Print JSON", false).action(async (options) => {
6120
+ const { statusCommand: statusCommand2 } = await Promise.resolve().then(() => (init_status(), exports_status));
6121
+ await runStatus(options, statusCommand2);
6122
+ });
5435
6123
  }
5436
6124
  function addTrackCommands(program2) {
5437
6125
  const tracks = configureCommand(program2.command("tracks", { hidden: true }).alias("track").description("Public track lookups"));
@@ -5490,7 +6178,7 @@ function addAdminCommands(program2) {
5490
6178
  const { trackUpdateCommand: trackUpdateCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
5491
6179
  await runTrackUpdate(trackId, options, trackUpdateCommand2);
5492
6180
  });
5493
- adminTrack.command("video").description("Upload a track's video bundle to R2 and link it").argument("[idOrLogId]").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-silent <file>", "Silent video footage (retiring)").option("--footage-social <file>", "Portrait social cut (baked text)").option("--json", "Print JSON", false).option("--model <model>", "Authoring AI model (<provider>/<model>)").option("--note <file>", "Note file").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").allowExcessArguments().action(async (idOrLogId, options) => {
6181
+ adminTrack.command("video").description("Upload a track's video bundle to R2 and link it").argument("[idOrLogId]").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-social <file>", "Portrait social cut (baked text)").option("--json", "Print JSON", false).option("--model <model>", "Authoring AI model (<provider>/<model>)").option("--note <file>", "Note file").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").allowExcessArguments().action(async (idOrLogId, options) => {
5494
6182
  const { trackVideoCommand: trackVideoCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
5495
6183
  await runTrackVideo(idOrLogId, options, trackVideoCommand2);
5496
6184
  });
@@ -5862,7 +6550,7 @@ async function runBackfillDiscogs(options, backfillDiscogsCommand2) {
5862
6550
  }
5863
6551
  async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
5864
6552
  if (!idOrLogId) {
5865
- throw new Error("Missing id. Usage: fluncle admin tracks video <track_id|log_id> (--dir <dir> | --footage <file> [--footage-social <file>] [--footage-silent <file>] [--poster <file>] [--cover <file>] [--note <file>] [--composition <file>] [--props <file>] [--render <file>])");
6553
+ throw new Error("Missing id. Usage: fluncle admin tracks video <track_id|log_id> (--dir <dir> | --footage <file> [--footage-social <file>] [--poster <file>] [--cover <file>] [--note <file>] [--composition <file>] [--props <file>] [--render <file>])");
5866
6554
  }
5867
6555
  const dir = options.dir ? path2.resolve(process.cwd(), options.dir) : undefined;
5868
6556
  const fromDir = (name) => {
@@ -5882,7 +6570,6 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
5882
6570
  composition: resolveFile(options.composition, "composition.tsx"),
5883
6571
  cover: resolveFile(options.cover, "cover.jpg"),
5884
6572
  footage: resolveFile(options.footage, "footage.mp4"),
5885
- footageSilent: resolveFile(options.footageSilent, "footage-silent.mp4"),
5886
6573
  footageSocial: resolveFile(options.footageSocial, "footage.social.mp4"),
5887
6574
  model: options.model,
5888
6575
  note: resolveFile(options.note, "note.txt"),
@@ -6448,6 +7135,19 @@ async function runRandom(options, randomCommand2) {
6448
7135
  console.log(`Log: ${track.logPageUrl}`);
6449
7136
  }
6450
7137
  }
7138
+ async function runStatus(options, statusCommand2) {
7139
+ const snapshot = await statusCommand2();
7140
+ if (options.json) {
7141
+ printJson({
7142
+ ok: true,
7143
+ ...snapshot
7144
+ });
7145
+ return;
7146
+ }
7147
+ const { statusLines: statusLines2 } = await Promise.resolve().then(() => (init_status(), exports_status));
7148
+ console.log(statusLines2(snapshot).join(`
7149
+ `));
7150
+ }
6451
7151
  function rejectUnexpectedPositionals(positionals) {
6452
7152
  if (positionals.length === 0) {
6453
7153
  return;
@@ -6586,7 +7286,6 @@ var stringOptions = new Set([
6586
7286
  "--features",
6587
7287
  "--file",
6588
7288
  "--footage",
6589
- "--footage-silent",
6590
7289
  "--footage-social",
6591
7290
  "--from",
6592
7291
  "--key",
@@ -6622,6 +7321,7 @@ Share:
6622
7321
 
6623
7322
  Meta:
6624
7323
  fluncle about Fluncle, and where to find him
7324
+ fluncle status [--json] How Fluncle's services are holding up
6625
7325
  fluncle version [--check] [--json] Print or check the version
6626
7326
 
6627
7327
  Fluncle elsewhere:
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.61.0"
34
+ "version": "0.63.0"
35
35
  }