import { mkdirSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import type { SidecarCommand, SidecarEvent } from '../protocol'; import { createSidecarConnector } from '../connect'; import { streamAudioFile } from './stream-audio'; import { reindexNow, ensureIndexFresh, getIndexStatus, getManifest, albumVersion, metaFilePath, coverFilePath, discographyFilePath, posterFilePath, onIndexProgress, buildReport, } from './indexer'; const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data'); // The music sidecar (officer-music). Same philosophy as the other officer-* sidecars: a singleton // process that registers with the API server. It OWNS an audio-streaming HTTP server (all path // resolution + streaming + ffprobe duration happens here); the platform API is just a thin proxy that // authenticates and forwards to us. The server listens on a random loopback port, reported to the API // on connect so it can route `/api/music/*` here. // // ───────────────────────────────────────────────────────────────────────────────────────────────── // HTTP CONTRACT — the full `/api/music/*` surface (this fetch handler is the source of truth; the // platform side is an opaque catch-all proxy). All routes are reached as `/api/music/`, authed // upstream by userMiddleware (Bearer header or `?token=` for media). Data shapes are the exported // `IndexStatus` / `IndexReport` / `IndexMeta` types in indexer.ts. // // GET /stream?path= audio with Range→206 (Content-Range/Length/Accept-Ranges) // + `X-Audio-Duration` (seconds, ffprobe). 400/404/416. // GET /manifest ensures a fresh index (debounced rebuild) then returns // { version, generatedAt, albums: { "": { v, cover, tracks, disco? } } } // GET /meta?path= album meta.json (IndexMeta). ETag: ; If-None-Match → 304. // GET /cover?path= compressed cover.jpg. ETag: ; If-None-Match → 304. // GET /poster?path=&file=