From e6e163a82746053bf2f60938ee4af265f1dc4b9e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 08:43:22 +0000 Subject: [PATCH] Version Packages --- .../album-artist-browsing-capability.md | 10 - .changeset/album-artist-path-template.md | 12 - .changeset/audio-normalization-capability.md | 8 - .changeset/audio-normalization-refactoring.md | 29 - .changeset/bundling-pipeline-externals.md | 10 - .changeset/clean-artists-device-set.md | 7 - .changeset/cli-error-shape-collection.md | 24 - .changeset/cli-error-shape-unified.md | 74 --- ...codec-vorbis-rename-and-container-types.md | 19 - .changeset/completions-namespace-isolation.md | 7 - .changeset/configurable-codec-preferences.md | 29 - .changeset/convergent-track-metadata.md | 22 - .changeset/core-capability-adapter.md | 11 - .changeset/device-add-slick-flow.md | 21 - .changeset/device-aware-doctor.md | 6 - .changeset/device-capability-finalisation.md | 29 - .../device-commands-readiness-output.md | 11 - .changeset/device-list-resolved-config.md | 15 - .changeset/device-output-refactor.md | 11 - .changeset/device-readiness-diagnostics.md | 11 - .../devices-extraction-and-providers.md | 16 - ...doctor-artwork-reset-and-rebuild-rename.md | 8 - .../doctor-no-system-and-sysinfo-redesign.md | 8 - .changeset/echo-mini-e2e-bugfixes.md | 25 - .changeset/eject-device-label.md | 6 - .changeset/fields-validation-error.md | 5 - .changeset/fix-device-model-data.md | 5 - .../fix-process-exit-stdout-truncation.md | 5 - .changeset/fix-subsonic-source-display.md | 5 - .changeset/fix-sudo-config-path.md | 5 - .changeset/identity-cascade-compose.md | 18 - .changeset/ipod-firmware-scsi-delivery.md | 16 - .changeset/ipod-model-identity.md | 17 - .changeset/libgpod-node-device-class.md | 10 - .changeset/mass-storage-delete-and-orphans.md | 16 - .changeset/mass-storage-device-support.md | 61 -- .changeset/multi-ipod-daemon.md | 5 - .changeset/ogg-artwork-embedding.md | 15 - .changeset/path-template-cli-exposure.md | 14 - .changeset/replaygain-mass-storage-tags.md | 18 - .changeset/sync-engine-refactor.md | 10 - .changeset/sync-tag-adapter-refactor.md | 5 - .changeset/sysinfo-extended-auto-identify.md | 35 -- .changeset/sysinfo-extended-scsi-fallback.md | 21 - .../test-fixtures-mini-tracks-library.md | 14 - .changeset/track-metadata-polish.md | 15 - .changeset/transfer-mode.md | 33 -- .changeset/tty-aware-interactive-output.md | 5 - .../usb-enumeration-classify-refactor.md | 22 - .changeset/usb-error-reporting.md | 7 - .changeset/usb-inquiry-consolidation.md | 15 - .changeset/usb-via-npm-package.md | 19 - bun.lock | 26 +- packages/compatibility/CHANGELOG.md | 7 + packages/compatibility/package.json | 2 +- packages/device-testing/CHANGELOG.md | 9 + packages/device-testing/package.json | 2 +- packages/device-types/CHANGELOG.md | 48 ++ packages/device-types/package.json | 2 +- packages/devices-ipod/CHANGELOG.md | 80 +++ packages/devices-ipod/package.json | 2 +- packages/devices-mass-storage/CHANGELOG.md | 77 +++ packages/devices-mass-storage/package.json | 2 +- packages/docs-site/CHANGELOG.md | 7 + packages/docs-site/package.json | 2 +- packages/ipod-firmware/CHANGELOG.md | 95 +++ packages/ipod-firmware/package.json | 2 +- packages/libgpod-node/CHANGELOG.md | 54 ++ packages/libgpod-node/package.json | 2 +- packages/podkit-cli/CHANGELOG.md | 561 ++++++++++++++++++ packages/podkit-cli/package.json | 2 +- packages/podkit-core/CHANGELOG.md | 461 ++++++++++++++ packages/podkit-core/package.json | 2 +- packages/podkit-daemon/CHANGELOG.md | 68 +++ packages/podkit-daemon/package.json | 2 +- packages/podkit-docker/CHANGELOG.md | 8 + packages/podkit-docker/package.json | 2 +- packages/test-fixtures/CHANGELOG.md | 15 + packages/test-fixtures/package.json | 2 +- 79 files changed, 1516 insertions(+), 871 deletions(-) delete mode 100644 .changeset/album-artist-browsing-capability.md delete mode 100644 .changeset/album-artist-path-template.md delete mode 100644 .changeset/audio-normalization-capability.md delete mode 100644 .changeset/audio-normalization-refactoring.md delete mode 100644 .changeset/bundling-pipeline-externals.md delete mode 100644 .changeset/clean-artists-device-set.md delete mode 100644 .changeset/cli-error-shape-collection.md delete mode 100644 .changeset/cli-error-shape-unified.md delete mode 100644 .changeset/codec-vorbis-rename-and-container-types.md delete mode 100644 .changeset/completions-namespace-isolation.md delete mode 100644 .changeset/configurable-codec-preferences.md delete mode 100644 .changeset/convergent-track-metadata.md delete mode 100644 .changeset/core-capability-adapter.md delete mode 100644 .changeset/device-add-slick-flow.md delete mode 100644 .changeset/device-aware-doctor.md delete mode 100644 .changeset/device-capability-finalisation.md delete mode 100644 .changeset/device-commands-readiness-output.md delete mode 100644 .changeset/device-list-resolved-config.md delete mode 100644 .changeset/device-output-refactor.md delete mode 100644 .changeset/device-readiness-diagnostics.md delete mode 100644 .changeset/devices-extraction-and-providers.md delete mode 100644 .changeset/doctor-artwork-reset-and-rebuild-rename.md delete mode 100644 .changeset/doctor-no-system-and-sysinfo-redesign.md delete mode 100644 .changeset/echo-mini-e2e-bugfixes.md delete mode 100644 .changeset/eject-device-label.md delete mode 100644 .changeset/fields-validation-error.md delete mode 100644 .changeset/fix-device-model-data.md delete mode 100644 .changeset/fix-process-exit-stdout-truncation.md delete mode 100644 .changeset/fix-subsonic-source-display.md delete mode 100644 .changeset/fix-sudo-config-path.md delete mode 100644 .changeset/identity-cascade-compose.md delete mode 100644 .changeset/ipod-firmware-scsi-delivery.md delete mode 100644 .changeset/ipod-model-identity.md delete mode 100644 .changeset/libgpod-node-device-class.md delete mode 100644 .changeset/mass-storage-delete-and-orphans.md delete mode 100644 .changeset/mass-storage-device-support.md delete mode 100644 .changeset/multi-ipod-daemon.md delete mode 100644 .changeset/ogg-artwork-embedding.md delete mode 100644 .changeset/path-template-cli-exposure.md delete mode 100644 .changeset/replaygain-mass-storage-tags.md delete mode 100644 .changeset/sync-engine-refactor.md delete mode 100644 .changeset/sync-tag-adapter-refactor.md delete mode 100644 .changeset/sysinfo-extended-auto-identify.md delete mode 100644 .changeset/sysinfo-extended-scsi-fallback.md delete mode 100644 .changeset/test-fixtures-mini-tracks-library.md delete mode 100644 .changeset/track-metadata-polish.md delete mode 100644 .changeset/transfer-mode.md delete mode 100644 .changeset/tty-aware-interactive-output.md delete mode 100644 .changeset/usb-enumeration-classify-refactor.md delete mode 100644 .changeset/usb-error-reporting.md delete mode 100644 .changeset/usb-inquiry-consolidation.md delete mode 100644 .changeset/usb-via-npm-package.md create mode 100644 packages/device-testing/CHANGELOG.md create mode 100644 packages/device-types/CHANGELOG.md create mode 100644 packages/devices-ipod/CHANGELOG.md create mode 100644 packages/devices-mass-storage/CHANGELOG.md create mode 100644 packages/ipod-firmware/CHANGELOG.md create mode 100644 packages/test-fixtures/CHANGELOG.md diff --git a/.changeset/album-artist-browsing-capability.md b/.changeset/album-artist-browsing-capability.md deleted file mode 100644 index 5bc1b087..00000000 --- a/.changeset/album-artist-browsing-capability.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"podkit": minor -"@podkit/core": minor ---- - -Add capability-gated clean artists transform - -Devices now declare whether they use Album Artist for browse navigation via `supportsAlbumArtistBrowsing`. When enabled globally, the `cleanArtists` transform is automatically suppressed on devices that support Album Artist browsing (Rockbox, Echo Mini, generic) and auto-applied on devices that don't (iPod). Per-device overrides still take priority. - -The dry-run summary shows when the transform is skipped (`Clean artists: skipped (device supports Album Artist browsing)`), and warns when it's force-enabled on a capable device. Both `sync --dry-run` and `device info` surface these in text and JSON output. diff --git a/.changeset/album-artist-path-template.md b/.changeset/album-artist-path-template.md deleted file mode 100644 index d2bb8b83..00000000 --- a/.changeset/album-artist-path-template.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"podkit": minor -"@podkit/core": minor ---- - -Fix mass-storage directory structure to use album artist instead of track artist, and add template-based path system with self-healing relocate. - -**Bug fix:** Mass-storage devices (Echo Mini, Rockbox) now use `albumArtist` for directory grouping, falling back to `artist` when absent. Previously, compilation/various-artist albums had their tracks scattered across separate artist directories instead of being grouped together under the album artist. - -**Path templates:** File paths are now generated from a configurable template string (`{albumArtist}/{album}/{trackNumber} - {title}{ext}` by default). This lays the groundwork for user-customisable folder structures in a future release. - -**Self-healing relocate:** When source metadata changes (e.g. album artist corrected) or the path template changes, the next sync detects the path mismatch and moves files to their correct location via `fs.rename()` — no re-copying of audio data. Relocate operations appear in dry-run output and are tracked as a new `relocate` operation type. diff --git a/.changeset/audio-normalization-capability.md b/.changeset/audio-normalization-capability.md deleted file mode 100644 index 6f488249..00000000 --- a/.changeset/audio-normalization-capability.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"podkit": minor -"@podkit/core": minor ---- - -Add audioNormalization device capability for device-appropriate Sound Check / ReplayGain handling - -Devices now declare their normalization support: 'soundcheck' (iPod), 'replaygain' (Rockbox), or 'none' (Echo Mini, generic). Devices with no normalization support skip soundcheck upgrade detection entirely, and the dry-run output hides or relabels the normalization line accordingly. Configurable via `audioNormalization` in device config. diff --git a/.changeset/audio-normalization-refactoring.md b/.changeset/audio-normalization-refactoring.md deleted file mode 100644 index f14870b5..00000000 --- a/.changeset/audio-normalization-refactoring.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -"@podkit/core": minor -"podkit": minor ---- - -Refactor audio normalization from iPod-centric Sound Check to a generic `AudioNormalization` type, and add ReplayGain album gain/peak support - -**Normalization refactoring:** - -- Introduce `AudioNormalization` type that preserves source format fidelity (ReplayGain dB, iTunNORM soundcheck integers) without unnecessary round-trip conversions -- Replace scattered `soundcheck`, `soundcheckSource`, `replayGainTrackGain`, `replayGainTrackPeak` fields on `CollectionTrack` with a single `normalization` field -- Replace `soundcheck`, `replayGainTrackGain`, `replayGainTrackPeak` fields on `DeviceTrackInput` with `normalization` -- Conversions now happen at device boundaries: iPod adapter reads soundcheck integers, mass-storage adapter reads dB values directly -- Upgrade detection compares in dB space with 0.1 dB epsilon tolerance, eliminating false positives from integer rounding -- Metadata update diffs show human-readable dB values (e.g., `normalization: -7.5 dB → -6.2 dB`) instead of opaque integers - -**Album gain/peak support (TASK-253):** - -- Extract `albumGain` and `albumPeak` from local file metadata and Subsonic API -- Write `REPLAYGAIN_ALBUM_GAIN` and `REPLAYGAIN_ALBUM_PEAK` via FFmpeg metadata flags during transcode -- Write album gain/peak via node-taglib-sharp tag writer for M4A files -- Thread album data through the full sync pipeline for mass-storage devices (Rockbox, etc.) - -**Breaking changes:** - -- `CollectionTrack` shape: four normalization fields replaced by single `normalization?: AudioNormalization` -- `SoundCheckSource` type removed, replaced by `NormalizationSource` -- Upgrade reason `'soundcheck-update'` renamed to `'normalization-update'` in JSON output -- `soundCheckTracks` stat renamed to `normalizedTracks` diff --git a/.changeset/bundling-pipeline-externals.md b/.changeset/bundling-pipeline-externals.md deleted file mode 100644 index c9f197f5..00000000 --- a/.changeset/bundling-pipeline-externals.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"podkit": patch -"@podkit/ipod-firmware": patch ---- - -Externalize `koffi` and `usb` from the published `bun build` bundles. Koffi loads its native binding via `eval('require')(filename)`; bun's bundler shims top-level `require` as `__require` (via `createRequire(import.meta.url)`) but does not inject `require` into eval'd literals, so the bundled CLI hit `ReferenceError: require is not defined` whenever the SCSI inquiry path was actually reached. The native loaders are now resolved at runtime via `node_modules`, which is also more correct for `usb` (whose `bun build`-time prebuild only matched the build host's platform). - -The standalone-binary path (`bun --compile` via `compile.sh`) is unchanged — it stages platform-specific `.node` files and uses static `require()` in `compile-entry.js`, which works correctly. A bug in `compile.sh`'s linux-arm64 branch is also fixed: the script previously constructed `linux-arm64/node.napi.${USB_VARIANT}.node` (where `USB_VARIANT` is `glibc` or `musl`) but the `usb` package only ships `linux-arm64/node.napi.armv8.node` — no glibc/musl split exists for arm64. The script now selects the armv8 prebuild unconditionally on arm64. - -`@podkit/ipod-firmware` is also externalized from the `@podkit/core` and `@podkit/devices-ipod` builds, so neither package's `dist/index.js` re-inlines firmware (and therefore koffi/usb imports). Bundle content-check tests under `packages/*/src/bundle.test.ts` assert that no `eval("require")` slips into any published bundle. diff --git a/.changeset/clean-artists-device-set.md b/.changeset/clean-artists-device-set.md deleted file mode 100644 index 3cb0c6e7..00000000 --- a/.changeset/clean-artists-device-set.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"podkit": patch ---- - -Add `--clean-artists` / `--no-clean-artists` / `--clear-clean-artists` options to `podkit device set` - -The clean artists transform can now be toggled per-device from the CLI instead of requiring manual config file edits. diff --git a/.changeset/cli-error-shape-collection.md b/.changeset/cli-error-shape-collection.md deleted file mode 100644 index c7c9819d..00000000 --- a/.changeset/cli-error-shape-collection.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -"podkit": minor ---- - -Unify JSON error shape for `device add` and `collection music`/`collection video` - -These commands now emit the same JSON error format on failure: - -```json -{ - "success": false, - "error": "", - "code": "", - "...details": "" -} -``` - -**Breaking for `collection music` / `collection video` JSON consumers.** The previous shape was `{ "error": true, "message": "..." }`. If you parse JSON output from these commands, update consumers to read `success === false` and `error` (instead of `error === true` and `message`). - -`device add` errors now also include a `code` field (additive, not breaking). - -Underneath: the runners (`runDeviceAdd`, `runCollectionMusic`, `runCollectionVideo`) throw a typed `CliError` and the action wrapper (`runAction`) translates it into structured output + exit code. Tests assert on the captured JSON instead of `process.exitCode` side-effects. - -Per CLI breaking-change convention this is a minor bump. Other commands still emit their existing shapes; that unification will land in a follow-up. diff --git a/.changeset/cli-error-shape-unified.md b/.changeset/cli-error-shape-unified.md deleted file mode 100644 index 0afeadad..00000000 --- a/.changeset/cli-error-shape-unified.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -"podkit": minor ---- - -Unify and harden CLI JSON error output across every command (ADR-015) - -## What changed - -Every CLI command now emits the same canonical JSON shape on failure. The shape, exit codes, and consumer ergonomics all changed in one breaking pass. - -### Canonical error shape - -```json -{ - "success": false, - "error": "", - "code": "", - "details": { "": "..." } -} -``` - -`code` is required and machine-readable (e.g. `MOUNT_REQUIRES_SUDO`, `FFMPEG_UNAVAILABLE`). `details` is **nested**, not spread at the top level — so command-specific extras can't accidentally collide with `success`/`error`/`code`. - -### Exit codes - -- `0` — success -- `1` — command error (any `CliError` thrown) -- `2` — ran cleanly but found problems (`doctor` reporting unhealthy device, `sync` reporting partial track failures). Carries a `status` field on the success-shape JSON: `'ok' | 'issues-found' | 'partial-failure'`. -- `130` — SIGINT (interrupted sync) - -### Per-command typed error codes - -Every command exports an exhaustive enum of its possible error codes: - -```ts -import { MountErrorCodes, type MountErrorCode } from 'podkit/commands/mount'; -// MountErrorCodes.DEVICE_NOT_RESOLVED, MountErrorCodes.MOUNT_REQUIRES_SUDO, etc. -``` - -A repo-wide barrel is at `packages/podkit-cli/src/commands/error-codes.ts` exporting `PodkitErrorCode` — the union of every code any podkit command may emit. - -### Discriminated `*Output` types - -Each command's output type is now a discriminated union: - -```ts -export type MountOutput = MountSuccess | MountErrorOutput; -``` - -Consumers narrow with `if (output.success) { ... }`. - -## Breaking for JSON consumers - -| Old shape | New shape | -|-----------|-----------| -| `{ error: true, message: "..." }` (collection music/video) | `{ success: false, error, code, details }` | -| `{ success: false, error: "..." }` (no code, top-level extras) | `{ success: false, error, code, details: {...} }` | -| Per-command extras at top level (e.g. `dryRun`, `device`) | Now nested under `details` | -| `process.exitCode === 1` for "found issues" | Now `2`; `1` is reserved for command errors | - -Update parsers to: - -1. Branch on `success === false`. -2. Read `code` for machine-readable tags. -3. Read `details.X` instead of `output.X` for command-specific extras. -4. Branch on exit code 2 for "ran cleanly with issues" (sync partial failure, doctor unhealthy). - -## New ergonomics - -`packages/podkit-cli/src/test-utils/cli-error.ts` and `packages/e2e-tests/src/helpers/cli-error.ts` export `expectCliError` for asserting on the canonical shape in one call. - -`OutputContext` now takes an optional `ExitCodeSink` (default: writes `process.exitCode`; tests use `BufferExitCodeSink` to avoid process-global mutation). - -Per CLI breaking-change convention this is a minor bump. diff --git a/.changeset/codec-vorbis-rename-and-container-types.md b/.changeset/codec-vorbis-rename-and-container-types.md deleted file mode 100644 index 26734fc7..00000000 --- a/.changeset/codec-vorbis-rename-and-container-types.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -"podkit": minor -"@podkit/core": minor -"@podkit/device-types": minor -"@podkit/devices-mass-storage": minor -"@podkit/devices-ipod": minor ---- - -Disambiguate codec from container: `AudioCodec` value `'ogg'` is renamed to `'vorbis'` - -The `AudioCodec` slot previously used `'ogg'` to mean "OGG Vorbis." That conflated the OGG container with the Vorbis stream codec and could not represent Vorbis-in-OGG vs Opus-in-OGG as distinct device capabilities — Echo Mini (which plays Vorbis but hides `.opus` files) could not be modelled accurately. The codec slot now names the audio stream codec; `'vorbis'` replaces `'ogg'` in device presets and config. - -Configs containing `supportedAudioCodecs = ["…", "ogg", "…"]` under `[devices.*]` are migrated automatically by `podkit migrate` (config version 1 → 2). The migration is purely a string substitution inside the `supportedAudioCodecs` array; comments and surrounding formatting are preserved. - -Also lands as type-level groundwork for the future container-aware sync work: `AudioContainer`, `AUDIO_CONTAINERS`, `CODEC_CANONICAL_CONTAINER`, and an optional `DeviceCapabilities.containerConstraints` field. These are declared and exported but not yet read by the planner; they are placeholders for the upcoming Phase 2 work documented in the container-aware sync PRD. - -`DirectoryAdapter` now uses each `.ogg` file's probed stream codec (already populated by `music-metadata`) to distinguish Vorbis, Opus, and OGG-FLAC — same pattern as the existing AAC/ALAC distinction for `.m4a`. `SubsonicAdapter` additionally checks the API's `contentType` field for Opus-in-`.ogg`. The Subsonic check is best-effort because most Subsonic servers report container MIME (`audio/ogg`) regardless of stream codec; deeper probing is deferred until evidence of real-world impact. - -User-facing reference page added at `docs/reference/codec-support.md` explaining the codec/container model and what each `AudioCodec` value means. diff --git a/.changeset/completions-namespace-isolation.md b/.changeset/completions-namespace-isolation.md deleted file mode 100644 index 48735238..00000000 --- a/.changeset/completions-namespace-isolation.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"podkit": patch ---- - -Fix shell completions namespace conflict when multiple podkit binaries are installed. - -The `--cmd` flag now derives the completion function prefix from the binary name (`podkit-dev` → `_podkit_dev`), so `podkit` and `podkit-dev` each get an isolated namespace and their completion scripts no longer clobber each other. The `podkit-dev` binary built via `install:dev` now reports a `-dev` version suffix. diff --git a/.changeset/configurable-codec-preferences.md b/.changeset/configurable-codec-preferences.md deleted file mode 100644 index 7431b401..00000000 --- a/.changeset/configurable-codec-preferences.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -"podkit": minor -"@podkit/core": minor ---- - -Add configurable codec preference system for multi-device audio format support - -Users can now configure an ordered list of preferred audio codecs globally and per-device. The system walks the list top-to-bottom, selecting the first codec that is both supported by the target device and has an available FFmpeg encoder. This replaces the hardcoded AAC-only transcoding pipeline. - -- **Default lossy stack:** opus → aac → mp3 (Rockbox devices get Opus automatically, iPods fall through to AAC) -- **Default lossless stack:** source → flac → alac (lossless files are kept in their original format when possible) -- **Quality presets are codec-aware:** "high" delivers perceptually equivalent quality regardless of codec (e.g., Opus 160 kbps ≈ AAC 256 kbps) -- **Codec change detection:** changing your codec preference re-transcodes affected tracks on the next sync -- **`podkit device info`** shows your codec preference list with supported/unsupported codecs marked -- **`podkit sync --dry-run`** shows which codec will be used and any codec changes -- **`podkit doctor`** warns when FFmpeg is missing an encoder for a preferred codec - -Configure via `config.toml`: - -```toml -[codec] -lossy = ["opus", "aac", "mp3"] -lossless = ["source", "flac", "alac"] - -[devices.myipod.codec] -lossy = "aac" -``` - -No configuration is required — existing setups work unchanged with sensible defaults. diff --git a/.changeset/convergent-track-metadata.md b/.changeset/convergent-track-metadata.md deleted file mode 100644 index ba62334d..00000000 --- a/.changeset/convergent-track-metadata.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -"podkit": minor -"@podkit/core": minor ---- - -Fix track metadata convergence on mass-storage devices and add transfer-mode-aware on-disk tag writes for iPod portable. - -**Bug fix (mass-storage)**: `MassStorageAdapter.updateTrack` previously only wrote `comment`, OGG/Opus artwork, and ReplayGain to disk. All other metadata fields (title, artist, album, albumArtist, genre, year, trackNumber, discNumber, compilation) updated in-memory only — the file's embedded tags on the device were never rewritten. After a relocate or metadata-correction sync the next sync re-detected the same diff every time, looping forever as a zero-byte `update-metadata` op. - -`MassStorageAdapter` now queues every changed textual tag in a single `pendingTagWrites` map and flushes them as one `writeTags(filePath, fields)` call per file via `Promise.allSettled`. Per-file failures are aggregated and re-thrown so the sync executor can categorise them. - -**New behaviour (iPod portable)**: `IpodDeviceAdapter` now mirrors iTunesDB metadata into the on-disk file tags when `transferMode === 'portable'`. This makes files pulled off the iPod self-describing for re-import into a music library. `fast` and `optimized` modes still touch iTunesDB only — the iPod firmware reads metadata from iTunesDB and never falls back to file tags during playback, so paying the tag-rewrite cost in those modes would be wasted work. - -Tag writes are best-effort on iPod portable: failures are surfaced as warnings, not hard errors, because the iTunesDB write (the authoritative store for playback) already succeeded. - -**`addTrack` consistency**: When `transferMode === 'portable'`, both backends now also rewrite tags on first transfer to honour any collection-adapter transforms (e.g. clean-artists, Subsonic-side corrections) that FFmpeg's `-map_metadata 0` would otherwise copy through from the source. - -**On first sync after upgrade**: Existing mass-storage tracks will likely report a `metadata-correction` op on the next sync as stale on-disk tags converge to source values. These are zero-byte writes — no transcoding or transfers happen — but the operation list will look longer than usual for one cycle. - -**Scope notes**: -- Match-key changes (title, artist, album corrections) still produce a remove+add rather than a metadata update. By design: when those fields change, podkit treats it as a different track. -- Virtual-iPod (m-17) inherits the iPod behaviour automatically; no changes needed there. diff --git a/.changeset/core-capability-adapter.md b/.changeset/core-capability-adapter.md deleted file mode 100644 index 55773a72..00000000 --- a/.changeset/core-capability-adapter.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@podkit/core": minor ---- - -Add capability adapter and fix iPod generation metadata - -- Add `createIpodCapabilities()` adapter — maps libgpod device data to core `DeviceCapabilities`, using libgpod as authority for video/artwork support and supplementing codec support from generation metadata -- Add `toLibgpodGeneration()` mapping from detection-layer IDs (`nano_4g`) to libgpod IDs (`nano_4`) -- Fix artwork resolution: nano 3G→320, nano 4-6G→240, photo→320 (were all incorrectly 176) -- Fix ALAC support: add to 4th gen, Photo, Mini 2G, Touch 1-4, iPhone 1-4, iPad 1 -- Add video profiles for Touch, iPhone, and iPad generations (were missing, preventing video sync) diff --git a/.changeset/device-add-slick-flow.md b/.changeset/device-add-slick-flow.md deleted file mode 100644 index 580ea138..00000000 --- a/.changeset/device-add-slick-flow.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -"podkit": patch -"@podkit/core": patch ---- - -Redesign `podkit device add` to be slick and informative. Previously, plugging in a post-2006 iPod (nano 2G, nano 7G, iPod 5G) and running `device add` displayed the device as `Model: Invalid` (libgpod's wording for an empty SysInfo file) and instructed the user to manually write a SysInfo file with `ModelNumStr: MA147` — neither friendly nor accurate. - -The new flow: - -1. **Identity is cascade-resolved** from USB product ID, classic SysInfo, SysInfoExtended, and serial — whichever sources are available. Display reads `Found iPod nano (2nd Generation):` rather than `Model: Invalid`. -2. **A single combined prompt** asks `Add this iPod as "X" and write SysInfoExtended? [Y/n]` when SysInfoExtended is missing and USB is reachable. Confirming triggers firmware inquiry, writes SysInfoExtended, and persists to config in one step. -3. **Capabilities are derived from the cascade-resolved generation**, not from libgpod's pessimistic fallback. Negative capabilities cite the reason (`- Video (not supported on iPod nano 4GB Green (2nd Generation))`). -4. **The follow-up tip** suggests `podkit sync -d --dry-run`, not "go run two more commands". - -New flag `--no-firmware-inquiry` skips the firmware fetch+write when used with `--yes` — for the case where the user wants to defer the write or doesn't have the device connected over USB. - -Internal API changes in `@podkit/core`: - -- **Added** `assessIpodIdentity(mountPoint, opts?)` returning `IpodIdentityAssessment` — pure cascade-driven assessment (no writes). Combines all available identification sources and returns `{ model, capabilities, firmwareInquiry: 'present' | 'missing' | 'unwritable', needsChecksum }`. The CLI now composes from this primitive instead of reaching into libgpod for identity. - -The misleading `device-validation.ts` warning text (`Ensure /Volumes/X/iPod_Control/Device/SysInfo exists with your model number (e.g., "ModelNumStr: MA147")`) has been replaced with a pointer to the canonical fix: `podkit doctor --repair sysinfo-extended`. diff --git a/.changeset/device-aware-doctor.md b/.changeset/device-aware-doctor.md deleted file mode 100644 index b0254bc8..00000000 --- a/.changeset/device-aware-doctor.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@podkit/core': minor -'podkit': minor ---- - -Add device-aware diagnostics framework to `podkit doctor`. The doctor command now handles mass-storage devices gracefully instead of crashing when pointed at a non-iPod device. Diagnostic checks declare which device types they apply to, and the runner filters them automatically. JSON output now includes a `deviceType` field. diff --git a/.changeset/device-capability-finalisation.md b/.changeset/device-capability-finalisation.md deleted file mode 100644 index b89baf87..00000000 --- a/.changeset/device-capability-finalisation.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -'podkit': patch -'@podkit/core': minor -'@podkit/ipod-firmware': minor -'@podkit/device-types': minor -'@podkit/devices-ipod': patch -'@podkit/devices-mass-storage': patch ---- - -P4 — device-capability architecture complete. All in-tree migration finished; deprecated shims removed. - -**Breaking changes in `@podkit/core`:** The following symbols have been removed from the public API: `createIpodCapabilities`, `LibgpodDeviceInfo`, `DEVICE_PRESETS`, `DevicePreset`, `getDevicePreset`, and `resolveDeviceCapabilities`. Callers must migrate before upgrading. - -Migration guide: -- `createIpodCapabilities(libgpodInfo)` → `resolveCapabilities(identity)` (preferred, identity-driven) or `resolveIpodModelCapabilities(modelFromLibgpodInfo(libgpodInfo))` for callers that genuinely hold libgpod data. -- `getDevicePreset(deviceType)` → `BUILT_IN_PRESETS[deviceType]` from `@podkit/devices-mass-storage`. -- `DEVICE_PRESETS` → `BUILT_IN_PRESETS` from `@podkit/devices-mass-storage`. -- `resolveDeviceCapabilities(type, overrides)` → `resolveCapabilities(identity, { overrides })` from `@podkit/core`. -- The `core/device/sysinfo-extended` shim path is gone; import `readSysInfoExtended`, `writeSysInfoExtended`, and `ensureSysInfoExtended` from `@podkit/ipod-firmware` directly. - -See ADR-295.07 for the full architectural rationale. - -**New in `@podkit/ipod-firmware`:** SysInfoExtended file I/O is now owned by this package: `readSysInfoExtended`, `writeSysInfoExtended`, `ensureSysInfoExtended`, `SYSINFO_EXTENDED_PATH`, `SYSINFO_DEVICE_DIR`. Diagnostic helpers `compareSysInfoConsistency` and `normaliseFireWireGuid` are also exported. `ParsedFirmware` gains the optional `modelNumber` field (populated from the `ModelNumStr` plist key when present). - -**New in `@podkit/device-types`:** `IpodModel`, `IpodChecksumType`, `IpodGenerationId`, `IpodGenerationIdLike`, `IpodModelSource`, and `IPOD_GENERATION_IDS` are now exported from this package (canonical home). `UsbConnectionInfo` has been removed — use `UsbFingerprint` instead. `IpodIdentity.notSupportedReason` is added for devices identified as iPods that podkit cannot fully support (e.g. iOS-mode devices). `DeviceCapabilities.artworkMaxResolution` is now `number | null` (null when the generation has no known limit or artwork is unsupported). - -**In `@podkit/devices-ipod`:** `IpodGeneration.supported` and `IpodGeneration.artworkMaxResolution: number | null` are new fields on every generation entry. `lookupByFamilyId` and `FAMILY_ID_TO_GENERATION` are now exported. `unsupported.ts` is populated with comprehensive Apple iOS device PIDs to allow early rejection of phones and tablets at the USB identification stage. - -No user-facing CLI behaviour changes. `podkit device scan`, `podkit device info`, and all sync paths behave identically to P3. diff --git a/.changeset/device-commands-readiness-output.md b/.changeset/device-commands-readiness-output.md deleted file mode 100644 index 6958a7e4..00000000 --- a/.changeset/device-commands-readiness-output.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"podkit": minor ---- - -Enhanced device commands with readiness diagnostics - -- `device scan`: verbose readiness output with per-stage checks, USB discovery for unpartitioned devices, config relationship display, `--mount` flag for automatic mounting, `--report` flag for diagnostic reports -- `podkit doctor`: two-phase diagnostics — readiness checks before database health, graceful handling of devices without databases -- `device info`: readiness summary line in output -- `device init`: readiness-aware guidance with stub messages for format/partition operations -- OS error codes (errno 71, 13, 19, 5) translated to plain-language explanations diff --git a/.changeset/device-list-resolved-config.md b/.changeset/device-list-resolved-config.md deleted file mode 100644 index 23892182..00000000 --- a/.changeset/device-list-resolved-config.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"podkit": minor ---- - -Redesigned `podkit device list` output with resolved config values and provenance tracking - -- Shows resolved quality, audio, video, and artwork settings per device with inheritance indicators -- Global config line shows top-level resolved values -- Connected devices detected automatically and marked with ● prefix -- Devices sorted by connection status, then default, then alphabetical -- Values explicitly set on a device shown without brackets; inherited values wrapped in [brackets] -- Unsupported capabilities shown as ✗, unknown (disconnected iPod) shown as ? -- TYPE column replaces VOLUME column -- New config resolution module (`config/resolve.ts`) with `ResolvedValue` provenance tracking -- `device scan` "Configured devices" section renamed to "Not detected" and now includes iPod devices diff --git a/.changeset/device-output-refactor.md b/.changeset/device-output-refactor.md deleted file mode 100644 index b169a3e4..00000000 --- a/.changeset/device-output-refactor.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"podkit": minor -"@podkit/core": patch ---- - -Improve device command output: USB model in scan, SysInfo mismatch detection, summary/issues layout - -- `podkit device scan` now shows the USB-detected iPod model (e.g., "iPod Classic 6th generation (USB)") and always runs USB discovery in parallel with disk scanning -- `podkit device scan` and `podkit doctor` detect generation mismatches between SysInfo and USB data, warning when the SysInfo file may have been copied from a different device -- `podkit device info`, `podkit device scan`, and `podkit doctor` now separate compact check summaries from detailed issue explanations — warnings and fix commands appear in a dedicated "Issues" section instead of inline -- New `lookupGenerationByModelNumber()` function in `@podkit/core` for resolving iPod generation from SysInfo model numbers diff --git a/.changeset/device-readiness-diagnostics.md b/.changeset/device-readiness-diagnostics.md deleted file mode 100644 index 30485cb3..00000000 --- a/.changeset/device-readiness-diagnostics.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@podkit/core": minor ---- - -Add device readiness diagnostic system - -- New 6-stage readiness pipeline (USB → Partition → Filesystem → Mount → SysInfo → Database) that checks every stage of device health -- OS error code interpreter translates errno values into actionable explanations -- USB discovery finds iPods even without disk representation (unpartitioned/uninitialized devices) -- Enhanced SysInfo validation detects missing, corrupt, or unrecognized model files -- Diagnostics framework now handles missing database gracefully (checks skip instead of crashing) diff --git a/.changeset/devices-extraction-and-providers.md b/.changeset/devices-extraction-and-providers.md deleted file mode 100644 index e9663c67..00000000 --- a/.changeset/devices-extraction-and-providers.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'podkit': minor -'@podkit/core': minor -'@podkit/devices-ipod': patch -'@podkit/devices-mass-storage': patch ---- - -New packages: `@podkit/devices-ipod` (canonical home for iPod generation tables, model lookups, and capability synthesis) and `@podkit/devices-mass-storage` (user-extensible DAP preset framework for Echo Mini, Rockbox, generic, and custom devices). - -Echo Mini is now auto-detected at `device add` — when the USB descriptor matches the known VID/PID (`0x071b`/`0x3203`), no `--type echo-mini` flag is required. - -`enumerateConnectedDevices` is now the recommended way to discover and classify USB devices. It accepts a `providers: DeviceProvider[]` array and returns `EnumeratedDevice[]` carrying both the USB connection info and the provider-produced identity. - -`getCapabilities` in `@podkit/devices-ipod` is libgpod-free. Capability synthesis is purely table-and-firmware-driven; the legacy `createIpodCapabilities` adapter that depended on a live libgpod `LibgpodDeviceInfo` struct is deprecated in `@podkit/core`. Parity is verified across all 29 generations (the 4 that were libgpod `unknown` degenerate cases are now correctly populated from the table). - -Internal re-export shims in `@podkit/core` keep all existing call paths compiling for one release. The shims delegate to `@podkit/devices-ipod` and `@podkit/devices-mass-storage` and will be removed in P4. diff --git a/.changeset/doctor-artwork-reset-and-rebuild-rename.md b/.changeset/doctor-artwork-reset-and-rebuild-rename.md deleted file mode 100644 index b96a2b68..00000000 --- a/.changeset/doctor-artwork-reset-and-rebuild-rename.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"podkit": minor -"@podkit/core": minor ---- - -Add `podkit doctor --repair artwork-reset` to clear all artwork from an iPod without needing a source collection. This is a fast alternative to a full rebuild — useful when your source collection isn't available or you just want to clear corrupted artwork quickly. - -Rename `--repair artwork-integrity` to `--repair artwork-rebuild` to better describe what the repair does. The old name no longer works. diff --git a/.changeset/doctor-no-system-and-sysinfo-redesign.md b/.changeset/doctor-no-system-and-sysinfo-redesign.md deleted file mode 100644 index ea36a84c..00000000 --- a/.changeset/doctor-no-system-and-sysinfo-redesign.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@podkit/core': minor -'podkit': minor ---- - -`podkit doctor` gains `--no-system` to skip system-scope checks (FFmpeg encoders, libusb availability, udev rule). System checks remain on by default; pass `--no-system` for device-only diagnostics or in tests where the host environment shouldn't influence the result. - -The `sysinfo-consistency` check is redesigned: a missing `SysInfoExtended` file is now `skip` (not `fail`) since absence is not a failure mode. When the file is present it's compared against the live device on two independent axes — FireWireGUID and model generation — and only fails when at least one axis can be evaluated and disagrees. The check picks up live device data via the new `liveIdentity` field on `DiagnosticContext`, which `runDiagnostics` accepts as part of `RunDiagnosticsInput`. diff --git a/.changeset/echo-mini-e2e-bugfixes.md b/.changeset/echo-mini-e2e-bugfixes.md deleted file mode 100644 index 49b9ab4b..00000000 --- a/.changeset/echo-mini-e2e-bugfixes.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -"@podkit/core": patch -"podkit": patch ---- - -Fix multiple bugs discovered during end-to-end Echo Mini hardware validation - -**Sync pipeline:** - -- Create temp directory for optimized-copy operations (not just transcodes), fixing "No such file or directory" FFmpeg failures on mass-storage devices -- Capture last 1000 chars of FFmpeg stderr (instead of first 500) so actual errors aren't swallowed by the version banner - -**Device preset content paths:** - -- Pass device preset content paths to adapter even when no user overrides exist, fixing Echo Mini's `musicDir: ''` being ignored and files landing in `Music/` instead of device root - -**Artwork:** - -- Read embedded artwork during mass-storage device scan (`skipCovers: false`) so artwork presence is correctly detected, preventing false `artwork-added` upgrades on every sync -- Force `yuvj420p` (4:2:0) pixel format in artwork scale filter — JPEG with 4:4:4 chroma subsampling does not display on the Echo Mini - -**Sync tag and preset detection:** - -- Treat `quality=copy` sync tags as in-sync when the classifier would also route the source as a copy, preventing false preset-upgrade detection on FLAC-capable mass-storage devices -- Route lossless sources to transcode (not copy) when quality preset is non-lossless, even if the device natively supports the source codec (e.g., FLAC device with quality=high should produce AAC) diff --git a/.changeset/eject-device-label.md b/.changeset/eject-device-label.md deleted file mode 100644 index 3771a67e..00000000 --- a/.changeset/eject-device-label.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@podkit/core": patch -"podkit": patch ---- - -Use configurable device label in eject messages instead of hardcoded 'iPod' diff --git a/.changeset/fields-validation-error.md b/.changeset/fields-validation-error.md deleted file mode 100644 index 8dd4bba3..00000000 --- a/.changeset/fields-validation-error.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"podkit": patch ---- - -Hard error on invalid `--fields` names with message listing valid fields; valid fields now listed in `--help` diff --git a/.changeset/fix-device-model-data.md b/.changeset/fix-device-model-data.md deleted file mode 100644 index 61889722..00000000 --- a/.changeset/fix-device-model-data.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@podkit/core": patch ---- - -Fix incorrect device model data: correct checksum types (nano 7G: hashAB, touch 1G-3G: hash72), fix USB product IDs (0x1205: iPod mini, 0x1209: iPod Video, 0x120a: nano 1G), reclassify model B867 from nano 4G to shuffle 3G, add 15 nano 7G model number variants, add missing touch 4G serial suffixes, and add first crowd-sourced nano 7G serial suffix mapping diff --git a/.changeset/fix-process-exit-stdout-truncation.md b/.changeset/fix-process-exit-stdout-truncation.md deleted file mode 100644 index 329af4e6..00000000 --- a/.changeset/fix-process-exit-stdout-truncation.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"podkit": patch ---- - -Fix stdout truncation when piping CLI output to another process. Commands that used `process.exit(1)` could terminate before stdout buffers flushed, truncating JSON output (e.g. `podkit init --json | node -e ...`). All error exit paths now use `process.exitCode = 1` and return normally, allowing Node.js to drain streams before exiting. diff --git a/.changeset/fix-subsonic-source-display.md b/.changeset/fix-subsonic-source-display.md deleted file mode 100644 index 0d31c06f..00000000 --- a/.changeset/fix-subsonic-source-display.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"podkit": patch ---- - -Fix blank source path in sync output for subsonic collections diff --git a/.changeset/fix-sudo-config-path.md b/.changeset/fix-sudo-config-path.md deleted file mode 100644 index 569c0ecb..00000000 --- a/.changeset/fix-sudo-config-path.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'podkit': patch ---- - -Fix config not found when running `podkit` under `sudo`. The default config path now resolves the invoking user's home directory via `SUDO_USER`/`DOAS_USER` and `/etc/passwd`, rather than using root's home. diff --git a/.changeset/identity-cascade-compose.md b/.changeset/identity-cascade-compose.md deleted file mode 100644 index 58db8422..00000000 --- a/.changeset/identity-cascade-compose.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"podkit": patch -"@podkit/core": patch -"@podkit/ipod-firmware": patch -"@podkit/devices-ipod": patch ---- - -Fix iPod model identification regressing to "Unknown iPod" after `doctor --repair sysinfo-extended` on pre-2006 devices (mini 2G), and tighten the package-boundary contract so consumers compose identity instead of injecting resolution policy. - -The bug: each consumer of `ensureSysInfoExtended` / `readSysInfoExtended` passed a serial-only `resolveModel` callback. When the 3-character serial suffix wasn't in `tables/serials.ts`, the resolver returned undefined and the device was displayed as "Unknown iPod" — even when a SysInfo file with a known `ModelNumStr` was sitting next to the SysInfoExtended on disk. - -The fix: - -- **Removed** `ModelResolver` type and the `resolveModel` callback from `@podkit/ipod-firmware`. `readSysInfoExtended` and `ensureSysInfoExtended` now return a flat `SysInfoIdentity` bag (`firewireGuid?, serialNumber?, modelNumStr?, familyId?`). When a SysInfo file is on disk alongside SysInfoExtended, its `ModelNumStr` is read opportunistically. -- **Callers compose** with `resolveIpodModel(bag)` from `@podkit/devices-ipod`, which cascades modelNumStr → serial → productId → familyId → libgpodGeneration. The CLI no longer makes resolution decisions. -- **Added** `SYSINFO_PATH`, `SYSINFO_EXTENDED_PATH`, `SYSINFO_DEVICE_DIR` exported from `@podkit/ipod-firmware` and re-exported from `@podkit/core`. Consumers use these constants instead of duplicating the literal `iPod_Control/Device/...` paths. -- **Added** `S4G: '9804'` entry to `tables/serials.ts` (mini 2G 4GB Pink, sourced from real hardware, serial `JQ5141TFS4G`). -- **Post-write enrichment.** After `ensureSysInfoExtended` writes the file via USB inquiry, it now re-reads via `readSysInfoExtended` so the post-write identity bag includes `modelNumStr` from the SysInfo neighbour. Eliminates the cosmetic regression where the repair-success message showed a less-specific name than the subsequent `doctor` run. diff --git a/.changeset/ipod-firmware-scsi-delivery.md b/.changeset/ipod-firmware-scsi-delivery.md deleted file mode 100644 index a820035b..00000000 --- a/.changeset/ipod-firmware-scsi-delivery.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -"podkit": minor -"@podkit/core": minor -"@podkit/device-types": minor -"@podkit/ipod-firmware": minor ---- - -Add SCSI firmware inquiry for iPod identification (P1 — m-18 device-capability architecture). - -`@podkit/device-types` (first published release) provides the canonical shared type definitions — `DeviceCapabilities`, `DeviceIdentity`, `ParsedFirmware`, and `DeviceProvider` — used across the podkit monorepo without circular dependencies. - -`@podkit/ipod-firmware` (first published release) implements iPod firmware inquiry via SCSI (Linux SG_IO + macOS IOKit, using koffi FFI) with USB fallback through the existing libgpod-node binding. Devices that previously failed identification over USB — including iPod mini 2G, nano 2G, and some iPod 5G Video configurations — can now be identified via SCSI. The orchestrator probes available transports at startup, prefers USB when both are available, and falls back to SCSI transparently. - -`@podkit/core` now routes `ensureSysInfoExtended` through the new orchestrator with SCSI fallback, and registers two new `podkit doctor` checks: `inquiry-methods` (reports which transports are available on this host) and `sysinfo-consistency` (validates that the on-disk SysInfo file matches the live firmware read). EACCES errors from SCSI include step-by-step recovery instructions. - -`podkit` CLI gains `--repair udev-rule` in `podkit doctor` to install the Linux udev rule that grants non-root `/dev/sg*` access, and surfaces the new doctor checks in the readiness output. diff --git a/.changeset/ipod-model-identity.md b/.changeset/ipod-model-identity.md deleted file mode 100644 index ec7e56b4..00000000 --- a/.changeset/ipod-model-identity.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -"@podkit/core": minor -"podkit": minor ---- - -Add canonical IpodModel type for structured device identity - -- Add `IpodModel` interface — canonical representation of identified iPod model with `displayName`, `generationId`, `checksumType`, `color`, `capacityGb`, `modelNumber`, and `source` provenance -- Add `resolveIpodModel()` factory — builds an `IpodModel` from USB product ID, SysInfo model number, or serial number suffix -- Add `UsbConnectionInfo` interface — pure USB bus topology data, split from device identity -- Restructure `UsbDiscoveredDevice` to carry `usb: UsbConnectionInfo` + `model?: IpodModel` -- Add `usbModel` and `deviceModel` to `ReadinessResult` — USB-derived and SysInfo-derived models kept separate for mismatch detection -- Update `SysInfoExtendedResult` with structured `model`, `firewireGuid`, `serialNumber` fields -- Clean `checkSysInfo()` return type — new `SysInfoCheckResult` separates stage result from device model -- Add `model` to JSON output for `device scan` and `device info` commands -- `device scan` and `device info` now display richest available model name (color/capacity from SysInfo when available) -- Remove `UsbDeviceInfo` type (replaced by `UsbConnectionInfo` + `IpodModel`) diff --git a/.changeset/libgpod-node-device-class.md b/.changeset/libgpod-node-device-class.md deleted file mode 100644 index b7588ce7..00000000 --- a/.changeset/libgpod-node-device-class.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@podkit/libgpod-node": minor ---- - -Add standalone Device class for capability queries without opening a database - -- `Device.fromMountPoint(path)` — reads SysInfo from filesystem, determines capabilities -- `Device.fromModelNumber(num)` — cached lookup from model number string, no filesystem needed -- Exposes `supportsArtwork`, `supportsVideo`, `supportsPhoto`, `supportsPodcast`, `generation`, `modelNumber`, `modelName`, `capacity` -- Add `ArtworkFormat` type (reserved for future artwork dimension exposure) diff --git a/.changeset/mass-storage-delete-and-orphans.md b/.changeset/mass-storage-delete-and-orphans.md deleted file mode 100644 index e2978bd9..00000000 --- a/.changeset/mass-storage-delete-and-orphans.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -"podkit": minor -"@podkit/core": minor ---- - -Fix `--delete` to only remove managed files on mass-storage devices, and add orphan file detection via `podkit doctor`. - -**Bug fix:** `--delete` previously removed all unmatched files on mass-storage devices, including user-placed files. It now only removes files that podkit manages (tracked in `.podkit/state.json`), matching iPod behavior where only database tracks are candidates for deletion. - -**Collision detection:** Sync now detects when a planned file write would collide with an existing unmanaged file and reports the conflict before writing. Works in both normal sync and `--dry-run` mode. - -**New diagnostic check:** `podkit doctor` now runs health checks on mass-storage devices. The `orphan-files-mass-storage` check detects unmanaged files in content directories and can clean them up via `podkit doctor --repair orphan-files-mass-storage`. - -**Other improvements:** -- State manifest (`.podkit/state.json`) is now written without pretty-printing to reduce file size on device storage -- Shell completions now include valid repair IDs for the `--repair` option diff --git a/.changeset/mass-storage-device-support.md b/.changeset/mass-storage-device-support.md deleted file mode 100644 index f1d1b335..00000000 --- a/.changeset/mass-storage-device-support.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -"podkit": minor -"@podkit/core": minor -"@podkit/daemon": minor ---- - -Add mass-storage device support for non-iPod portable music players. - -**Supported device types:** Echo Mini, Rockbox, and generic mass-storage DAPs. iPod support is unchanged. - -**New in CLI (`podkit`):** -- `podkit device add --type ` registers mass-storage devices by type and mount path -- `podkit device info/music/video` work with mass-storage devices via `DeviceAdapter` interface -- `podkit device scan` shows configured path-based devices alongside auto-detected iPods -- `podkit sync` routes to the correct adapter (iPod or mass-storage) based on device config -- Video sync now uses capabilities-based gating instead of iPod-only checks -- Safety gates on `device init/reset/clear` (iPod-only commands) for mass-storage devices -- Mount and eject commands show device-appropriate messaging -- Config validation rejects capability overrides on iPod devices (capabilities are auto-detected from generation) -- Shared `openDevice()` function eliminates duplicated device-opening logic across commands - -**New in core (`@podkit/core`):** -- `DeviceAdapter` interface — generic abstraction over device databases (iPod, mass-storage) -- `MassStorageAdapter` — filesystem-based track management with `.podkit/state.json` manifest -- `IpodDeviceAdapter` — thin wrapper making `IpodDatabase` implement `DeviceAdapter` -- Device capability presets for Echo Mini, Rockbox, and generic devices -- `resolveDeviceCapabilities()` merges preset defaults with user config overrides -- `DeviceTrack` type used throughout sync engine (replaces `IPodTrack` casts in execution paths) -- Configurable content path prefixes (`musicDir`, `moviesDir`, `tvShowsDir`) with device-type defaults -- Device presets include default content paths (Echo Mini: root for music; generic/Rockbox: `Music/`, `Video/Movies/`, `Video/Shows/`) -- Manifest v2 stores active content paths; files automatically moved when prefixes change -- Root path support (`/`, `.`, or empty string all normalize to device root) -- Content path duplicate validation (no two content types can share the same prefix) -- Video scanning support for mass-storage devices (.m4v, .mp4, .mov, .avi, .mkv) - -**New in daemon (`@podkit/daemon`):** -- Mass-storage device polling via `PODKIT_MASS_STORAGE_PATHS` env var (colon/comma separated) -- Second `DevicePoller` + `SyncOrchestrator` pair for mass-storage devices -- No-op mount/eject runners (mass-storage devices are externally managed) -- Graceful shutdown handles both iPod and mass-storage sync pipelines - -**Configuration:** -```toml -[devices.echo] -type = "echo-mini" -path = "/Volumes/ECHO" - -# Optional capability overrides (mass-storage only) -artworkMaxResolution = 800 -supportedAudioCodecs = ["aac", "mp3", "flac"] - -# Optional content path overrides (mass-storage only) -musicDir = "/" # Place music at device root -moviesDir = "Films" # Custom movies directory -tvShowsDir = "TV Shows" # Custom TV shows directory -``` - -**Environment variables for content paths:** -- `PODKIT_MUSIC_DIR` — global default music directory -- `PODKIT_MOVIES_DIR` — global default movies directory -- `PODKIT_TV_SHOWS_DIR` — global default TV shows directory diff --git a/.changeset/multi-ipod-daemon.md b/.changeset/multi-ipod-daemon.md deleted file mode 100644 index 31dcf60d..00000000 --- a/.changeset/multi-ipod-daemon.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@podkit/daemon": minor ---- - -Support multiple iPods plugged in simultaneously. Each device gets a unique mount point and devices appearing during a sync are queued and synced sequentially after the current sync completes. diff --git a/.changeset/ogg-artwork-embedding.md b/.changeset/ogg-artwork-embedding.md deleted file mode 100644 index 13153894..00000000 --- a/.changeset/ogg-artwork-embedding.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@podkit/core": minor ---- - -Embed artwork in OGG/Opus files for mass-storage devices with `artworkSources: ['embedded']`. - -FFmpeg's OGG muxer cannot write image streams (upstream tickets #4448, #9044, open since 2015), so OGG output was previously stripped of artwork with `-vn`. Mass-storage devices relying on embedded artwork showed no cover art for Opus tracks. - -**What changed:** - -- After FFmpeg produces an OGG file (with artwork stripped), the pipeline post-processes it via node-taglib-sharp to embed artwork as a `METADATA_BLOCK_PICTURE` Vorbis comment -- Artwork is resized to the device's `artworkMaxResolution` before embedding, matching the behavior of other formats where FFmpeg handles resize during transcode -- Resize results are cached per-album to avoid redundant FFmpeg image-processing spawns -- New `TagWriter.writePicture()` method and `resizeArtwork()` utility -- Pending picture writes follow the same deferred flush pattern as comment and ReplayGain tag writes (queued by `updateTrack`, flushed by `save()`) diff --git a/.changeset/path-template-cli-exposure.md b/.changeset/path-template-cli-exposure.md deleted file mode 100644 index 22f77c8d..00000000 --- a/.changeset/path-template-cli-exposure.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"podkit": minor ---- - -Expose `pathTemplate` as a per-device config option for mass-storage devices, allowing user-customisable folder structures. - -Configurable via: - -- `[devices.] pathTemplate = "..."` in TOML -- `PODKIT_PATH_TEMPLATE` env var (applied as a global default for mass-storage devices) - -Variables: `{albumArtist}`, `{artist}`, `{album}`, `{title}`, `{trackNumber}`, `{discNumber}`, `{totalDiscs}`, `{genre}`, `{year}`, `{ext}`. The template must contain `{title}` and `{ext}` and is rejected on iPod devices (iPod paths are managed by libgpod, not by template). - -Changing the template between syncs triggers the existing self-healing relocate flow — existing files are moved via `fs.rename()` to match the new layout, with no re-transcoding. Adds, removes (`--delete`), and template-driven relocates all compose in a single sync operation. diff --git a/.changeset/replaygain-mass-storage-tags.md b/.changeset/replaygain-mass-storage-tags.md deleted file mode 100644 index 94f35dba..00000000 --- a/.changeset/replaygain-mass-storage-tags.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"@podkit/core": minor ---- - -Write ReplayGain tags to transcoded files for mass-storage devices with `audioNormalization: 'replaygain'` (e.g., Rockbox). - -Previously, ReplayGain data was only stored as iPod soundcheck values in the iTunes database. Mass-storage devices read volume normalization from file tags, but FLAC→AAC transcoding strips ReplayGain metadata. Tracks on Rockbox devices played without normalization. - -**What changed:** - -- ReplayGain tags (`REPLAYGAIN_TRACK_GAIN`, `REPLAYGAIN_TRACK_PEAK`) are injected via FFmpeg `-metadata` flags during transcoding for MP3, FLAC, and OGG/Opus output -- M4A files (where FFmpeg can't write ReplayGain metadata) get tags written via node-taglib-sharp after transfer -- Raw ReplayGain dB/peak values are preserved from collection sources (Subsonic API, local files) through the sync pipeline, avoiding precision loss from soundcheck integer conversion -- Device scan reads ReplayGain from file tags so the sync engine can detect when normalization data changes and needs updating -- Direct-copy operations skip tag writing since source files already have correct tags -- `soundcheckToReplayGainDb()` reverse conversion function added for back-converting when raw values aren't available - -**Bug fix:** `IpodTrackImpl` used `data.soundcheck || undefined` which coerced a valid soundcheck of `0` to `undefined`. Changed to `data.soundcheck ?? undefined`. diff --git a/.changeset/sync-engine-refactor.md b/.changeset/sync-engine-refactor.md deleted file mode 100644 index c30373d9..00000000 --- a/.changeset/sync-engine-refactor.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@podkit/core": minor -"podkit": minor ---- - -Refactor sync engine to be fully content-type-agnostic with per-handler operation types. - -**Breaking:** `createMusicHandler()` and `createVideoHandler()` now take a config object at construction instead of using `setTransformsConfig()`/`setExecutionConfig()`. Removed `HandlerDiffOptions`, `HandlerPlanOptions`, `MusicExecutionConfig` types. Renamed `MusicExecutor` to `MusicPipeline`. Removed legacy planner functions (`createMusicPlan`, `planVideoSync` and related helpers). - -**New:** `MusicSyncConfig`, `VideoSyncConfig`, `MusicTrackClassifier`, `VideoTrackClassifier`, `MusicOperationFactory`, `MusicOperation`, `VideoOperation`, `BaseOperation` types. Handlers now own their operation types via `TOp` type parameter on `ContentTypeHandler`. diff --git a/.changeset/sync-tag-adapter-refactor.md b/.changeset/sync-tag-adapter-refactor.md deleted file mode 100644 index c937d0a8..00000000 --- a/.changeset/sync-tag-adapter-refactor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@podkit/core': minor ---- - -Internalize raw sync tag functions (`parseSyncTag`, `formatSyncTag`, `writeSyncTag`) from the public API. Sync tag reads now use the typed `DeviceTrack.syncTag` field, and writes use `DeviceAdapter.writeSyncTag()` or the new `update-sync-tag` operation type. Adds `SyncTagUpdate` type and `syncTagsEqual` to the public API. diff --git a/.changeset/sysinfo-extended-auto-identify.md b/.changeset/sysinfo-extended-auto-identify.md deleted file mode 100644 index 6a23fadb..00000000 --- a/.changeset/sysinfo-extended-auto-identify.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -"podkit": minor -"@podkit/core": minor -"@podkit/libgpod-node": minor ---- - -Automated iPod device identification via SysInfoExtended. - -Modern iPods (post-2006) ship without a populated `SysInfo` file after iTunes restore. Without it, libgpod treats the device as generic — artwork breaks, ALAC support is unknown, and database checksums fail on Classic 6/7G and Nano 3G+. podkit now reads SysInfoExtended directly from iPod firmware over USB during `device add`, so first-time setup works with no manual tooling. - -**User-visible:** -- `podkit device add` identifies the exact model (e.g. "iPod nano 8GB Black (3rd Generation)") with no input -- `podkit doctor` detects missing SysInfoExtended and offers `--repair sysinfo-extended` -- `podkit sync` works correctly on first run with full capability detection -- Hash72 (Nano 5G) and HashAB (Nano 6G) devices get clear limitation messages -- SysInfoExtended write is gated on user confirmation during `device add` - -**Core (`@podkit/core`):** -- Unified iPod model registry — single table, both `0x120x`/`0x126x` USB ID ranges, 190+ serial-suffix → model mappings, checksum-type classification per generation -- `ensureSysInfoExtended()` orchestrator: check existing → USB read → validate XML → write -- USB discovery now exposes `serialNumber`, `busNumber`, `deviceAddress`; `resolveUsbDeviceFromPath()` on macOS + Linux -- Readiness pipeline: checksum-aware severity (hash58+ devices fail without SysInfoExtended; pre-checksum devices warn) -- `READINESS_RULES` declarative array replaces ad-hoc `determineLevel()` logic -- New `sysinfo-extended` diagnostic check -- Recognizes `P` / `F` model prefixes in SysInfo - -**libgpod-node (`@podkit/libgpod-node`):** -- `readSysInfoExtendedFromUsb()` N-API binding, resolved via `dlsym` at runtime so it loads gracefully on systems where libgpod lacks the symbol -- Prebuild patches upstream libgpod 0.8.3 to move `itdb_usb.c` from `tools/` into the library; libusb 1.0.27 built from source on all 6 platforms -- `--whole-archive` / `-force_load` linker flags preserve the dlsym symbol in the `.node` binary - -**CLI (`podkit`):** -- `device add` attempts SysInfoExtended read after mount, before DB init; enriches model name in summary -- `doctor` adds suggested-actions section, drops destructive sysinfo guidance -- `device scan` and `doctor` show clearer SysInfo readout diff --git a/.changeset/sysinfo-extended-scsi-fallback.md b/.changeset/sysinfo-extended-scsi-fallback.md deleted file mode 100644 index fee853a2..00000000 --- a/.changeset/sysinfo-extended-scsi-fallback.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -"podkit": patch -"@podkit/core": patch -"@podkit/ipod-firmware": patch ---- - -Fix SysInfoExtended SCSI-fallback on macOS for SCSI-only iPods (mini 2G, nano 2G, iPod 5G/5.5G). `device add` and `doctor --repair sysinfo-extended` now correctly fall back from USB → SCSI when the device does not respond to vendor control transfers, instead of failing with a misleading "Could not read device identity from USB" error. - -Internal API changes in `@podkit/ipod-firmware`: - -- **Changed:** `ensureSysInfoExtended(mountPoint, fp, options?)` now takes a full `UsbFingerprint` instead of the previous `{ busNumber, deviceAddress }` shape. Required so the macOS SCSI transport can locate the IOService via vendorId/productId/serialNumber. `UsbDeviceAddress` is removed. -- **Added:** `inquireFirmwareDetailed(fp, opts?)` — like `inquireFirmware` but returns `{ firmware, plan, attempts }` so callers can distinguish which transports were attempted. `inquireFirmware` is unchanged for existing consumers. -- **Added:** `EnsureSysInfoExtendedOptions` type with `readFromUsb`, `resolveModel`, `inquireOptions` fields. Replaces the previous positional `(mountPoint, fp, readFromUsb, resolveModel)` signature. - -Internal API changes in `@podkit/core`: - -- **Added:** `hasCompleteUsbFingerprint(fp): fp is CompleteUsbDevice` type guard exported from `@podkit/core`. -- **Added:** `CompleteUsbDevice` type — a `UsbFingerprint` with vendorId, productId, bus, devnum guaranteed present (serialNumber optional). -- **Changed:** `resolveUsbDeviceFromPath(path)` now also returns `vendorId` and `productId`. Linux extracts from sysfs `idVendor`/`idProduct`; macOS extracts from `system_profiler` JSON. - -User-facing error messages now differentiate between transport failures: "Could not read device identity from USB and SCSI" / "...from USB" / "...from SCSI" / "...no firmware inquiry transport is available on this system" / "...returned data but it could not be parsed". diff --git a/.changeset/test-fixtures-mini-tracks-library.md b/.changeset/test-fixtures-mini-tracks-library.md deleted file mode 100644 index 1186e2cb..00000000 --- a/.changeset/test-fixtures-mini-tracks-library.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@podkit/test-fixtures": minor -"@podkit/core": patch ---- - -`@podkit/test-fixtures`: expose synthetic-track generators as a library - -Adds a library entry (`src/lib.ts`) exposing `generateMiniFlac`, `generateMiniMp3`, `generateMiniM4a`, `generateMiniOggVorbis`, and `generateMiniOggOpus`. Each helper writes a single short sine-tone file in the requested codec/container with optional metadata. Integration tests that need real audio for tag round-trip coverage now import these from `@podkit/test-fixtures` rather than re-implementing the ffmpeg invocation inline. - -Each generator calls a new `requireEncoder()` guard before invoking ffmpeg. If the host's ffmpeg is missing the codec's encoder, the helper throws a clear error with platform-aware install hints. There is also an explicit `bun run --filter @podkit/test-fixtures check-ffmpeg` script that verifies the full set of required encoders against the host environment in one shot. - -The mass-storage tag writer integration test (`packages/podkit-core/src/device/mass-storage-tag-writer.integration.test.ts`) drops its inline `generateOgg`, `generateOpus`, `generateFlac`, `generateM4a`, `generateMp3` helpers and the `HAS_LIBVORBIS` skip predicate. The OGG Vorbis tests now run unconditionally — they fail loudly with an install hint when libvorbis is absent rather than skipping silently. - -Developer docs (`docs/developers/development.md`) updated to point macOS contributors at the `homebrew-ffmpeg/ffmpeg` tap for full encoder coverage. diff --git a/.changeset/track-metadata-polish.md b/.changeset/track-metadata-polish.md deleted file mode 100644 index 2573e228..00000000 --- a/.changeset/track-metadata-polish.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"podkit": minor -"@podkit/core": minor -"@podkit/devices-mass-storage": minor ---- - -Polish on the convergent-metadata work (TASK-327 follow-up): - -- **Tag-write concurrency cap.** `save()` now caps in-flight tag writes at 16 via a small `runWithConcurrency` helper instead of firing every pending write at once. Avoids `EMFILE` on large libraries. -- **Aggregated tag-write errors.** Failure messages now begin with `tag write failed` so the executor's error categorizer classifies them as file-I/O (`copy`) rather than risking a path-keyword mis-classification. -- **WAV/AIFF on mass-storage.** Podkit transcodes WAV and AIFF source files to a managed codec before placing them on a mass-storage device, even when the device firmware can play them. RIFF/IFF tag-writing is unreliable. Presets continue to list these codecs for documentation. iPod is unaffected (libgpod / iTunesDB handle metadata for WAV/AIFF). -- **OGG Vorbis tag round-trip tests.** Now run on builds with libvorbis (skipped automatically when absent). -- **Shared TagFields helpers.** `buildTagFieldsFromInput` and `diffTagFields` replace three duplicate field-by-field walks across adapters. -- **`TransferMode` type unified.** Removed `'fast' | 'optimized' | 'portable'` duplication between `DeviceTrackInput`, `DeviceTrackMetadata`, and the canonical `TransferMode` in `transcode/types.ts`. Drops several inline type casts. -- **Docs.** `transferMode` now has a dedicated section in `docs/reference/config-file.md` explaining the iPod vs mass-storage contract and migration churn. `pathTemplate` (from the prior release) and `PODKIT_PATH_TEMPLATE` are now documented in the config reference and environment-variables reference. diff --git a/.changeset/transfer-mode.md b/.changeset/transfer-mode.md deleted file mode 100644 index 7c764855..00000000 --- a/.changeset/transfer-mode.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -"podkit": minor -"@podkit/core": minor ---- - -Add three-tier transfer mode system controlling how files are prepared for the device. - -**Transfer modes:** -- `fast` (default): optimizes for sync speed — direct-copies compatible files, strips artwork from transcodes -- `optimized`: strips embedded artwork from all file types (including MP3, M4A, ALAC copies) via FFmpeg stream-copy, reducing storage usage without re-encoding -- `portable`: preserves embedded artwork in all files for use outside the iPod ecosystem - -**Configuration:** -- `transferMode` config option (global and per-device) -- `--transfer-mode` CLI flag -- `PODKIT_TRANSFER_MODE` environment variable - -**Selective re-processing:** -- `--force-transfer-mode` flag re-processes only tracks whose transfer mode doesn't match the current setting -- `PODKIT_FORCE_TRANSFER_MODE` environment variable -- Works on all file types including direct copies (unlike `--force-transcode` which only affects transcoded tracks) - -**Device inspection:** -- `podkit device music` and `podkit device video` stats show transfer mode distribution -- Missing transfer field flagged alongside missing artwork hash in sync tag summary -- New `syncTagTransfer` field available in `--tracks --fields` for querying transfer mode data -- Dry-run output shows configured transfer mode - -**Under the hood:** -- Granular operation types: `add-direct-copy`, `add-optimized-copy`, `add-transcode` (and upgrade equivalents) -- Sync tags written to all tracks including direct copies (`quality=copy`) -- `DeviceCapabilities` abstraction for device-aware sync decisions -- Sync tag field `transfer=` tracks which mode was used per track diff --git a/.changeset/tty-aware-interactive-output.md b/.changeset/tty-aware-interactive-output.md deleted file mode 100644 index 7d4bca1e..00000000 --- a/.changeset/tty-aware-interactive-output.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"podkit": patch ---- - -Move spinners and progress bars to stderr and auto-suppress when stdout is not a TTY. Adds `--no-tty` flag for explicit suppression. Piped output (e.g. `podkit collection music --format json | jq .`) now produces clean stdout without needing `--quiet`. diff --git a/.changeset/usb-enumeration-classify-refactor.md b/.changeset/usb-enumeration-classify-refactor.md deleted file mode 100644 index f0d72b2e..00000000 --- a/.changeset/usb-enumeration-classify-refactor.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -"podkit": patch -"@podkit/core": patch -"@podkit/devices-ipod": patch -"@podkit/devices-mass-storage": patch ---- - -Fix `podkit device scan` reporting phantom "Unknown iPod (USB only)" entries for non-iPod USB peripherals (mice, hubs, Thunderbolt docks, Ethernet adapters, USB drives). Each phantom suggested `podkit device init` — a destructive operation that could mutate an unrelated device. - -Root cause was architectural: a single `discoverUsbIpods()` function mixed three concerns — USB enumeration, iPod-domain enrichment, and the function name's implied filter (which it didn't actually do). Refactored into clean layers: - -- **`@podkit/core` — pure USB enumeration.** `enumerateUsb()` returns `EnumeratedUsbDevice[]` with vendor/product/serial/bus/devnum/diskIdentifier ONLY. No iPod-domain knowledge. -- **`@podkit/devices-ipod` — iPod classifier.** `classifyAsIpod(dev)` returns `IpodClassification | null` (matches Apple-vendor with iPod or iOS PIDs). -- **`@podkit/devices-mass-storage` — mass-storage classifier.** `classifyAsMassStorage(dev)` returns `MassStorageClassification | null` (matches `USB_PRESET_HINTS` entries like Echo Mini). -- **`@podkit/core` composer.** `classifyUsbDevices()` runs both classifiers and returns recognized devices as a tagged union; drops unknown peripherals. -- **CLI `device scan`** now calls `enumerateUsb()` → `classifyUsbDevices()` → renders by `kind`. No domain logic in the command layer. - -Added `kind: 'mass-storage'` rendering branch so mass-storage DAPs are no longer mis-labeled as "Unknown iPod". - -Removed `discoverUsbIpods` and the leaky `UsbDiscoveredDevice` type (which previously carried iPod-domain fields). Adding a new mass-storage device now means adding one entry to `USB_PRESET_HINTS` — no `@podkit/core` change required. Adding a new iPod generation means updating `@podkit/devices-ipod` tables — no other package changes. - -Also split `usb-discovery.ts` into `usb-enumeration.ts` (bus walk) + `usb-path-resolution.ts` (mount-path → fingerprint resolver) since those two concerns were unrelated. diff --git a/.changeset/usb-error-reporting.md b/.changeset/usb-error-reporting.md deleted file mode 100644 index f0f96f1d..00000000 --- a/.changeset/usb-error-reporting.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"podkit": patch -"@podkit/core": patch -"@podkit/libgpod-node": patch ---- - -Fix `doctor --repair sysinfo-extended` showing unhelpful "Could not read device identity from USB" with no detail. The native USB binding now throws descriptive errors (e.g. "USB control transfer failed (bus 3, device 4)") instead of returning null silently. Also fix all doctor repair intro messages — they incorrectly said "Repairing X for N tracks" even for non-track operations like SysInfoExtended and orphan cleanup. Intro messages now use each repair's own description. diff --git a/.changeset/usb-inquiry-consolidation.md b/.changeset/usb-inquiry-consolidation.md deleted file mode 100644 index ccc1c4b3..00000000 --- a/.changeset/usb-inquiry-consolidation.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@podkit/libgpod-node': minor -'@podkit/ipod-firmware': minor -'podkit': patch ---- - -USB firmware inquiry consolidated into @podkit/ipod-firmware (P2 — m-18 device-capability architecture). - -**Breaking change in `@podkit/libgpod-node`:** The `readSysInfoExtendedFromUsb` function has been removed from the package's public exports. All in-tree callers were already routed through `@podkit/ipod-firmware` since P1 — only external consumers of `@podkit/libgpod-node` who called this function directly are affected. - -`@podkit/ipod-firmware` now owns the complete firmware inquiry surface: SCSI (Linux SG_IO + macOS IOKit) and USB (direct libusb-1.0 via koffi FFI). The P1 transitional shim that delegated USB reads to libgpod-node has been replaced by a native TypeScript implementation. No API change is visible to callers of `@podkit/ipod-firmware`. - -`@podkit/libgpod-node` no longer requires libusb at build or runtime. Distro packagers can now build the native binding without `libusb-1.0-0-dev` (Debian/Ubuntu), `libusb-devel` (Fedora/RHEL), or equivalent system packages. The `itdb_usb.c` patch, the `dlsym` shim, and the libusb pkg-config dependency have all been removed from the binding. - -No user-facing CLI behaviour changes. `podkit doctor` inquiry checks, `podkit device scan`, and all sync paths behave identically to P1. diff --git a/.changeset/usb-via-npm-package.md b/.changeset/usb-via-npm-package.md deleted file mode 100644 index dfdb8882..00000000 --- a/.changeset/usb-via-npm-package.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -"podkit": minor -"@podkit/core": minor -"@podkit/ipod-firmware": minor ---- - -Replace koffi-based libusb FFI with the `usb` npm package for USB firmware inquiry, eliminating the runtime libusb system dependency. - -The `@podkit/ipod-firmware` USB transport now uses the `usb` npm package, whose prebuilt N-API bindings statically link libusb. End-user binaries embed that prebuild via Bun `--compile`; no system `libusb-1.0` is required at runtime. - -Public-surface changes in `@podkit/ipod-firmware`: - -- **Removed:** `loadLibusb`, `LibusbBinding`, `LibusbPtr`, `LibusbLoadResult`, `_resetLibusbCacheForTests`. The koffi-shaped binding interface is gone. -- **Added:** `loadUsb`, `UsbBinding`, `UsbDeviceHandle`, `UsbLoadResult`, `_resetUsbCacheForTests`. Higher-level `withOpenDevice(bus, devnum, fn)` seam — implementations handle enumeration, open, and cleanup internally. -- **Added:** `setLogger(fn | null)`, `FirmwareLogger`, `FirmwareLogEvent`. Library no longer writes to stderr/stdout; consumers install a receiver and decide format/destination. The CLI installs one when `-v` is passed. -- **Added:** `@podkit/ipod-firmware/bundle` subpath export with `bundleUsbNative(nativeModule)` for single-file binary builds. See `agents/ipod-firmware.md` for the staging recipe. -- **Renamed:** `UsbInquiryError.libusbCode` → `UsbInquiryError.libusbStatus`. The new field carries `LIBUSB_TRANSFER_*` status codes (positive enum) from the `usb` npm package, not the negative `LIBUSB_ERROR_*` codes the koffi path returned. - -Doctor's `inquiry-methods` check no longer reports libusb availability — the USB transport is bundled and always present in shipped binaries. The check now reports SCSI transport availability only, which remains user-actionable on Linux (udev permissions) and macOS (iPodDriver.kext). diff --git a/bun.lock b/bun.lock index a59e1811..b67f5ebe 100644 --- a/bun.lock +++ b/bun.lock @@ -18,7 +18,7 @@ }, "packages/compatibility": { "name": "@podkit/compatibility", - "version": "0.0.1", + "version": "0.0.2", "dependencies": { "@podkit/libgpod-node": "workspace:*", }, @@ -35,7 +35,7 @@ }, "packages/device-testing": { "name": "@podkit/device-testing", - "version": "0.0.1", + "version": "0.0.2", "dependencies": { "@podkit/core": "workspace:*", "@podkit/device-types": "workspace:*", @@ -46,14 +46,14 @@ }, "packages/device-types": { "name": "@podkit/device-types", - "version": "0.0.1", + "version": "0.1.0", "devDependencies": { "@types/bun": "latest", }, }, "packages/devices-ipod": { "name": "@podkit/devices-ipod", - "version": "0.0.1", + "version": "0.1.0", "dependencies": { "@podkit/device-types": "workspace:*", "@podkit/ipod-firmware": "workspace:*", @@ -64,7 +64,7 @@ }, "packages/devices-mass-storage": { "name": "@podkit/devices-mass-storage", - "version": "0.0.1", + "version": "0.1.0", "dependencies": { "@podkit/device-types": "workspace:*", }, @@ -74,7 +74,7 @@ }, "packages/docs-site": { "name": "@podkit/docs-site", - "version": "0.0.2", + "version": "0.0.3", "dependencies": { "@astrojs/starlight": "^0.33.0", "@podkit/compatibility": "workspace:*", @@ -122,7 +122,7 @@ }, "packages/ipod-firmware": { "name": "@podkit/ipod-firmware", - "version": "0.0.1", + "version": "0.1.0", "dependencies": { "@podkit/device-types": "workspace:*", "koffi": "^2.13.0", @@ -160,7 +160,7 @@ }, "packages/libgpod-node": { "name": "@podkit/libgpod-node", - "version": "0.1.0", + "version": "0.2.0", "dependencies": { "node-addon-api": "^8.3.1", }, @@ -173,7 +173,7 @@ }, "packages/podkit-cli": { "name": "podkit", - "version": "0.6.0", + "version": "0.7.0", "bin": { "podkit": "./dist/main.js", }, @@ -196,7 +196,7 @@ }, "packages/podkit-core": { "name": "@podkit/core", - "version": "0.6.0", + "version": "0.7.0", "dependencies": { "@ctrl/video-filename-parser": "^5.4.1", "@podkit/device-types": "workspace:*", @@ -217,7 +217,7 @@ }, "packages/podkit-daemon": { "name": "@podkit/daemon", - "version": "0.2.2", + "version": "0.3.0", "dependencies": { "@podkit/core": "workspace:*", }, @@ -227,7 +227,7 @@ }, "packages/podkit-docker": { "name": "@podkit/docker", - "version": "0.2.3", + "version": "0.2.4", "dependencies": { "@podkit/daemon": "workspace:*", "podkit": "workspace:*", @@ -235,7 +235,7 @@ }, "packages/test-fixtures": { "name": "@podkit/test-fixtures", - "version": "0.0.0", + "version": "0.1.0", "devDependencies": { "@types/bun": "latest", "typescript": "^5.9.0", diff --git a/packages/compatibility/CHANGELOG.md b/packages/compatibility/CHANGELOG.md index e229b682..30a85186 100644 --- a/packages/compatibility/CHANGELOG.md +++ b/packages/compatibility/CHANGELOG.md @@ -1,5 +1,12 @@ # @podkit/compatibility +## 0.0.2 + +### Patch Changes + +- Updated dependencies [[`22dddf4`](https://github.com/jvgomg/podkit/commit/22dddf4803f4cfd7b004d80dffd83878a68b10f2), [`1ec30ac`](https://github.com/jvgomg/podkit/commit/1ec30acca1109178012db3913a60967a2087fb5b), [`6000868`](https://github.com/jvgomg/podkit/commit/6000868830d9437a6fff3c1a77adb254d9579fe7), [`4598f8f`](https://github.com/jvgomg/podkit/commit/4598f8f3347cf40b94fdf1585215e5b0f54d9cf6)]: + - @podkit/libgpod-node@0.2.0 + ## 0.0.1 ### Patch Changes diff --git a/packages/compatibility/package.json b/packages/compatibility/package.json index b84e1f55..c61984c3 100644 --- a/packages/compatibility/package.json +++ b/packages/compatibility/package.json @@ -1,7 +1,7 @@ { "name": "@podkit/compatibility", "private": true, - "version": "0.0.1", + "version": "0.0.2", "description": "iPod model compatibility data — single source of truth for E2E tests and docs", "type": "module", "main": "./src/index.ts", diff --git a/packages/device-testing/CHANGELOG.md b/packages/device-testing/CHANGELOG.md new file mode 100644 index 00000000..92c9af1a --- /dev/null +++ b/packages/device-testing/CHANGELOG.md @@ -0,0 +1,9 @@ +# @podkit/device-testing + +## 0.0.2 + +### Patch Changes + +- Updated dependencies [[`0f3e4dd`](https://github.com/jvgomg/podkit/commit/0f3e4ddae134228b5e874b21db33f74547867b6c), [`036b107`](https://github.com/jvgomg/podkit/commit/036b1077748253385b6f4ff873a7cdb52c54b004), [`89ff40c`](https://github.com/jvgomg/podkit/commit/89ff40c2adedd9fec38ae5ad0eb89b75525642f2), [`c5c0236`](https://github.com/jvgomg/podkit/commit/c5c0236c232cc3fa086fd3937b0e2fbe0f326185), [`0d4a4c2`](https://github.com/jvgomg/podkit/commit/0d4a4c2bd98667989b9631d981e609bc72e604af), [`513173d`](https://github.com/jvgomg/podkit/commit/513173d1832bf9ca2894214e97d9d65cf02c52a5), [`0cc39d3`](https://github.com/jvgomg/podkit/commit/0cc39d3c62343591127d5c79deed2478f8dc4f60), [`22dddf4`](https://github.com/jvgomg/podkit/commit/22dddf4803f4cfd7b004d80dffd83878a68b10f2), [`348f2c5`](https://github.com/jvgomg/podkit/commit/348f2c53cec06598903b5cf128663d5121c46865), [`7534c2f`](https://github.com/jvgomg/podkit/commit/7534c2f19d81087413af8abbf764fe20cef61384), [`d1147e4`](https://github.com/jvgomg/podkit/commit/d1147e4a65ac103608da3730f530f6deab3cd0b6), [`6747667`](https://github.com/jvgomg/podkit/commit/6747667049cd793fdb13e3d1bc1092651f8e969c), [`8bc3126`](https://github.com/jvgomg/podkit/commit/8bc3126ec415aa836b746ec921b6738abdd9e538), [`01ecedd`](https://github.com/jvgomg/podkit/commit/01ecedde623ff99e94c5cbda75ff9f9c9ecef632), [`03f1046`](https://github.com/jvgomg/podkit/commit/03f1046b70898b0282d0c96927bca60ee0d55eeb), [`14d83e5`](https://github.com/jvgomg/podkit/commit/14d83e5e59eb0a8a801850de775f9fdb4c0e7aa9), [`3db3d88`](https://github.com/jvgomg/podkit/commit/3db3d887ae2cd19d01ba2c1f00b8682e783fac84), [`7ebb7c5`](https://github.com/jvgomg/podkit/commit/7ebb7c5c0e1c7c3d549196347029d9ce660fcb8b), [`34e8bf2`](https://github.com/jvgomg/podkit/commit/34e8bf2341111df1e8f85361b8047eed9f31665a), [`3e95baf`](https://github.com/jvgomg/podkit/commit/3e95baffc65b683b5e3f80906e9a342245a6e4ce), [`bddea04`](https://github.com/jvgomg/podkit/commit/bddea044342ca9027fc95593a35795fd8de1faf4), [`09c4acd`](https://github.com/jvgomg/podkit/commit/09c4acdec349f200a649b2db15fe05345e380a7b), [`94c85d2`](https://github.com/jvgomg/podkit/commit/94c85d2a9d6c85875432a0ebecab540a9ebd67d7), [`efa14c6`](https://github.com/jvgomg/podkit/commit/efa14c623e7bda81066bd77142cddb28e4de615d), [`208e482`](https://github.com/jvgomg/podkit/commit/208e482db9730064a25e53e03121bdcfcbea6341), [`bb96778`](https://github.com/jvgomg/podkit/commit/bb96778dde9063267188b2b83535ec279cd5c550), [`f72fa01`](https://github.com/jvgomg/podkit/commit/f72fa0170872fc0a6e5719b4509abae24e6414cd), [`c9c268e`](https://github.com/jvgomg/podkit/commit/c9c268ea4b25b39543e5c53a1928e72b4c31e0c8), [`1ec30ac`](https://github.com/jvgomg/podkit/commit/1ec30acca1109178012db3913a60967a2087fb5b), [`80fe65a`](https://github.com/jvgomg/podkit/commit/80fe65a022c65da512f571a8abf83f9385a649e6), [`52894c1`](https://github.com/jvgomg/podkit/commit/52894c1977bccd51a86929debfbaa7028a19dd61), [`c5cba69`](https://github.com/jvgomg/podkit/commit/c5cba6998283663b42659f02b17b194ab256c137), [`1c3ebc3`](https://github.com/jvgomg/podkit/commit/1c3ebc381276accdb8361f50454b90c75f2391df), [`f61a83b`](https://github.com/jvgomg/podkit/commit/f61a83b3a2d13612730f174759fd3b86edd42e82), [`6000868`](https://github.com/jvgomg/podkit/commit/6000868830d9437a6fff3c1a77adb254d9579fe7), [`e825ee1`](https://github.com/jvgomg/podkit/commit/e825ee1dd4933ecbfd070dda27f96f43056f0baf)]: + - @podkit/core@0.7.0 + - @podkit/device-types@0.1.0 diff --git a/packages/device-testing/package.json b/packages/device-testing/package.json index 8cd79220..78b5a1cb 100644 --- a/packages/device-testing/package.json +++ b/packages/device-testing/package.json @@ -1,6 +1,6 @@ { "name": "@podkit/device-testing", - "version": "0.0.1", + "version": "0.0.2", "description": "Device persona + system-state fixtures and test runtime harness for podkit's three-tier test stack", "type": "module", "main": "./dist/index.js", diff --git a/packages/device-types/CHANGELOG.md b/packages/device-types/CHANGELOG.md new file mode 100644 index 00000000..f1444e7f --- /dev/null +++ b/packages/device-types/CHANGELOG.md @@ -0,0 +1,48 @@ +# @podkit/device-types + +## 0.1.0 + +### Minor Changes + +- [`0d4a4c2`](https://github.com/jvgomg/podkit/commit/0d4a4c2bd98667989b9631d981e609bc72e604af) Thanks [@jvgomg](https://github.com/jvgomg)! - Disambiguate codec from container: `AudioCodec` value `'ogg'` is renamed to `'vorbis'` + + The `AudioCodec` slot previously used `'ogg'` to mean "OGG Vorbis." That conflated the OGG container with the Vorbis stream codec and could not represent Vorbis-in-OGG vs Opus-in-OGG as distinct device capabilities — Echo Mini (which plays Vorbis but hides `.opus` files) could not be modelled accurately. The codec slot now names the audio stream codec; `'vorbis'` replaces `'ogg'` in device presets and config. + + Configs containing `supportedAudioCodecs = ["…", "ogg", "…"]` under `[devices.*]` are migrated automatically by `podkit migrate` (config version 1 → 2). The migration is purely a string substitution inside the `supportedAudioCodecs` array; comments and surrounding formatting are preserved. + + Also lands as type-level groundwork for the future container-aware sync work: `AudioContainer`, `AUDIO_CONTAINERS`, `CODEC_CANONICAL_CONTAINER`, and an optional `DeviceCapabilities.containerConstraints` field. These are declared and exported but not yet read by the planner; they are placeholders for the upcoming Phase 2 work documented in the container-aware sync PRD. + + `DirectoryAdapter` now uses each `.ogg` file's probed stream codec (already populated by `music-metadata`) to distinguish Vorbis, Opus, and OGG-FLAC — same pattern as the existing AAC/ALAC distinction for `.m4a`. `SubsonicAdapter` additionally checks the API's `contentType` field for Opus-in-`.ogg`. The Subsonic check is best-effort because most Subsonic servers report container MIME (`audio/ogg`) regardless of stream codec; deeper probing is deferred until evidence of real-world impact. + + User-facing reference page added at `docs/reference/codec-support.md` explaining the codec/container model and what each `AudioCodec` value means. + +- [`d1147e4`](https://github.com/jvgomg/podkit/commit/d1147e4a65ac103608da3730f530f6deab3cd0b6) Thanks [@jvgomg](https://github.com/jvgomg)! - P4 — device-capability architecture complete. All in-tree migration finished; deprecated shims removed. + + **Breaking changes in `@podkit/core`:** The following symbols have been removed from the public API: `createIpodCapabilities`, `LibgpodDeviceInfo`, `DEVICE_PRESETS`, `DevicePreset`, `getDevicePreset`, and `resolveDeviceCapabilities`. Callers must migrate before upgrading. + + Migration guide: + - `createIpodCapabilities(libgpodInfo)` → `resolveCapabilities(identity)` (preferred, identity-driven) or `resolveIpodModelCapabilities(modelFromLibgpodInfo(libgpodInfo))` for callers that genuinely hold libgpod data. + - `getDevicePreset(deviceType)` → `BUILT_IN_PRESETS[deviceType]` from `@podkit/devices-mass-storage`. + - `DEVICE_PRESETS` → `BUILT_IN_PRESETS` from `@podkit/devices-mass-storage`. + - `resolveDeviceCapabilities(type, overrides)` → `resolveCapabilities(identity, { overrides })` from `@podkit/core`. + - The `core/device/sysinfo-extended` shim path is gone; import `readSysInfoExtended`, `writeSysInfoExtended`, and `ensureSysInfoExtended` from `@podkit/ipod-firmware` directly. + + See ADR-295.07 for the full architectural rationale. + + **New in `@podkit/ipod-firmware`:** SysInfoExtended file I/O is now owned by this package: `readSysInfoExtended`, `writeSysInfoExtended`, `ensureSysInfoExtended`, `SYSINFO_EXTENDED_PATH`, `SYSINFO_DEVICE_DIR`. Diagnostic helpers `compareSysInfoConsistency` and `normaliseFireWireGuid` are also exported. `ParsedFirmware` gains the optional `modelNumber` field (populated from the `ModelNumStr` plist key when present). + + **New in `@podkit/device-types`:** `IpodModel`, `IpodChecksumType`, `IpodGenerationId`, `IpodGenerationIdLike`, `IpodModelSource`, and `IPOD_GENERATION_IDS` are now exported from this package (canonical home). `UsbConnectionInfo` has been removed — use `UsbFingerprint` instead. `IpodIdentity.notSupportedReason` is added for devices identified as iPods that podkit cannot fully support (e.g. iOS-mode devices). `DeviceCapabilities.artworkMaxResolution` is now `number | null` (null when the generation has no known limit or artwork is unsupported). + + **In `@podkit/devices-ipod`:** `IpodGeneration.supported` and `IpodGeneration.artworkMaxResolution: number | null` are new fields on every generation entry. `lookupByFamilyId` and `FAMILY_ID_TO_GENERATION` are now exported. `unsupported.ts` is populated with comprehensive Apple iOS device PIDs to allow early rejection of phones and tablets at the USB identification stage. + + No user-facing CLI behaviour changes. `podkit device scan`, `podkit device info`, and all sync paths behave identically to P3. + +- [`bddea04`](https://github.com/jvgomg/podkit/commit/bddea044342ca9027fc95593a35795fd8de1faf4) Thanks [@jvgomg](https://github.com/jvgomg)! - Add SCSI firmware inquiry for iPod identification (P1 — m-18 device-capability architecture). + + `@podkit/device-types` (first published release) provides the canonical shared type definitions — `DeviceCapabilities`, `DeviceIdentity`, `ParsedFirmware`, and `DeviceProvider` — used across the podkit monorepo without circular dependencies. + + `@podkit/ipod-firmware` (first published release) implements iPod firmware inquiry via SCSI (Linux SG_IO + macOS IOKit, using koffi FFI) with USB fallback through the existing libgpod-node binding. Devices that previously failed identification over USB — including iPod mini 2G, nano 2G, and some iPod 5G Video configurations — can now be identified via SCSI. The orchestrator probes available transports at startup, prefers USB when both are available, and falls back to SCSI transparently. + + `@podkit/core` now routes `ensureSysInfoExtended` through the new orchestrator with SCSI fallback, and registers two new `podkit doctor` checks: `inquiry-methods` (reports which transports are available on this host) and `sysinfo-consistency` (validates that the on-disk SysInfo file matches the live firmware read). EACCES errors from SCSI include step-by-step recovery instructions. + + `podkit` CLI gains `--repair udev-rule` in `podkit doctor` to install the Linux udev rule that grants non-root `/dev/sg*` access, and surfaces the new doctor checks in the readiness output. diff --git a/packages/device-types/package.json b/packages/device-types/package.json index ca83b140..b992f2a7 100644 --- a/packages/device-types/package.json +++ b/packages/device-types/package.json @@ -1,6 +1,6 @@ { "name": "@podkit/device-types", - "version": "0.0.1", + "version": "0.1.0", "description": "Shared device capability and identity types for podkit", "type": "module", "main": "./dist/index.js", diff --git a/packages/devices-ipod/CHANGELOG.md b/packages/devices-ipod/CHANGELOG.md new file mode 100644 index 00000000..e041c4bd --- /dev/null +++ b/packages/devices-ipod/CHANGELOG.md @@ -0,0 +1,80 @@ +# @podkit/devices-ipod + +## 0.1.0 + +### Minor Changes + +- [`0d4a4c2`](https://github.com/jvgomg/podkit/commit/0d4a4c2bd98667989b9631d981e609bc72e604af) Thanks [@jvgomg](https://github.com/jvgomg)! - Disambiguate codec from container: `AudioCodec` value `'ogg'` is renamed to `'vorbis'` + + The `AudioCodec` slot previously used `'ogg'` to mean "OGG Vorbis." That conflated the OGG container with the Vorbis stream codec and could not represent Vorbis-in-OGG vs Opus-in-OGG as distinct device capabilities — Echo Mini (which plays Vorbis but hides `.opus` files) could not be modelled accurately. The codec slot now names the audio stream codec; `'vorbis'` replaces `'ogg'` in device presets and config. + + Configs containing `supportedAudioCodecs = ["…", "ogg", "…"]` under `[devices.*]` are migrated automatically by `podkit migrate` (config version 1 → 2). The migration is purely a string substitution inside the `supportedAudioCodecs` array; comments and surrounding formatting are preserved. + + Also lands as type-level groundwork for the future container-aware sync work: `AudioContainer`, `AUDIO_CONTAINERS`, `CODEC_CANONICAL_CONTAINER`, and an optional `DeviceCapabilities.containerConstraints` field. These are declared and exported but not yet read by the planner; they are placeholders for the upcoming Phase 2 work documented in the container-aware sync PRD. + + `DirectoryAdapter` now uses each `.ogg` file's probed stream codec (already populated by `music-metadata`) to distinguish Vorbis, Opus, and OGG-FLAC — same pattern as the existing AAC/ALAC distinction for `.m4a`. `SubsonicAdapter` additionally checks the API's `contentType` field for Opus-in-`.ogg`. The Subsonic check is best-effort because most Subsonic servers report container MIME (`audio/ogg`) regardless of stream codec; deeper probing is deferred until evidence of real-world impact. + + User-facing reference page added at `docs/reference/codec-support.md` explaining the codec/container model and what each `AudioCodec` value means. + +### Patch Changes + +- [`d1147e4`](https://github.com/jvgomg/podkit/commit/d1147e4a65ac103608da3730f530f6deab3cd0b6) Thanks [@jvgomg](https://github.com/jvgomg)! - P4 — device-capability architecture complete. All in-tree migration finished; deprecated shims removed. + + **Breaking changes in `@podkit/core`:** The following symbols have been removed from the public API: `createIpodCapabilities`, `LibgpodDeviceInfo`, `DEVICE_PRESETS`, `DevicePreset`, `getDevicePreset`, and `resolveDeviceCapabilities`. Callers must migrate before upgrading. + + Migration guide: + - `createIpodCapabilities(libgpodInfo)` → `resolveCapabilities(identity)` (preferred, identity-driven) or `resolveIpodModelCapabilities(modelFromLibgpodInfo(libgpodInfo))` for callers that genuinely hold libgpod data. + - `getDevicePreset(deviceType)` → `BUILT_IN_PRESETS[deviceType]` from `@podkit/devices-mass-storage`. + - `DEVICE_PRESETS` → `BUILT_IN_PRESETS` from `@podkit/devices-mass-storage`. + - `resolveDeviceCapabilities(type, overrides)` → `resolveCapabilities(identity, { overrides })` from `@podkit/core`. + - The `core/device/sysinfo-extended` shim path is gone; import `readSysInfoExtended`, `writeSysInfoExtended`, and `ensureSysInfoExtended` from `@podkit/ipod-firmware` directly. + + See ADR-295.07 for the full architectural rationale. + + **New in `@podkit/ipod-firmware`:** SysInfoExtended file I/O is now owned by this package: `readSysInfoExtended`, `writeSysInfoExtended`, `ensureSysInfoExtended`, `SYSINFO_EXTENDED_PATH`, `SYSINFO_DEVICE_DIR`. Diagnostic helpers `compareSysInfoConsistency` and `normaliseFireWireGuid` are also exported. `ParsedFirmware` gains the optional `modelNumber` field (populated from the `ModelNumStr` plist key when present). + + **New in `@podkit/device-types`:** `IpodModel`, `IpodChecksumType`, `IpodGenerationId`, `IpodGenerationIdLike`, `IpodModelSource`, and `IPOD_GENERATION_IDS` are now exported from this package (canonical home). `UsbConnectionInfo` has been removed — use `UsbFingerprint` instead. `IpodIdentity.notSupportedReason` is added for devices identified as iPods that podkit cannot fully support (e.g. iOS-mode devices). `DeviceCapabilities.artworkMaxResolution` is now `number | null` (null when the generation has no known limit or artwork is unsupported). + + **In `@podkit/devices-ipod`:** `IpodGeneration.supported` and `IpodGeneration.artworkMaxResolution: number | null` are new fields on every generation entry. `lookupByFamilyId` and `FAMILY_ID_TO_GENERATION` are now exported. `unsupported.ts` is populated with comprehensive Apple iOS device PIDs to allow early rejection of phones and tablets at the USB identification stage. + + No user-facing CLI behaviour changes. `podkit device scan`, `podkit device info`, and all sync paths behave identically to P3. + +- [`01ecedd`](https://github.com/jvgomg/podkit/commit/01ecedde623ff99e94c5cbda75ff9f9c9ecef632) Thanks [@jvgomg](https://github.com/jvgomg)! - New packages: `@podkit/devices-ipod` (canonical home for iPod generation tables, model lookups, and capability synthesis) and `@podkit/devices-mass-storage` (user-extensible DAP preset framework for Echo Mini, Rockbox, generic, and custom devices). + + Echo Mini is now auto-detected at `device add` — when the USB descriptor matches the known VID/PID (`0x071b`/`0x3203`), no `--type echo-mini` flag is required. + + `enumerateConnectedDevices` is now the recommended way to discover and classify USB devices. It accepts a `providers: DeviceProvider[]` array and returns `EnumeratedDevice[]` carrying both the USB connection info and the provider-produced identity. + + `getCapabilities` in `@podkit/devices-ipod` is libgpod-free. Capability synthesis is purely table-and-firmware-driven; the legacy `createIpodCapabilities` adapter that depended on a live libgpod `LibgpodDeviceInfo` struct is deprecated in `@podkit/core`. Parity is verified across all 29 generations (the 4 that were libgpod `unknown` degenerate cases are now correctly populated from the table). + + Internal re-export shims in `@podkit/core` keep all existing call paths compiling for one release. The shims delegate to `@podkit/devices-ipod` and `@podkit/devices-mass-storage` and will be removed in P4. + +- [`3e95baf`](https://github.com/jvgomg/podkit/commit/3e95baffc65b683b5e3f80906e9a342245a6e4ce) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix iPod model identification regressing to "Unknown iPod" after `doctor --repair sysinfo-extended` on pre-2006 devices (mini 2G), and tighten the package-boundary contract so consumers compose identity instead of injecting resolution policy. + + The bug: each consumer of `ensureSysInfoExtended` / `readSysInfoExtended` passed a serial-only `resolveModel` callback. When the 3-character serial suffix wasn't in `tables/serials.ts`, the resolver returned undefined and the device was displayed as "Unknown iPod" — even when a SysInfo file with a known `ModelNumStr` was sitting next to the SysInfoExtended on disk. + + The fix: + - **Removed** `ModelResolver` type and the `resolveModel` callback from `@podkit/ipod-firmware`. `readSysInfoExtended` and `ensureSysInfoExtended` now return a flat `SysInfoIdentity` bag (`firewireGuid?, serialNumber?, modelNumStr?, familyId?`). When a SysInfo file is on disk alongside SysInfoExtended, its `ModelNumStr` is read opportunistically. + - **Callers compose** with `resolveIpodModel(bag)` from `@podkit/devices-ipod`, which cascades modelNumStr → serial → productId → familyId → libgpodGeneration. The CLI no longer makes resolution decisions. + - **Added** `SYSINFO_PATH`, `SYSINFO_EXTENDED_PATH`, `SYSINFO_DEVICE_DIR` exported from `@podkit/ipod-firmware` and re-exported from `@podkit/core`. Consumers use these constants instead of duplicating the literal `iPod_Control/Device/...` paths. + - **Added** `S4G: '9804'` entry to `tables/serials.ts` (mini 2G 4GB Pink, sourced from real hardware, serial `JQ5141TFS4G`). + - **Post-write enrichment.** After `ensureSysInfoExtended` writes the file via USB inquiry, it now re-reads via `readSysInfoExtended` so the post-write identity bag includes `modelNumStr` from the SysInfo neighbour. Eliminates the cosmetic regression where the repair-success message showed a less-specific name than the subsequent `doctor` run. + +- [`f61a83b`](https://github.com/jvgomg/podkit/commit/f61a83b3a2d13612730f174759fd3b86edd42e82) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix `podkit device scan` reporting phantom "Unknown iPod (USB only)" entries for non-iPod USB peripherals (mice, hubs, Thunderbolt docks, Ethernet adapters, USB drives). Each phantom suggested `podkit device init` — a destructive operation that could mutate an unrelated device. + + Root cause was architectural: a single `discoverUsbIpods()` function mixed three concerns — USB enumeration, iPod-domain enrichment, and the function name's implied filter (which it didn't actually do). Refactored into clean layers: + - **`@podkit/core` — pure USB enumeration.** `enumerateUsb()` returns `EnumeratedUsbDevice[]` with vendor/product/serial/bus/devnum/diskIdentifier ONLY. No iPod-domain knowledge. + - **`@podkit/devices-ipod` — iPod classifier.** `classifyAsIpod(dev)` returns `IpodClassification | null` (matches Apple-vendor with iPod or iOS PIDs). + - **`@podkit/devices-mass-storage` — mass-storage classifier.** `classifyAsMassStorage(dev)` returns `MassStorageClassification | null` (matches `USB_PRESET_HINTS` entries like Echo Mini). + - **`@podkit/core` composer.** `classifyUsbDevices()` runs both classifiers and returns recognized devices as a tagged union; drops unknown peripherals. + - **CLI `device scan`** now calls `enumerateUsb()` → `classifyUsbDevices()` → renders by `kind`. No domain logic in the command layer. + + Added `kind: 'mass-storage'` rendering branch so mass-storage DAPs are no longer mis-labeled as "Unknown iPod". + + Removed `discoverUsbIpods` and the leaky `UsbDiscoveredDevice` type (which previously carried iPod-domain fields). Adding a new mass-storage device now means adding one entry to `USB_PRESET_HINTS` — no `@podkit/core` change required. Adding a new iPod generation means updating `@podkit/devices-ipod` tables — no other package changes. + + Also split `usb-discovery.ts` into `usb-enumeration.ts` (bus walk) + `usb-path-resolution.ts` (mount-path → fingerprint resolver) since those two concerns were unrelated. + +- Updated dependencies [[`bb2e637`](https://github.com/jvgomg/podkit/commit/bb2e6374151605d11baf052c452f10a842e5353e), [`0d4a4c2`](https://github.com/jvgomg/podkit/commit/0d4a4c2bd98667989b9631d981e609bc72e604af), [`d1147e4`](https://github.com/jvgomg/podkit/commit/d1147e4a65ac103608da3730f530f6deab3cd0b6), [`3e95baf`](https://github.com/jvgomg/podkit/commit/3e95baffc65b683b5e3f80906e9a342245a6e4ce), [`bddea04`](https://github.com/jvgomg/podkit/commit/bddea044342ca9027fc95593a35795fd8de1faf4), [`80fe65a`](https://github.com/jvgomg/podkit/commit/80fe65a022c65da512f571a8abf83f9385a649e6), [`4598f8f`](https://github.com/jvgomg/podkit/commit/4598f8f3347cf40b94fdf1585215e5b0f54d9cf6), [`e825ee1`](https://github.com/jvgomg/podkit/commit/e825ee1dd4933ecbfd070dda27f96f43056f0baf)]: + - @podkit/ipod-firmware@0.1.0 + - @podkit/device-types@0.1.0 diff --git a/packages/devices-ipod/package.json b/packages/devices-ipod/package.json index 5feb6bc2..7e0d62c0 100644 --- a/packages/devices-ipod/package.json +++ b/packages/devices-ipod/package.json @@ -1,6 +1,6 @@ { "name": "@podkit/devices-ipod", - "version": "0.0.1", + "version": "0.1.0", "description": "iPod generation tables, USB ID lookup, and model identification for podkit", "type": "module", "main": "./dist/index.js", diff --git a/packages/devices-mass-storage/CHANGELOG.md b/packages/devices-mass-storage/CHANGELOG.md new file mode 100644 index 00000000..e816a6a0 --- /dev/null +++ b/packages/devices-mass-storage/CHANGELOG.md @@ -0,0 +1,77 @@ +# @podkit/devices-mass-storage + +## 0.1.0 + +### Minor Changes + +- [`0d4a4c2`](https://github.com/jvgomg/podkit/commit/0d4a4c2bd98667989b9631d981e609bc72e604af) Thanks [@jvgomg](https://github.com/jvgomg)! - Disambiguate codec from container: `AudioCodec` value `'ogg'` is renamed to `'vorbis'` + + The `AudioCodec` slot previously used `'ogg'` to mean "OGG Vorbis." That conflated the OGG container with the Vorbis stream codec and could not represent Vorbis-in-OGG vs Opus-in-OGG as distinct device capabilities — Echo Mini (which plays Vorbis but hides `.opus` files) could not be modelled accurately. The codec slot now names the audio stream codec; `'vorbis'` replaces `'ogg'` in device presets and config. + + Configs containing `supportedAudioCodecs = ["…", "ogg", "…"]` under `[devices.*]` are migrated automatically by `podkit migrate` (config version 1 → 2). The migration is purely a string substitution inside the `supportedAudioCodecs` array; comments and surrounding formatting are preserved. + + Also lands as type-level groundwork for the future container-aware sync work: `AudioContainer`, `AUDIO_CONTAINERS`, `CODEC_CANONICAL_CONTAINER`, and an optional `DeviceCapabilities.containerConstraints` field. These are declared and exported but not yet read by the planner; they are placeholders for the upcoming Phase 2 work documented in the container-aware sync PRD. + + `DirectoryAdapter` now uses each `.ogg` file's probed stream codec (already populated by `music-metadata`) to distinguish Vorbis, Opus, and OGG-FLAC — same pattern as the existing AAC/ALAC distinction for `.m4a`. `SubsonicAdapter` additionally checks the API's `contentType` field for Opus-in-`.ogg`. The Subsonic check is best-effort because most Subsonic servers report container MIME (`audio/ogg`) regardless of stream codec; deeper probing is deferred until evidence of real-world impact. + + User-facing reference page added at `docs/reference/codec-support.md` explaining the codec/container model and what each `AudioCodec` value means. + +- [`c5cba69`](https://github.com/jvgomg/podkit/commit/c5cba6998283663b42659f02b17b194ab256c137) Thanks [@jvgomg](https://github.com/jvgomg)! - Polish on the convergent-metadata work (TASK-327 follow-up): + - **Tag-write concurrency cap.** `save()` now caps in-flight tag writes at 16 via a small `runWithConcurrency` helper instead of firing every pending write at once. Avoids `EMFILE` on large libraries. + - **Aggregated tag-write errors.** Failure messages now begin with `tag write failed` so the executor's error categorizer classifies them as file-I/O (`copy`) rather than risking a path-keyword mis-classification. + - **WAV/AIFF on mass-storage.** Podkit transcodes WAV and AIFF source files to a managed codec before placing them on a mass-storage device, even when the device firmware can play them. RIFF/IFF tag-writing is unreliable. Presets continue to list these codecs for documentation. iPod is unaffected (libgpod / iTunesDB handle metadata for WAV/AIFF). + - **OGG Vorbis tag round-trip tests.** Now run on builds with libvorbis (skipped automatically when absent). + - **Shared TagFields helpers.** `buildTagFieldsFromInput` and `diffTagFields` replace three duplicate field-by-field walks across adapters. + - **`TransferMode` type unified.** Removed `'fast' | 'optimized' | 'portable'` duplication between `DeviceTrackInput`, `DeviceTrackMetadata`, and the canonical `TransferMode` in `transcode/types.ts`. Drops several inline type casts. + - **Docs.** `transferMode` now has a dedicated section in `docs/reference/config-file.md` explaining the iPod vs mass-storage contract and migration churn. `pathTemplate` (from the prior release) and `PODKIT_PATH_TEMPLATE` are now documented in the config reference and environment-variables reference. + +### Patch Changes + +- [`d1147e4`](https://github.com/jvgomg/podkit/commit/d1147e4a65ac103608da3730f530f6deab3cd0b6) Thanks [@jvgomg](https://github.com/jvgomg)! - P4 — device-capability architecture complete. All in-tree migration finished; deprecated shims removed. + + **Breaking changes in `@podkit/core`:** The following symbols have been removed from the public API: `createIpodCapabilities`, `LibgpodDeviceInfo`, `DEVICE_PRESETS`, `DevicePreset`, `getDevicePreset`, and `resolveDeviceCapabilities`. Callers must migrate before upgrading. + + Migration guide: + - `createIpodCapabilities(libgpodInfo)` → `resolveCapabilities(identity)` (preferred, identity-driven) or `resolveIpodModelCapabilities(modelFromLibgpodInfo(libgpodInfo))` for callers that genuinely hold libgpod data. + - `getDevicePreset(deviceType)` → `BUILT_IN_PRESETS[deviceType]` from `@podkit/devices-mass-storage`. + - `DEVICE_PRESETS` → `BUILT_IN_PRESETS` from `@podkit/devices-mass-storage`. + - `resolveDeviceCapabilities(type, overrides)` → `resolveCapabilities(identity, { overrides })` from `@podkit/core`. + - The `core/device/sysinfo-extended` shim path is gone; import `readSysInfoExtended`, `writeSysInfoExtended`, and `ensureSysInfoExtended` from `@podkit/ipod-firmware` directly. + + See ADR-295.07 for the full architectural rationale. + + **New in `@podkit/ipod-firmware`:** SysInfoExtended file I/O is now owned by this package: `readSysInfoExtended`, `writeSysInfoExtended`, `ensureSysInfoExtended`, `SYSINFO_EXTENDED_PATH`, `SYSINFO_DEVICE_DIR`. Diagnostic helpers `compareSysInfoConsistency` and `normaliseFireWireGuid` are also exported. `ParsedFirmware` gains the optional `modelNumber` field (populated from the `ModelNumStr` plist key when present). + + **New in `@podkit/device-types`:** `IpodModel`, `IpodChecksumType`, `IpodGenerationId`, `IpodGenerationIdLike`, `IpodModelSource`, and `IPOD_GENERATION_IDS` are now exported from this package (canonical home). `UsbConnectionInfo` has been removed — use `UsbFingerprint` instead. `IpodIdentity.notSupportedReason` is added for devices identified as iPods that podkit cannot fully support (e.g. iOS-mode devices). `DeviceCapabilities.artworkMaxResolution` is now `number | null` (null when the generation has no known limit or artwork is unsupported). + + **In `@podkit/devices-ipod`:** `IpodGeneration.supported` and `IpodGeneration.artworkMaxResolution: number | null` are new fields on every generation entry. `lookupByFamilyId` and `FAMILY_ID_TO_GENERATION` are now exported. `unsupported.ts` is populated with comprehensive Apple iOS device PIDs to allow early rejection of phones and tablets at the USB identification stage. + + No user-facing CLI behaviour changes. `podkit device scan`, `podkit device info`, and all sync paths behave identically to P3. + +- [`01ecedd`](https://github.com/jvgomg/podkit/commit/01ecedde623ff99e94c5cbda75ff9f9c9ecef632) Thanks [@jvgomg](https://github.com/jvgomg)! - New packages: `@podkit/devices-ipod` (canonical home for iPod generation tables, model lookups, and capability synthesis) and `@podkit/devices-mass-storage` (user-extensible DAP preset framework for Echo Mini, Rockbox, generic, and custom devices). + + Echo Mini is now auto-detected at `device add` — when the USB descriptor matches the known VID/PID (`0x071b`/`0x3203`), no `--type echo-mini` flag is required. + + `enumerateConnectedDevices` is now the recommended way to discover and classify USB devices. It accepts a `providers: DeviceProvider[]` array and returns `EnumeratedDevice[]` carrying both the USB connection info and the provider-produced identity. + + `getCapabilities` in `@podkit/devices-ipod` is libgpod-free. Capability synthesis is purely table-and-firmware-driven; the legacy `createIpodCapabilities` adapter that depended on a live libgpod `LibgpodDeviceInfo` struct is deprecated in `@podkit/core`. Parity is verified across all 29 generations (the 4 that were libgpod `unknown` degenerate cases are now correctly populated from the table). + + Internal re-export shims in `@podkit/core` keep all existing call paths compiling for one release. The shims delegate to `@podkit/devices-ipod` and `@podkit/devices-mass-storage` and will be removed in P4. + +- [`f61a83b`](https://github.com/jvgomg/podkit/commit/f61a83b3a2d13612730f174759fd3b86edd42e82) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix `podkit device scan` reporting phantom "Unknown iPod (USB only)" entries for non-iPod USB peripherals (mice, hubs, Thunderbolt docks, Ethernet adapters, USB drives). Each phantom suggested `podkit device init` — a destructive operation that could mutate an unrelated device. + + Root cause was architectural: a single `discoverUsbIpods()` function mixed three concerns — USB enumeration, iPod-domain enrichment, and the function name's implied filter (which it didn't actually do). Refactored into clean layers: + - **`@podkit/core` — pure USB enumeration.** `enumerateUsb()` returns `EnumeratedUsbDevice[]` with vendor/product/serial/bus/devnum/diskIdentifier ONLY. No iPod-domain knowledge. + - **`@podkit/devices-ipod` — iPod classifier.** `classifyAsIpod(dev)` returns `IpodClassification | null` (matches Apple-vendor with iPod or iOS PIDs). + - **`@podkit/devices-mass-storage` — mass-storage classifier.** `classifyAsMassStorage(dev)` returns `MassStorageClassification | null` (matches `USB_PRESET_HINTS` entries like Echo Mini). + - **`@podkit/core` composer.** `classifyUsbDevices()` runs both classifiers and returns recognized devices as a tagged union; drops unknown peripherals. + - **CLI `device scan`** now calls `enumerateUsb()` → `classifyUsbDevices()` → renders by `kind`. No domain logic in the command layer. + + Added `kind: 'mass-storage'` rendering branch so mass-storage DAPs are no longer mis-labeled as "Unknown iPod". + + Removed `discoverUsbIpods` and the leaky `UsbDiscoveredDevice` type (which previously carried iPod-domain fields). Adding a new mass-storage device now means adding one entry to `USB_PRESET_HINTS` — no `@podkit/core` change required. Adding a new iPod generation means updating `@podkit/devices-ipod` tables — no other package changes. + + Also split `usb-discovery.ts` into `usb-enumeration.ts` (bus walk) + `usb-path-resolution.ts` (mount-path → fingerprint resolver) since those two concerns were unrelated. + +- Updated dependencies [[`0d4a4c2`](https://github.com/jvgomg/podkit/commit/0d4a4c2bd98667989b9631d981e609bc72e604af), [`d1147e4`](https://github.com/jvgomg/podkit/commit/d1147e4a65ac103608da3730f530f6deab3cd0b6), [`bddea04`](https://github.com/jvgomg/podkit/commit/bddea044342ca9027fc95593a35795fd8de1faf4)]: + - @podkit/device-types@0.1.0 diff --git a/packages/devices-mass-storage/package.json b/packages/devices-mass-storage/package.json index 87cfe16c..84aeef94 100644 --- a/packages/devices-mass-storage/package.json +++ b/packages/devices-mass-storage/package.json @@ -1,6 +1,6 @@ { "name": "@podkit/devices-mass-storage", - "version": "0.0.1", + "version": "0.1.0", "description": "Mass-storage device presets and types for podkit (Echo Mini, Rockbox, generic DAPs)", "type": "module", "main": "./dist/index.js", diff --git a/packages/docs-site/CHANGELOG.md b/packages/docs-site/CHANGELOG.md index 347a1568..e27fb499 100644 --- a/packages/docs-site/CHANGELOG.md +++ b/packages/docs-site/CHANGELOG.md @@ -1,5 +1,12 @@ # @podkit/docs-site +## 0.0.3 + +### Patch Changes + +- Updated dependencies []: + - @podkit/compatibility@0.0.2 + ## 0.0.2 ### Patch Changes diff --git a/packages/docs-site/package.json b/packages/docs-site/package.json index fbeff8a7..bbce04c1 100644 --- a/packages/docs-site/package.json +++ b/packages/docs-site/package.json @@ -1,6 +1,6 @@ { "name": "@podkit/docs-site", - "version": "0.0.2", + "version": "0.0.3", "dependencies": { "@astrojs/starlight": "^0.33.0", "@podkit/compatibility": "workspace:*", diff --git a/packages/ipod-firmware/CHANGELOG.md b/packages/ipod-firmware/CHANGELOG.md new file mode 100644 index 00000000..7241fcce --- /dev/null +++ b/packages/ipod-firmware/CHANGELOG.md @@ -0,0 +1,95 @@ +# @podkit/ipod-firmware + +## 0.1.0 + +### Minor Changes + +- [`d1147e4`](https://github.com/jvgomg/podkit/commit/d1147e4a65ac103608da3730f530f6deab3cd0b6) Thanks [@jvgomg](https://github.com/jvgomg)! - P4 — device-capability architecture complete. All in-tree migration finished; deprecated shims removed. + + **Breaking changes in `@podkit/core`:** The following symbols have been removed from the public API: `createIpodCapabilities`, `LibgpodDeviceInfo`, `DEVICE_PRESETS`, `DevicePreset`, `getDevicePreset`, and `resolveDeviceCapabilities`. Callers must migrate before upgrading. + + Migration guide: + - `createIpodCapabilities(libgpodInfo)` → `resolveCapabilities(identity)` (preferred, identity-driven) or `resolveIpodModelCapabilities(modelFromLibgpodInfo(libgpodInfo))` for callers that genuinely hold libgpod data. + - `getDevicePreset(deviceType)` → `BUILT_IN_PRESETS[deviceType]` from `@podkit/devices-mass-storage`. + - `DEVICE_PRESETS` → `BUILT_IN_PRESETS` from `@podkit/devices-mass-storage`. + - `resolveDeviceCapabilities(type, overrides)` → `resolveCapabilities(identity, { overrides })` from `@podkit/core`. + - The `core/device/sysinfo-extended` shim path is gone; import `readSysInfoExtended`, `writeSysInfoExtended`, and `ensureSysInfoExtended` from `@podkit/ipod-firmware` directly. + + See ADR-295.07 for the full architectural rationale. + + **New in `@podkit/ipod-firmware`:** SysInfoExtended file I/O is now owned by this package: `readSysInfoExtended`, `writeSysInfoExtended`, `ensureSysInfoExtended`, `SYSINFO_EXTENDED_PATH`, `SYSINFO_DEVICE_DIR`. Diagnostic helpers `compareSysInfoConsistency` and `normaliseFireWireGuid` are also exported. `ParsedFirmware` gains the optional `modelNumber` field (populated from the `ModelNumStr` plist key when present). + + **New in `@podkit/device-types`:** `IpodModel`, `IpodChecksumType`, `IpodGenerationId`, `IpodGenerationIdLike`, `IpodModelSource`, and `IPOD_GENERATION_IDS` are now exported from this package (canonical home). `UsbConnectionInfo` has been removed — use `UsbFingerprint` instead. `IpodIdentity.notSupportedReason` is added for devices identified as iPods that podkit cannot fully support (e.g. iOS-mode devices). `DeviceCapabilities.artworkMaxResolution` is now `number | null` (null when the generation has no known limit or artwork is unsupported). + + **In `@podkit/devices-ipod`:** `IpodGeneration.supported` and `IpodGeneration.artworkMaxResolution: number | null` are new fields on every generation entry. `lookupByFamilyId` and `FAMILY_ID_TO_GENERATION` are now exported. `unsupported.ts` is populated with comprehensive Apple iOS device PIDs to allow early rejection of phones and tablets at the USB identification stage. + + No user-facing CLI behaviour changes. `podkit device scan`, `podkit device info`, and all sync paths behave identically to P3. + +- [`bddea04`](https://github.com/jvgomg/podkit/commit/bddea044342ca9027fc95593a35795fd8de1faf4) Thanks [@jvgomg](https://github.com/jvgomg)! - Add SCSI firmware inquiry for iPod identification (P1 — m-18 device-capability architecture). + + `@podkit/device-types` (first published release) provides the canonical shared type definitions — `DeviceCapabilities`, `DeviceIdentity`, `ParsedFirmware`, and `DeviceProvider` — used across the podkit monorepo without circular dependencies. + + `@podkit/ipod-firmware` (first published release) implements iPod firmware inquiry via SCSI (Linux SG_IO + macOS IOKit, using koffi FFI) with USB fallback through the existing libgpod-node binding. Devices that previously failed identification over USB — including iPod mini 2G, nano 2G, and some iPod 5G Video configurations — can now be identified via SCSI. The orchestrator probes available transports at startup, prefers USB when both are available, and falls back to SCSI transparently. + + `@podkit/core` now routes `ensureSysInfoExtended` through the new orchestrator with SCSI fallback, and registers two new `podkit doctor` checks: `inquiry-methods` (reports which transports are available on this host) and `sysinfo-consistency` (validates that the on-disk SysInfo file matches the live firmware read). EACCES errors from SCSI include step-by-step recovery instructions. + + `podkit` CLI gains `--repair udev-rule` in `podkit doctor` to install the Linux udev rule that grants non-root `/dev/sg*` access, and surfaces the new doctor checks in the readiness output. + +- [`4598f8f`](https://github.com/jvgomg/podkit/commit/4598f8f3347cf40b94fdf1585215e5b0f54d9cf6) Thanks [@jvgomg](https://github.com/jvgomg)! - USB firmware inquiry consolidated into @podkit/ipod-firmware (P2 — m-18 device-capability architecture). + + **Breaking change in `@podkit/libgpod-node`:** The `readSysInfoExtendedFromUsb` function has been removed from the package's public exports. All in-tree callers were already routed through `@podkit/ipod-firmware` since P1 — only external consumers of `@podkit/libgpod-node` who called this function directly are affected. + + `@podkit/ipod-firmware` now owns the complete firmware inquiry surface: SCSI (Linux SG_IO + macOS IOKit) and USB (direct libusb-1.0 via koffi FFI). The P1 transitional shim that delegated USB reads to libgpod-node has been replaced by a native TypeScript implementation. No API change is visible to callers of `@podkit/ipod-firmware`. + + `@podkit/libgpod-node` no longer requires libusb at build or runtime. Distro packagers can now build the native binding without `libusb-1.0-0-dev` (Debian/Ubuntu), `libusb-devel` (Fedora/RHEL), or equivalent system packages. The `itdb_usb.c` patch, the `dlsym` shim, and the libusb pkg-config dependency have all been removed from the binding. + + No user-facing CLI behaviour changes. `podkit doctor` inquiry checks, `podkit device scan`, and all sync paths behave identically to P1. + +- [`e825ee1`](https://github.com/jvgomg/podkit/commit/e825ee1dd4933ecbfd070dda27f96f43056f0baf) Thanks [@jvgomg](https://github.com/jvgomg)! - Replace koffi-based libusb FFI with the `usb` npm package for USB firmware inquiry, eliminating the runtime libusb system dependency. + + The `@podkit/ipod-firmware` USB transport now uses the `usb` npm package, whose prebuilt N-API bindings statically link libusb. End-user binaries embed that prebuild via Bun `--compile`; no system `libusb-1.0` is required at runtime. + + Public-surface changes in `@podkit/ipod-firmware`: + - **Removed:** `loadLibusb`, `LibusbBinding`, `LibusbPtr`, `LibusbLoadResult`, `_resetLibusbCacheForTests`. The koffi-shaped binding interface is gone. + - **Added:** `loadUsb`, `UsbBinding`, `UsbDeviceHandle`, `UsbLoadResult`, `_resetUsbCacheForTests`. Higher-level `withOpenDevice(bus, devnum, fn)` seam — implementations handle enumeration, open, and cleanup internally. + - **Added:** `setLogger(fn | null)`, `FirmwareLogger`, `FirmwareLogEvent`. Library no longer writes to stderr/stdout; consumers install a receiver and decide format/destination. The CLI installs one when `-v` is passed. + - **Added:** `@podkit/ipod-firmware/bundle` subpath export with `bundleUsbNative(nativeModule)` for single-file binary builds. See `agents/ipod-firmware.md` for the staging recipe. + - **Renamed:** `UsbInquiryError.libusbCode` → `UsbInquiryError.libusbStatus`. The new field carries `LIBUSB_TRANSFER_*` status codes (positive enum) from the `usb` npm package, not the negative `LIBUSB_ERROR_*` codes the koffi path returned. + + Doctor's `inquiry-methods` check no longer reports libusb availability — the USB transport is bundled and always present in shipped binaries. The check now reports SCSI transport availability only, which remains user-actionable on Linux (udev permissions) and macOS (iPodDriver.kext). + +### Patch Changes + +- [`bb2e637`](https://github.com/jvgomg/podkit/commit/bb2e6374151605d11baf052c452f10a842e5353e) Thanks [@jvgomg](https://github.com/jvgomg)! - Externalize `koffi` and `usb` from the published `bun build` bundles. Koffi loads its native binding via `eval('require')(filename)`; bun's bundler shims top-level `require` as `__require` (via `createRequire(import.meta.url)`) but does not inject `require` into eval'd literals, so the bundled CLI hit `ReferenceError: require is not defined` whenever the SCSI inquiry path was actually reached. The native loaders are now resolved at runtime via `node_modules`, which is also more correct for `usb` (whose `bun build`-time prebuild only matched the build host's platform). + + The standalone-binary path (`bun --compile` via `compile.sh`) is unchanged — it stages platform-specific `.node` files and uses static `require()` in `compile-entry.js`, which works correctly. A bug in `compile.sh`'s linux-arm64 branch is also fixed: the script previously constructed `linux-arm64/node.napi.${USB_VARIANT}.node` (where `USB_VARIANT` is `glibc` or `musl`) but the `usb` package only ships `linux-arm64/node.napi.armv8.node` — no glibc/musl split exists for arm64. The script now selects the armv8 prebuild unconditionally on arm64. + + `@podkit/ipod-firmware` is also externalized from the `@podkit/core` and `@podkit/devices-ipod` builds, so neither package's `dist/index.js` re-inlines firmware (and therefore koffi/usb imports). Bundle content-check tests under `packages/*/src/bundle.test.ts` assert that no `eval("require")` slips into any published bundle. + +- [`3e95baf`](https://github.com/jvgomg/podkit/commit/3e95baffc65b683b5e3f80906e9a342245a6e4ce) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix iPod model identification regressing to "Unknown iPod" after `doctor --repair sysinfo-extended` on pre-2006 devices (mini 2G), and tighten the package-boundary contract so consumers compose identity instead of injecting resolution policy. + + The bug: each consumer of `ensureSysInfoExtended` / `readSysInfoExtended` passed a serial-only `resolveModel` callback. When the 3-character serial suffix wasn't in `tables/serials.ts`, the resolver returned undefined and the device was displayed as "Unknown iPod" — even when a SysInfo file with a known `ModelNumStr` was sitting next to the SysInfoExtended on disk. + + The fix: + - **Removed** `ModelResolver` type and the `resolveModel` callback from `@podkit/ipod-firmware`. `readSysInfoExtended` and `ensureSysInfoExtended` now return a flat `SysInfoIdentity` bag (`firewireGuid?, serialNumber?, modelNumStr?, familyId?`). When a SysInfo file is on disk alongside SysInfoExtended, its `ModelNumStr` is read opportunistically. + - **Callers compose** with `resolveIpodModel(bag)` from `@podkit/devices-ipod`, which cascades modelNumStr → serial → productId → familyId → libgpodGeneration. The CLI no longer makes resolution decisions. + - **Added** `SYSINFO_PATH`, `SYSINFO_EXTENDED_PATH`, `SYSINFO_DEVICE_DIR` exported from `@podkit/ipod-firmware` and re-exported from `@podkit/core`. Consumers use these constants instead of duplicating the literal `iPod_Control/Device/...` paths. + - **Added** `S4G: '9804'` entry to `tables/serials.ts` (mini 2G 4GB Pink, sourced from real hardware, serial `JQ5141TFS4G`). + - **Post-write enrichment.** After `ensureSysInfoExtended` writes the file via USB inquiry, it now re-reads via `readSysInfoExtended` so the post-write identity bag includes `modelNumStr` from the SysInfo neighbour. Eliminates the cosmetic regression where the repair-success message showed a less-specific name than the subsequent `doctor` run. + +- [`80fe65a`](https://github.com/jvgomg/podkit/commit/80fe65a022c65da512f571a8abf83f9385a649e6) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix SysInfoExtended SCSI-fallback on macOS for SCSI-only iPods (mini 2G, nano 2G, iPod 5G/5.5G). `device add` and `doctor --repair sysinfo-extended` now correctly fall back from USB → SCSI when the device does not respond to vendor control transfers, instead of failing with a misleading "Could not read device identity from USB" error. + + Internal API changes in `@podkit/ipod-firmware`: + - **Changed:** `ensureSysInfoExtended(mountPoint, fp, options?)` now takes a full `UsbFingerprint` instead of the previous `{ busNumber, deviceAddress }` shape. Required so the macOS SCSI transport can locate the IOService via vendorId/productId/serialNumber. `UsbDeviceAddress` is removed. + - **Added:** `inquireFirmwareDetailed(fp, opts?)` — like `inquireFirmware` but returns `{ firmware, plan, attempts }` so callers can distinguish which transports were attempted. `inquireFirmware` is unchanged for existing consumers. + - **Added:** `EnsureSysInfoExtendedOptions` type with `readFromUsb`, `resolveModel`, `inquireOptions` fields. Replaces the previous positional `(mountPoint, fp, readFromUsb, resolveModel)` signature. + + Internal API changes in `@podkit/core`: + - **Added:** `hasCompleteUsbFingerprint(fp): fp is CompleteUsbDevice` type guard exported from `@podkit/core`. + - **Added:** `CompleteUsbDevice` type — a `UsbFingerprint` with vendorId, productId, bus, devnum guaranteed present (serialNumber optional). + - **Changed:** `resolveUsbDeviceFromPath(path)` now also returns `vendorId` and `productId`. Linux extracts from sysfs `idVendor`/`idProduct`; macOS extracts from `system_profiler` JSON. + + User-facing error messages now differentiate between transport failures: "Could not read device identity from USB and SCSI" / "...from USB" / "...from SCSI" / "...no firmware inquiry transport is available on this system" / "...returned data but it could not be parsed". + +- Updated dependencies [[`0d4a4c2`](https://github.com/jvgomg/podkit/commit/0d4a4c2bd98667989b9631d981e609bc72e604af), [`d1147e4`](https://github.com/jvgomg/podkit/commit/d1147e4a65ac103608da3730f530f6deab3cd0b6), [`bddea04`](https://github.com/jvgomg/podkit/commit/bddea044342ca9027fc95593a35795fd8de1faf4)]: + - @podkit/device-types@0.1.0 diff --git a/packages/ipod-firmware/package.json b/packages/ipod-firmware/package.json index 972a4ba2..ff8a78d7 100644 --- a/packages/ipod-firmware/package.json +++ b/packages/ipod-firmware/package.json @@ -1,6 +1,6 @@ { "name": "@podkit/ipod-firmware", - "version": "0.0.1", + "version": "0.1.0", "description": "iPod firmware inquiry — SCSI and USB delivery for podkit", "type": "module", "main": "./dist/index.js", diff --git a/packages/libgpod-node/CHANGELOG.md b/packages/libgpod-node/CHANGELOG.md index 1a227715..cc5ea59e 100644 --- a/packages/libgpod-node/CHANGELOG.md +++ b/packages/libgpod-node/CHANGELOG.md @@ -1,5 +1,59 @@ # @podkit/libgpod-node +## 0.2.0 + +### Minor Changes + +- [`22dddf4`](https://github.com/jvgomg/podkit/commit/22dddf4803f4cfd7b004d80dffd83878a68b10f2) Thanks [@jvgomg](https://github.com/jvgomg)! - Add standalone Device class for capability queries without opening a database + - `Device.fromMountPoint(path)` — reads SysInfo from filesystem, determines capabilities + - `Device.fromModelNumber(num)` — cached lookup from model number string, no filesystem needed + - Exposes `supportsArtwork`, `supportsVideo`, `supportsPhoto`, `supportsPodcast`, `generation`, `modelNumber`, `modelName`, `capacity` + - Add `ArtworkFormat` type (reserved for future artwork dimension exposure) + +- [#59](https://github.com/jvgomg/podkit/pull/59) [`1ec30ac`](https://github.com/jvgomg/podkit/commit/1ec30acca1109178012db3913a60967a2087fb5b) Thanks [@jvgomg](https://github.com/jvgomg)! - Automated iPod device identification via SysInfoExtended. + + Modern iPods (post-2006) ship without a populated `SysInfo` file after iTunes restore. Without it, libgpod treats the device as generic — artwork breaks, ALAC support is unknown, and database checksums fail on Classic 6/7G and Nano 3G+. podkit now reads SysInfoExtended directly from iPod firmware over USB during `device add`, so first-time setup works with no manual tooling. + + **User-visible:** + - `podkit device add` identifies the exact model (e.g. "iPod nano 8GB Black (3rd Generation)") with no input + - `podkit doctor` detects missing SysInfoExtended and offers `--repair sysinfo-extended` + - `podkit sync` works correctly on first run with full capability detection + - Hash72 (Nano 5G) and HashAB (Nano 6G) devices get clear limitation messages + - SysInfoExtended write is gated on user confirmation during `device add` + + **Core (`@podkit/core`):** + - Unified iPod model registry — single table, both `0x120x`/`0x126x` USB ID ranges, 190+ serial-suffix → model mappings, checksum-type classification per generation + - `ensureSysInfoExtended()` orchestrator: check existing → USB read → validate XML → write + - USB discovery now exposes `serialNumber`, `busNumber`, `deviceAddress`; `resolveUsbDeviceFromPath()` on macOS + Linux + - Readiness pipeline: checksum-aware severity (hash58+ devices fail without SysInfoExtended; pre-checksum devices warn) + - `READINESS_RULES` declarative array replaces ad-hoc `determineLevel()` logic + - New `sysinfo-extended` diagnostic check + - Recognizes `P` / `F` model prefixes in SysInfo + + **libgpod-node (`@podkit/libgpod-node`):** + - `readSysInfoExtendedFromUsb()` N-API binding, resolved via `dlsym` at runtime so it loads gracefully on systems where libgpod lacks the symbol + - Prebuild patches upstream libgpod 0.8.3 to move `itdb_usb.c` from `tools/` into the library; libusb 1.0.27 built from source on all 6 platforms + - `--whole-archive` / `-force_load` linker flags preserve the dlsym symbol in the `.node` binary + + **CLI (`podkit`):** + - `device add` attempts SysInfoExtended read after mount, before DB init; enriches model name in summary + - `doctor` adds suggested-actions section, drops destructive sysinfo guidance + - `device scan` and `doctor` show clearer SysInfo readout + +- [`4598f8f`](https://github.com/jvgomg/podkit/commit/4598f8f3347cf40b94fdf1585215e5b0f54d9cf6) Thanks [@jvgomg](https://github.com/jvgomg)! - USB firmware inquiry consolidated into @podkit/ipod-firmware (P2 — m-18 device-capability architecture). + + **Breaking change in `@podkit/libgpod-node`:** The `readSysInfoExtendedFromUsb` function has been removed from the package's public exports. All in-tree callers were already routed through `@podkit/ipod-firmware` since P1 — only external consumers of `@podkit/libgpod-node` who called this function directly are affected. + + `@podkit/ipod-firmware` now owns the complete firmware inquiry surface: SCSI (Linux SG_IO + macOS IOKit) and USB (direct libusb-1.0 via koffi FFI). The P1 transitional shim that delegated USB reads to libgpod-node has been replaced by a native TypeScript implementation. No API change is visible to callers of `@podkit/ipod-firmware`. + + `@podkit/libgpod-node` no longer requires libusb at build or runtime. Distro packagers can now build the native binding without `libusb-1.0-0-dev` (Debian/Ubuntu), `libusb-devel` (Fedora/RHEL), or equivalent system packages. The `itdb_usb.c` patch, the `dlsym` shim, and the libusb pkg-config dependency have all been removed from the binding. + + No user-facing CLI behaviour changes. `podkit doctor` inquiry checks, `podkit device scan`, and all sync paths behave identically to P1. + +### Patch Changes + +- [`6000868`](https://github.com/jvgomg/podkit/commit/6000868830d9437a6fff3c1a77adb254d9579fe7) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix `doctor --repair sysinfo-extended` showing unhelpful "Could not read device identity from USB" with no detail. The native USB binding now throws descriptive errors (e.g. "USB control transfer failed (bus 3, device 4)") instead of returning null silently. Also fix all doctor repair intro messages — they incorrectly said "Repairing X for N tracks" even for non-track operations like SysInfoExtended and orphan cleanup. Intro messages now use each repair's own description. + ## 0.1.0 ### Minor Changes diff --git a/packages/libgpod-node/package.json b/packages/libgpod-node/package.json index 2d0141b2..3441d9f1 100644 --- a/packages/libgpod-node/package.json +++ b/packages/libgpod-node/package.json @@ -1,6 +1,6 @@ { "name": "@podkit/libgpod-node", - "version": "0.1.0", + "version": "0.2.0", "description": "Native Node.js bindings for libgpod", "type": "module", "main": "./dist/index.js", diff --git a/packages/podkit-cli/CHANGELOG.md b/packages/podkit-cli/CHANGELOG.md index 82fa63ec..dbb2068f 100644 --- a/packages/podkit-cli/CHANGELOG.md +++ b/packages/podkit-cli/CHANGELOG.md @@ -1,5 +1,566 @@ # podkit +## 0.7.0 + +### Minor Changes + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`0f3e4dd`](https://github.com/jvgomg/podkit/commit/0f3e4ddae134228b5e874b21db33f74547867b6c) Thanks [@jvgomg](https://github.com/jvgomg)! - Add capability-gated clean artists transform + + Devices now declare whether they use Album Artist for browse navigation via `supportsAlbumArtistBrowsing`. When enabled globally, the `cleanArtists` transform is automatically suppressed on devices that support Album Artist browsing (Rockbox, Echo Mini, generic) and auto-applied on devices that don't (iPod). Per-device overrides still take priority. + + The dry-run summary shows when the transform is skipped (`Clean artists: skipped (device supports Album Artist browsing)`), and warns when it's force-enabled on a capable device. Both `sync --dry-run` and `device info` surface these in text and JSON output. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`036b107`](https://github.com/jvgomg/podkit/commit/036b1077748253385b6f4ff873a7cdb52c54b004) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix mass-storage directory structure to use album artist instead of track artist, and add template-based path system with self-healing relocate. + + **Bug fix:** Mass-storage devices (Echo Mini, Rockbox) now use `albumArtist` for directory grouping, falling back to `artist` when absent. Previously, compilation/various-artist albums had their tracks scattered across separate artist directories instead of being grouped together under the album artist. + + **Path templates:** File paths are now generated from a configurable template string (`{albumArtist}/{album}/{trackNumber} - {title}{ext}` by default). This lays the groundwork for user-customisable folder structures in a future release. + + **Self-healing relocate:** When source metadata changes (e.g. album artist corrected) or the path template changes, the next sync detects the path mismatch and moves files to their correct location via `fs.rename()` — no re-copying of audio data. Relocate operations appear in dry-run output and are tracked as a new `relocate` operation type. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`89ff40c`](https://github.com/jvgomg/podkit/commit/89ff40c2adedd9fec38ae5ad0eb89b75525642f2) Thanks [@jvgomg](https://github.com/jvgomg)! - Add audioNormalization device capability for device-appropriate Sound Check / ReplayGain handling + + Devices now declare their normalization support: 'soundcheck' (iPod), 'replaygain' (Rockbox), or 'none' (Echo Mini, generic). Devices with no normalization support skip soundcheck upgrade detection entirely, and the dry-run output hides or relabels the normalization line accordingly. Configurable via `audioNormalization` in device config. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`c5c0236`](https://github.com/jvgomg/podkit/commit/c5c0236c232cc3fa086fd3937b0e2fbe0f326185) Thanks [@jvgomg](https://github.com/jvgomg)! - Refactor audio normalization from iPod-centric Sound Check to a generic `AudioNormalization` type, and add ReplayGain album gain/peak support + + **Normalization refactoring:** + - Introduce `AudioNormalization` type that preserves source format fidelity (ReplayGain dB, iTunNORM soundcheck integers) without unnecessary round-trip conversions + - Replace scattered `soundcheck`, `soundcheckSource`, `replayGainTrackGain`, `replayGainTrackPeak` fields on `CollectionTrack` with a single `normalization` field + - Replace `soundcheck`, `replayGainTrackGain`, `replayGainTrackPeak` fields on `DeviceTrackInput` with `normalization` + - Conversions now happen at device boundaries: iPod adapter reads soundcheck integers, mass-storage adapter reads dB values directly + - Upgrade detection compares in dB space with 0.1 dB epsilon tolerance, eliminating false positives from integer rounding + - Metadata update diffs show human-readable dB values (e.g., `normalization: -7.5 dB → -6.2 dB`) instead of opaque integers + + **Album gain/peak support (TASK-253):** + - Extract `albumGain` and `albumPeak` from local file metadata and Subsonic API + - Write `REPLAYGAIN_ALBUM_GAIN` and `REPLAYGAIN_ALBUM_PEAK` via FFmpeg metadata flags during transcode + - Write album gain/peak via node-taglib-sharp tag writer for M4A files + - Thread album data through the full sync pipeline for mass-storage devices (Rockbox, etc.) + + **Breaking changes:** + - `CollectionTrack` shape: four normalization fields replaced by single `normalization?: AudioNormalization` + - `SoundCheckSource` type removed, replaced by `NormalizationSource` + - Upgrade reason `'soundcheck-update'` renamed to `'normalization-update'` in JSON output + - `soundCheckTracks` stat renamed to `normalizedTracks` + +- [`ae995ef`](https://github.com/jvgomg/podkit/commit/ae995ef99174f7381f4eeaeb79cee4e77ddc3136) Thanks [@jvgomg](https://github.com/jvgomg)! - Unify JSON error shape for `device add` and `collection music`/`collection video` + + These commands now emit the same JSON error format on failure: + + ```json + { + "success": false, + "error": "", + "code": "", + "...details": "" + } + ``` + + **Breaking for `collection music` / `collection video` JSON consumers.** The previous shape was `{ "error": true, "message": "..." }`. If you parse JSON output from these commands, update consumers to read `success === false` and `error` (instead of `error === true` and `message`). + + `device add` errors now also include a `code` field (additive, not breaking). + + Underneath: the runners (`runDeviceAdd`, `runCollectionMusic`, `runCollectionVideo`) throw a typed `CliError` and the action wrapper (`runAction`) translates it into structured output + exit code. Tests assert on the captured JSON instead of `process.exitCode` side-effects. + + Per CLI breaking-change convention this is a minor bump. Other commands still emit their existing shapes; that unification will land in a follow-up. + +- [`8c6dc1a`](https://github.com/jvgomg/podkit/commit/8c6dc1a3d355efe6542a7f00f8fa05da3225bb42) Thanks [@jvgomg](https://github.com/jvgomg)! - Unify and harden CLI JSON error output across every command (ADR-015) + + ## What changed + + Every CLI command now emits the same canonical JSON shape on failure. The shape, exit codes, and consumer ergonomics all changed in one breaking pass. + + ### Canonical error shape + + ```json + { + "success": false, + "error": "", + "code": "", + "details": { "": "..." } + } + ``` + + `code` is required and machine-readable (e.g. `MOUNT_REQUIRES_SUDO`, `FFMPEG_UNAVAILABLE`). `details` is **nested**, not spread at the top level — so command-specific extras can't accidentally collide with `success`/`error`/`code`. + + ### Exit codes + - `0` — success + - `1` — command error (any `CliError` thrown) + - `2` — ran cleanly but found problems (`doctor` reporting unhealthy device, `sync` reporting partial track failures). Carries a `status` field on the success-shape JSON: `'ok' | 'issues-found' | 'partial-failure'`. + - `130` — SIGINT (interrupted sync) + + ### Per-command typed error codes + + Every command exports an exhaustive enum of its possible error codes: + + ```ts + import { MountErrorCodes, type MountErrorCode } from 'podkit/commands/mount'; + // MountErrorCodes.DEVICE_NOT_RESOLVED, MountErrorCodes.MOUNT_REQUIRES_SUDO, etc. + ``` + + A repo-wide barrel is at `packages/podkit-cli/src/commands/error-codes.ts` exporting `PodkitErrorCode` — the union of every code any podkit command may emit. + + ### Discriminated `*Output` types + + Each command's output type is now a discriminated union: + + ```ts + export type MountOutput = MountSuccess | MountErrorOutput; + ``` + + Consumers narrow with `if (output.success) { ... }`. + + ## Breaking for JSON consumers + + | Old shape | New shape | + | -------------------------------------------------------------- | ------------------------------------------------- | + | `{ error: true, message: "..." }` (collection music/video) | `{ success: false, error, code, details }` | + | `{ success: false, error: "..." }` (no code, top-level extras) | `{ success: false, error, code, details: {...} }` | + | Per-command extras at top level (e.g. `dryRun`, `device`) | Now nested under `details` | + | `process.exitCode === 1` for "found issues" | Now `2`; `1` is reserved for command errors | + + Update parsers to: + 1. Branch on `success === false`. + 2. Read `code` for machine-readable tags. + 3. Read `details.X` instead of `output.X` for command-specific extras. + 4. Branch on exit code 2 for "ran cleanly with issues" (sync partial failure, doctor unhealthy). + + ## New ergonomics + + `packages/podkit-cli/src/test-utils/cli-error.ts` and `packages/e2e-tests/src/helpers/cli-error.ts` export `expectCliError` for asserting on the canonical shape in one call. + + `OutputContext` now takes an optional `ExitCodeSink` (default: writes `process.exitCode`; tests use `BufferExitCodeSink` to avoid process-global mutation). + + Per CLI breaking-change convention this is a minor bump. + +- [`0d4a4c2`](https://github.com/jvgomg/podkit/commit/0d4a4c2bd98667989b9631d981e609bc72e604af) Thanks [@jvgomg](https://github.com/jvgomg)! - Disambiguate codec from container: `AudioCodec` value `'ogg'` is renamed to `'vorbis'` + + The `AudioCodec` slot previously used `'ogg'` to mean "OGG Vorbis." That conflated the OGG container with the Vorbis stream codec and could not represent Vorbis-in-OGG vs Opus-in-OGG as distinct device capabilities — Echo Mini (which plays Vorbis but hides `.opus` files) could not be modelled accurately. The codec slot now names the audio stream codec; `'vorbis'` replaces `'ogg'` in device presets and config. + + Configs containing `supportedAudioCodecs = ["…", "ogg", "…"]` under `[devices.*]` are migrated automatically by `podkit migrate` (config version 1 → 2). The migration is purely a string substitution inside the `supportedAudioCodecs` array; comments and surrounding formatting are preserved. + + Also lands as type-level groundwork for the future container-aware sync work: `AudioContainer`, `AUDIO_CONTAINERS`, `CODEC_CANONICAL_CONTAINER`, and an optional `DeviceCapabilities.containerConstraints` field. These are declared and exported but not yet read by the planner; they are placeholders for the upcoming Phase 2 work documented in the container-aware sync PRD. + + `DirectoryAdapter` now uses each `.ogg` file's probed stream codec (already populated by `music-metadata`) to distinguish Vorbis, Opus, and OGG-FLAC — same pattern as the existing AAC/ALAC distinction for `.m4a`. `SubsonicAdapter` additionally checks the API's `contentType` field for Opus-in-`.ogg`. The Subsonic check is best-effort because most Subsonic servers report container MIME (`audio/ogg`) regardless of stream codec; deeper probing is deferred until evidence of real-world impact. + + User-facing reference page added at `docs/reference/codec-support.md` explaining the codec/container model and what each `AudioCodec` value means. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`513173d`](https://github.com/jvgomg/podkit/commit/513173d1832bf9ca2894214e97d9d65cf02c52a5) Thanks [@jvgomg](https://github.com/jvgomg)! - Add configurable codec preference system for multi-device audio format support + + Users can now configure an ordered list of preferred audio codecs globally and per-device. The system walks the list top-to-bottom, selecting the first codec that is both supported by the target device and has an available FFmpeg encoder. This replaces the hardcoded AAC-only transcoding pipeline. + - **Default lossy stack:** opus → aac → mp3 (Rockbox devices get Opus automatically, iPods fall through to AAC) + - **Default lossless stack:** source → flac → alac (lossless files are kept in their original format when possible) + - **Quality presets are codec-aware:** "high" delivers perceptually equivalent quality regardless of codec (e.g., Opus 160 kbps ≈ AAC 256 kbps) + - **Codec change detection:** changing your codec preference re-transcodes affected tracks on the next sync + - **`podkit device info`** shows your codec preference list with supported/unsupported codecs marked + - **`podkit sync --dry-run`** shows which codec will be used and any codec changes + - **`podkit doctor`** warns when FFmpeg is missing an encoder for a preferred codec + + Configure via `config.toml`: + + ```toml + [codec] + lossy = ["opus", "aac", "mp3"] + lossless = ["source", "flac", "alac"] + + [devices.myipod.codec] + lossy = "aac" + ``` + + No configuration is required — existing setups work unchanged with sensible defaults. + +- [`0cc39d3`](https://github.com/jvgomg/podkit/commit/0cc39d3c62343591127d5c79deed2478f8dc4f60) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix track metadata convergence on mass-storage devices and add transfer-mode-aware on-disk tag writes for iPod portable. + + **Bug fix (mass-storage)**: `MassStorageAdapter.updateTrack` previously only wrote `comment`, OGG/Opus artwork, and ReplayGain to disk. All other metadata fields (title, artist, album, albumArtist, genre, year, trackNumber, discNumber, compilation) updated in-memory only — the file's embedded tags on the device were never rewritten. After a relocate or metadata-correction sync the next sync re-detected the same diff every time, looping forever as a zero-byte `update-metadata` op. + + `MassStorageAdapter` now queues every changed textual tag in a single `pendingTagWrites` map and flushes them as one `writeTags(filePath, fields)` call per file via `Promise.allSettled`. Per-file failures are aggregated and re-thrown so the sync executor can categorise them. + + **New behaviour (iPod portable)**: `IpodDeviceAdapter` now mirrors iTunesDB metadata into the on-disk file tags when `transferMode === 'portable'`. This makes files pulled off the iPod self-describing for re-import into a music library. `fast` and `optimized` modes still touch iTunesDB only — the iPod firmware reads metadata from iTunesDB and never falls back to file tags during playback, so paying the tag-rewrite cost in those modes would be wasted work. + + Tag writes are best-effort on iPod portable: failures are surfaced as warnings, not hard errors, because the iTunesDB write (the authoritative store for playback) already succeeded. + + **`addTrack` consistency**: When `transferMode === 'portable'`, both backends now also rewrite tags on first transfer to honour any collection-adapter transforms (e.g. clean-artists, Subsonic-side corrections) that FFmpeg's `-map_metadata 0` would otherwise copy through from the source. + + **On first sync after upgrade**: Existing mass-storage tracks will likely report a `metadata-correction` op on the next sync as stale on-disk tags converge to source values. These are zero-byte writes — no transcoding or transfers happen — but the operation list will look longer than usual for one cycle. + + **Scope notes**: + - Match-key changes (title, artist, album corrections) still produce a remove+add rather than a metadata update. By design: when those fields change, podkit treats it as a different track. + - Virtual-iPod (m-17) inherits the iPod behaviour automatically; no changes needed there. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`7534c2f`](https://github.com/jvgomg/podkit/commit/7534c2f19d81087413af8abbf764fe20cef61384) Thanks [@jvgomg](https://github.com/jvgomg)! - Add device-aware diagnostics framework to `podkit doctor`. The doctor command now handles mass-storage devices gracefully instead of crashing when pointed at a non-iPod device. Diagnostic checks declare which device types they apply to, and the runner filters them automatically. JSON output now includes a `deviceType` field. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`8bc3126`](https://github.com/jvgomg/podkit/commit/8bc3126ec415aa836b746ec921b6738abdd9e538) Thanks [@jvgomg](https://github.com/jvgomg)! - Enhanced device commands with readiness diagnostics + - `device scan`: verbose readiness output with per-stage checks, USB discovery for unpartitioned devices, config relationship display, `--mount` flag for automatic mounting, `--report` flag for diagnostic reports + - `podkit doctor`: two-phase diagnostics — readiness checks before database health, graceful handling of devices without databases + - `device info`: readiness summary line in output + - `device init`: readiness-aware guidance with stub messages for format/partition operations + - OS error codes (errno 71, 13, 19, 5) translated to plain-language explanations + +- [`8d017e8`](https://github.com/jvgomg/podkit/commit/8d017e8ede48c98b9a1d1c627b882689c33da61e) Thanks [@jvgomg](https://github.com/jvgomg)! - Redesigned `podkit device list` output with resolved config values and provenance tracking + - Shows resolved quality, audio, video, and artwork settings per device with inheritance indicators + - Global config line shows top-level resolved values + - Connected devices detected automatically and marked with ● prefix + - Devices sorted by connection status, then default, then alphabetical + - Values explicitly set on a device shown without brackets; inherited values wrapped in [brackets] + - Unsupported capabilities shown as ✗, unknown (disconnected iPod) shown as ? + - TYPE column replaces VOLUME column + - New config resolution module (`config/resolve.ts`) with `ResolvedValue` provenance tracking + - `device scan` "Configured devices" section renamed to "Not detected" and now includes iPod devices + +- [`6747667`](https://github.com/jvgomg/podkit/commit/6747667049cd793fdb13e3d1bc1092651f8e969c) Thanks [@jvgomg](https://github.com/jvgomg)! - Improve device command output: USB model in scan, SysInfo mismatch detection, summary/issues layout + - `podkit device scan` now shows the USB-detected iPod model (e.g., "iPod Classic 6th generation (USB)") and always runs USB discovery in parallel with disk scanning + - `podkit device scan` and `podkit doctor` detect generation mismatches between SysInfo and USB data, warning when the SysInfo file may have been copied from a different device + - `podkit device info`, `podkit device scan`, and `podkit doctor` now separate compact check summaries from detailed issue explanations — warnings and fix commands appear in a dedicated "Issues" section instead of inline + - New `lookupGenerationByModelNumber()` function in `@podkit/core` for resolving iPod generation from SysInfo model numbers + +- [`01ecedd`](https://github.com/jvgomg/podkit/commit/01ecedde623ff99e94c5cbda75ff9f9c9ecef632) Thanks [@jvgomg](https://github.com/jvgomg)! - New packages: `@podkit/devices-ipod` (canonical home for iPod generation tables, model lookups, and capability synthesis) and `@podkit/devices-mass-storage` (user-extensible DAP preset framework for Echo Mini, Rockbox, generic, and custom devices). + + Echo Mini is now auto-detected at `device add` — when the USB descriptor matches the known VID/PID (`0x071b`/`0x3203`), no `--type echo-mini` flag is required. + + `enumerateConnectedDevices` is now the recommended way to discover and classify USB devices. It accepts a `providers: DeviceProvider[]` array and returns `EnumeratedDevice[]` carrying both the USB connection info and the provider-produced identity. + + `getCapabilities` in `@podkit/devices-ipod` is libgpod-free. Capability synthesis is purely table-and-firmware-driven; the legacy `createIpodCapabilities` adapter that depended on a live libgpod `LibgpodDeviceInfo` struct is deprecated in `@podkit/core`. Parity is verified across all 29 generations (the 4 that were libgpod `unknown` degenerate cases are now correctly populated from the table). + + Internal re-export shims in `@podkit/core` keep all existing call paths compiling for one release. The shims delegate to `@podkit/devices-ipod` and `@podkit/devices-mass-storage` and will be removed in P4. + +- [`03f1046`](https://github.com/jvgomg/podkit/commit/03f1046b70898b0282d0c96927bca60ee0d55eeb) Thanks [@jvgomg](https://github.com/jvgomg)! - Add `podkit doctor --repair artwork-reset` to clear all artwork from an iPod without needing a source collection. This is a fast alternative to a full rebuild — useful when your source collection isn't available or you just want to clear corrupted artwork quickly. + + Rename `--repair artwork-integrity` to `--repair artwork-rebuild` to better describe what the repair does. The old name no longer works. + +- [`14d83e5`](https://github.com/jvgomg/podkit/commit/14d83e5e59eb0a8a801850de775f9fdb4c0e7aa9) Thanks [@jvgomg](https://github.com/jvgomg)! - `podkit doctor` gains `--no-system` to skip system-scope checks (FFmpeg encoders, libusb availability, udev rule). System checks remain on by default; pass `--no-system` for device-only diagnostics or in tests where the host environment shouldn't influence the result. + + The `sysinfo-consistency` check is redesigned: a missing `SysInfoExtended` file is now `skip` (not `fail`) since absence is not a failure mode. When the file is present it's compared against the live device on two independent axes — FireWireGUID and model generation — and only fails when at least one axis can be evaluated and disagrees. The check picks up live device data via the new `liveIdentity` field on `DiagnosticContext`, which `runDiagnostics` accepts as part of `RunDiagnosticsInput`. + +- [`bddea04`](https://github.com/jvgomg/podkit/commit/bddea044342ca9027fc95593a35795fd8de1faf4) Thanks [@jvgomg](https://github.com/jvgomg)! - Add SCSI firmware inquiry for iPod identification (P1 — m-18 device-capability architecture). + + `@podkit/device-types` (first published release) provides the canonical shared type definitions — `DeviceCapabilities`, `DeviceIdentity`, `ParsedFirmware`, and `DeviceProvider` — used across the podkit monorepo without circular dependencies. + + `@podkit/ipod-firmware` (first published release) implements iPod firmware inquiry via SCSI (Linux SG_IO + macOS IOKit, using koffi FFI) with USB fallback through the existing libgpod-node binding. Devices that previously failed identification over USB — including iPod mini 2G, nano 2G, and some iPod 5G Video configurations — can now be identified via SCSI. The orchestrator probes available transports at startup, prefers USB when both are available, and falls back to SCSI transparently. + + `@podkit/core` now routes `ensureSysInfoExtended` through the new orchestrator with SCSI fallback, and registers two new `podkit doctor` checks: `inquiry-methods` (reports which transports are available on this host) and `sysinfo-consistency` (validates that the on-disk SysInfo file matches the live firmware read). EACCES errors from SCSI include step-by-step recovery instructions. + + `podkit` CLI gains `--repair udev-rule` in `podkit doctor` to install the Linux udev rule that grants non-root `/dev/sg*` access, and surfaces the new doctor checks in the readiness output. + +- [`09c4acd`](https://github.com/jvgomg/podkit/commit/09c4acdec349f200a649b2db15fe05345e380a7b) Thanks [@jvgomg](https://github.com/jvgomg)! - Add canonical IpodModel type for structured device identity + - Add `IpodModel` interface — canonical representation of identified iPod model with `displayName`, `generationId`, `checksumType`, `color`, `capacityGb`, `modelNumber`, and `source` provenance + - Add `resolveIpodModel()` factory — builds an `IpodModel` from USB product ID, SysInfo model number, or serial number suffix + - Add `UsbConnectionInfo` interface — pure USB bus topology data, split from device identity + - Restructure `UsbDiscoveredDevice` to carry `usb: UsbConnectionInfo` + `model?: IpodModel` + - Add `usbModel` and `deviceModel` to `ReadinessResult` — USB-derived and SysInfo-derived models kept separate for mismatch detection + - Update `SysInfoExtendedResult` with structured `model`, `firewireGuid`, `serialNumber` fields + - Clean `checkSysInfo()` return type — new `SysInfoCheckResult` separates stage result from device model + - Add `model` to JSON output for `device scan` and `device info` commands + - `device scan` and `device info` now display richest available model name (color/capacity from SysInfo when available) + - Remove `UsbDeviceInfo` type (replaced by `UsbConnectionInfo` + `IpodModel`) + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`94c85d2`](https://github.com/jvgomg/podkit/commit/94c85d2a9d6c85875432a0ebecab540a9ebd67d7) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix `--delete` to only remove managed files on mass-storage devices, and add orphan file detection via `podkit doctor`. + + **Bug fix:** `--delete` previously removed all unmatched files on mass-storage devices, including user-placed files. It now only removes files that podkit manages (tracked in `.podkit/state.json`), matching iPod behavior where only database tracks are candidates for deletion. + + **Collision detection:** Sync now detects when a planned file write would collide with an existing unmanaged file and reports the conflict before writing. Works in both normal sync and `--dry-run` mode. + + **New diagnostic check:** `podkit doctor` now runs health checks on mass-storage devices. The `orphan-files-mass-storage` check detects unmanaged files in content directories and can clean them up via `podkit doctor --repair orphan-files-mass-storage`. + + **Other improvements:** + - State manifest (`.podkit/state.json`) is now written without pretty-printing to reduce file size on device storage + - Shell completions now include valid repair IDs for the `--repair` option + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`efa14c6`](https://github.com/jvgomg/podkit/commit/efa14c623e7bda81066bd77142cddb28e4de615d) Thanks [@jvgomg](https://github.com/jvgomg)! - Add mass-storage device support for non-iPod portable music players. + + **Supported device types:** Echo Mini, Rockbox, and generic mass-storage DAPs. iPod support is unchanged. + + **New in CLI (`podkit`):** + - `podkit device add --type ` registers mass-storage devices by type and mount path + - `podkit device info/music/video` work with mass-storage devices via `DeviceAdapter` interface + - `podkit device scan` shows configured path-based devices alongside auto-detected iPods + - `podkit sync` routes to the correct adapter (iPod or mass-storage) based on device config + - Video sync now uses capabilities-based gating instead of iPod-only checks + - Safety gates on `device init/reset/clear` (iPod-only commands) for mass-storage devices + - Mount and eject commands show device-appropriate messaging + - Config validation rejects capability overrides on iPod devices (capabilities are auto-detected from generation) + - Shared `openDevice()` function eliminates duplicated device-opening logic across commands + + **New in core (`@podkit/core`):** + - `DeviceAdapter` interface — generic abstraction over device databases (iPod, mass-storage) + - `MassStorageAdapter` — filesystem-based track management with `.podkit/state.json` manifest + - `IpodDeviceAdapter` — thin wrapper making `IpodDatabase` implement `DeviceAdapter` + - Device capability presets for Echo Mini, Rockbox, and generic devices + - `resolveDeviceCapabilities()` merges preset defaults with user config overrides + - `DeviceTrack` type used throughout sync engine (replaces `IPodTrack` casts in execution paths) + - Configurable content path prefixes (`musicDir`, `moviesDir`, `tvShowsDir`) with device-type defaults + - Device presets include default content paths (Echo Mini: root for music; generic/Rockbox: `Music/`, `Video/Movies/`, `Video/Shows/`) + - Manifest v2 stores active content paths; files automatically moved when prefixes change + - Root path support (`/`, `.`, or empty string all normalize to device root) + - Content path duplicate validation (no two content types can share the same prefix) + - Video scanning support for mass-storage devices (.m4v, .mp4, .mov, .avi, .mkv) + + **New in daemon (`@podkit/daemon`):** + - Mass-storage device polling via `PODKIT_MASS_STORAGE_PATHS` env var (colon/comma separated) + - Second `DevicePoller` + `SyncOrchestrator` pair for mass-storage devices + - No-op mount/eject runners (mass-storage devices are externally managed) + - Graceful shutdown handles both iPod and mass-storage sync pipelines + + **Configuration:** + + ```toml + [devices.echo] + type = "echo-mini" + path = "/Volumes/ECHO" + + # Optional capability overrides (mass-storage only) + artworkMaxResolution = 800 + supportedAudioCodecs = ["aac", "mp3", "flac"] + + # Optional content path overrides (mass-storage only) + musicDir = "/" # Place music at device root + moviesDir = "Films" # Custom movies directory + tvShowsDir = "TV Shows" # Custom TV shows directory + ``` + + **Environment variables for content paths:** + - `PODKIT_MUSIC_DIR` — global default music directory + - `PODKIT_MOVIES_DIR` — global default movies directory + - `PODKIT_TV_SHOWS_DIR` — global default TV shows directory + +- [`34ad4d3`](https://github.com/jvgomg/podkit/commit/34ad4d39104600f1363bd434518b10b3399a652c) Thanks [@jvgomg](https://github.com/jvgomg)! - Expose `pathTemplate` as a per-device config option for mass-storage devices, allowing user-customisable folder structures. + + Configurable via: + - `[devices.] pathTemplate = "..."` in TOML + - `PODKIT_PATH_TEMPLATE` env var (applied as a global default for mass-storage devices) + + Variables: `{albumArtist}`, `{artist}`, `{album}`, `{title}`, `{trackNumber}`, `{discNumber}`, `{totalDiscs}`, `{genre}`, `{year}`, `{ext}`. The template must contain `{title}` and `{ext}` and is rejected on iPod devices (iPod paths are managed by libgpod, not by template). + + Changing the template between syncs triggers the existing self-healing relocate flow — existing files are moved via `fs.rename()` to match the new layout, with no re-transcoding. Adds, removes (`--delete`), and template-driven relocates all compose in a single sync operation. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`f72fa01`](https://github.com/jvgomg/podkit/commit/f72fa0170872fc0a6e5719b4509abae24e6414cd) Thanks [@jvgomg](https://github.com/jvgomg)! - Refactor sync engine to be fully content-type-agnostic with per-handler operation types. + + **Breaking:** `createMusicHandler()` and `createVideoHandler()` now take a config object at construction instead of using `setTransformsConfig()`/`setExecutionConfig()`. Removed `HandlerDiffOptions`, `HandlerPlanOptions`, `MusicExecutionConfig` types. Renamed `MusicExecutor` to `MusicPipeline`. Removed legacy planner functions (`createMusicPlan`, `planVideoSync` and related helpers). + + **New:** `MusicSyncConfig`, `VideoSyncConfig`, `MusicTrackClassifier`, `VideoTrackClassifier`, `MusicOperationFactory`, `MusicOperation`, `VideoOperation`, `BaseOperation` types. Handlers now own their operation types via `TOp` type parameter on `ContentTypeHandler`. + +- [#59](https://github.com/jvgomg/podkit/pull/59) [`1ec30ac`](https://github.com/jvgomg/podkit/commit/1ec30acca1109178012db3913a60967a2087fb5b) Thanks [@jvgomg](https://github.com/jvgomg)! - Automated iPod device identification via SysInfoExtended. + + Modern iPods (post-2006) ship without a populated `SysInfo` file after iTunes restore. Without it, libgpod treats the device as generic — artwork breaks, ALAC support is unknown, and database checksums fail on Classic 6/7G and Nano 3G+. podkit now reads SysInfoExtended directly from iPod firmware over USB during `device add`, so first-time setup works with no manual tooling. + + **User-visible:** + - `podkit device add` identifies the exact model (e.g. "iPod nano 8GB Black (3rd Generation)") with no input + - `podkit doctor` detects missing SysInfoExtended and offers `--repair sysinfo-extended` + - `podkit sync` works correctly on first run with full capability detection + - Hash72 (Nano 5G) and HashAB (Nano 6G) devices get clear limitation messages + - SysInfoExtended write is gated on user confirmation during `device add` + + **Core (`@podkit/core`):** + - Unified iPod model registry — single table, both `0x120x`/`0x126x` USB ID ranges, 190+ serial-suffix → model mappings, checksum-type classification per generation + - `ensureSysInfoExtended()` orchestrator: check existing → USB read → validate XML → write + - USB discovery now exposes `serialNumber`, `busNumber`, `deviceAddress`; `resolveUsbDeviceFromPath()` on macOS + Linux + - Readiness pipeline: checksum-aware severity (hash58+ devices fail without SysInfoExtended; pre-checksum devices warn) + - `READINESS_RULES` declarative array replaces ad-hoc `determineLevel()` logic + - New `sysinfo-extended` diagnostic check + - Recognizes `P` / `F` model prefixes in SysInfo + + **libgpod-node (`@podkit/libgpod-node`):** + - `readSysInfoExtendedFromUsb()` N-API binding, resolved via `dlsym` at runtime so it loads gracefully on systems where libgpod lacks the symbol + - Prebuild patches upstream libgpod 0.8.3 to move `itdb_usb.c` from `tools/` into the library; libusb 1.0.27 built from source on all 6 platforms + - `--whole-archive` / `-force_load` linker flags preserve the dlsym symbol in the `.node` binary + + **CLI (`podkit`):** + - `device add` attempts SysInfoExtended read after mount, before DB init; enriches model name in summary + - `doctor` adds suggested-actions section, drops destructive sysinfo guidance + - `device scan` and `doctor` show clearer SysInfo readout + +- [`c5cba69`](https://github.com/jvgomg/podkit/commit/c5cba6998283663b42659f02b17b194ab256c137) Thanks [@jvgomg](https://github.com/jvgomg)! - Polish on the convergent-metadata work (TASK-327 follow-up): + - **Tag-write concurrency cap.** `save()` now caps in-flight tag writes at 16 via a small `runWithConcurrency` helper instead of firing every pending write at once. Avoids `EMFILE` on large libraries. + - **Aggregated tag-write errors.** Failure messages now begin with `tag write failed` so the executor's error categorizer classifies them as file-I/O (`copy`) rather than risking a path-keyword mis-classification. + - **WAV/AIFF on mass-storage.** Podkit transcodes WAV and AIFF source files to a managed codec before placing them on a mass-storage device, even when the device firmware can play them. RIFF/IFF tag-writing is unreliable. Presets continue to list these codecs for documentation. iPod is unaffected (libgpod / iTunesDB handle metadata for WAV/AIFF). + - **OGG Vorbis tag round-trip tests.** Now run on builds with libvorbis (skipped automatically when absent). + - **Shared TagFields helpers.** `buildTagFieldsFromInput` and `diffTagFields` replace three duplicate field-by-field walks across adapters. + - **`TransferMode` type unified.** Removed `'fast' | 'optimized' | 'portable'` duplication between `DeviceTrackInput`, `DeviceTrackMetadata`, and the canonical `TransferMode` in `transcode/types.ts`. Drops several inline type casts. + - **Docs.** `transferMode` now has a dedicated section in `docs/reference/config-file.md` explaining the iPod vs mass-storage contract and migration churn. `pathTemplate` (from the prior release) and `PODKIT_PATH_TEMPLATE` are now documented in the config reference and environment-variables reference. + +- [`1c3ebc3`](https://github.com/jvgomg/podkit/commit/1c3ebc381276accdb8361f50454b90c75f2391df) Thanks [@jvgomg](https://github.com/jvgomg)! - Add three-tier transfer mode system controlling how files are prepared for the device. + + **Transfer modes:** + - `fast` (default): optimizes for sync speed — direct-copies compatible files, strips artwork from transcodes + - `optimized`: strips embedded artwork from all file types (including MP3, M4A, ALAC copies) via FFmpeg stream-copy, reducing storage usage without re-encoding + - `portable`: preserves embedded artwork in all files for use outside the iPod ecosystem + + **Configuration:** + - `transferMode` config option (global and per-device) + - `--transfer-mode` CLI flag + - `PODKIT_TRANSFER_MODE` environment variable + + **Selective re-processing:** + - `--force-transfer-mode` flag re-processes only tracks whose transfer mode doesn't match the current setting + - `PODKIT_FORCE_TRANSFER_MODE` environment variable + - Works on all file types including direct copies (unlike `--force-transcode` which only affects transcoded tracks) + + **Device inspection:** + - `podkit device music` and `podkit device video` stats show transfer mode distribution + - Missing transfer field flagged alongside missing artwork hash in sync tag summary + - New `syncTagTransfer` field available in `--tracks --fields` for querying transfer mode data + - Dry-run output shows configured transfer mode + + **Under the hood:** + - Granular operation types: `add-direct-copy`, `add-optimized-copy`, `add-transcode` (and upgrade equivalents) + - Sync tags written to all tracks including direct copies (`quality=copy`) + - `DeviceCapabilities` abstraction for device-aware sync decisions + - Sync tag field `transfer=` tracks which mode was used per track + +- [`e825ee1`](https://github.com/jvgomg/podkit/commit/e825ee1dd4933ecbfd070dda27f96f43056f0baf) Thanks [@jvgomg](https://github.com/jvgomg)! - Replace koffi-based libusb FFI with the `usb` npm package for USB firmware inquiry, eliminating the runtime libusb system dependency. + + The `@podkit/ipod-firmware` USB transport now uses the `usb` npm package, whose prebuilt N-API bindings statically link libusb. End-user binaries embed that prebuild via Bun `--compile`; no system `libusb-1.0` is required at runtime. + + Public-surface changes in `@podkit/ipod-firmware`: + - **Removed:** `loadLibusb`, `LibusbBinding`, `LibusbPtr`, `LibusbLoadResult`, `_resetLibusbCacheForTests`. The koffi-shaped binding interface is gone. + - **Added:** `loadUsb`, `UsbBinding`, `UsbDeviceHandle`, `UsbLoadResult`, `_resetUsbCacheForTests`. Higher-level `withOpenDevice(bus, devnum, fn)` seam — implementations handle enumeration, open, and cleanup internally. + - **Added:** `setLogger(fn | null)`, `FirmwareLogger`, `FirmwareLogEvent`. Library no longer writes to stderr/stdout; consumers install a receiver and decide format/destination. The CLI installs one when `-v` is passed. + - **Added:** `@podkit/ipod-firmware/bundle` subpath export with `bundleUsbNative(nativeModule)` for single-file binary builds. See `agents/ipod-firmware.md` for the staging recipe. + - **Renamed:** `UsbInquiryError.libusbCode` → `UsbInquiryError.libusbStatus`. The new field carries `LIBUSB_TRANSFER_*` status codes (positive enum) from the `usb` npm package, not the negative `LIBUSB_ERROR_*` codes the koffi path returned. + + Doctor's `inquiry-methods` check no longer reports libusb availability — the USB transport is bundled and always present in shipped binaries. The check now reports SCSI transport availability only, which remains user-actionable on Linux (udev permissions) and macOS (iPodDriver.kext). + +### Patch Changes + +- [`bb2e637`](https://github.com/jvgomg/podkit/commit/bb2e6374151605d11baf052c452f10a842e5353e) Thanks [@jvgomg](https://github.com/jvgomg)! - Externalize `koffi` and `usb` from the published `bun build` bundles. Koffi loads its native binding via `eval('require')(filename)`; bun's bundler shims top-level `require` as `__require` (via `createRequire(import.meta.url)`) but does not inject `require` into eval'd literals, so the bundled CLI hit `ReferenceError: require is not defined` whenever the SCSI inquiry path was actually reached. The native loaders are now resolved at runtime via `node_modules`, which is also more correct for `usb` (whose `bun build`-time prebuild only matched the build host's platform). + + The standalone-binary path (`bun --compile` via `compile.sh`) is unchanged — it stages platform-specific `.node` files and uses static `require()` in `compile-entry.js`, which works correctly. A bug in `compile.sh`'s linux-arm64 branch is also fixed: the script previously constructed `linux-arm64/node.napi.${USB_VARIANT}.node` (where `USB_VARIANT` is `glibc` or `musl`) but the `usb` package only ships `linux-arm64/node.napi.armv8.node` — no glibc/musl split exists for arm64. The script now selects the armv8 prebuild unconditionally on arm64. + + `@podkit/ipod-firmware` is also externalized from the `@podkit/core` and `@podkit/devices-ipod` builds, so neither package's `dist/index.js` re-inlines firmware (and therefore koffi/usb imports). Bundle content-check tests under `packages/*/src/bundle.test.ts` assert that no `eval("require")` slips into any published bundle. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`0ef210b`](https://github.com/jvgomg/podkit/commit/0ef210be6e5fc38203e5501d33cc1bb978ecc0c6) Thanks [@jvgomg](https://github.com/jvgomg)! - Add `--clean-artists` / `--no-clean-artists` / `--clear-clean-artists` options to `podkit device set` + + The clean artists transform can now be toggled per-device from the CLI instead of requiring manual config file edits. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`56c7ec3`](https://github.com/jvgomg/podkit/commit/56c7ec36fb00b6996beffdce76eb17a23211c628) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix shell completions namespace conflict when multiple podkit binaries are installed. + + The `--cmd` flag now derives the completion function prefix from the binary name (`podkit-dev` → `_podkit_dev`), so `podkit` and `podkit-dev` each get an isolated namespace and their completion scripts no longer clobber each other. The `podkit-dev` binary built via `install:dev` now reports a `-dev` version suffix. + +- [`348f2c5`](https://github.com/jvgomg/podkit/commit/348f2c53cec06598903b5cf128663d5121c46865) Thanks [@jvgomg](https://github.com/jvgomg)! - Redesign `podkit device add` to be slick and informative. Previously, plugging in a post-2006 iPod (nano 2G, nano 7G, iPod 5G) and running `device add` displayed the device as `Model: Invalid` (libgpod's wording for an empty SysInfo file) and instructed the user to manually write a SysInfo file with `ModelNumStr: MA147` — neither friendly nor accurate. + + The new flow: + 1. **Identity is cascade-resolved** from USB product ID, classic SysInfo, SysInfoExtended, and serial — whichever sources are available. Display reads `Found iPod nano (2nd Generation):` rather than `Model: Invalid`. + 2. **A single combined prompt** asks `Add this iPod as "X" and write SysInfoExtended? [Y/n]` when SysInfoExtended is missing and USB is reachable. Confirming triggers firmware inquiry, writes SysInfoExtended, and persists to config in one step. + 3. **Capabilities are derived from the cascade-resolved generation**, not from libgpod's pessimistic fallback. Negative capabilities cite the reason (`- Video (not supported on iPod nano 4GB Green (2nd Generation))`). + 4. **The follow-up tip** suggests `podkit sync -d --dry-run`, not "go run two more commands". + + New flag `--no-firmware-inquiry` skips the firmware fetch+write when used with `--yes` — for the case where the user wants to defer the write or doesn't have the device connected over USB. + + Internal API changes in `@podkit/core`: + - **Added** `assessIpodIdentity(mountPoint, opts?)` returning `IpodIdentityAssessment` — pure cascade-driven assessment (no writes). Combines all available identification sources and returns `{ model, capabilities, firmwareInquiry: 'present' | 'missing' | 'unwritable', needsChecksum }`. The CLI now composes from this primitive instead of reaching into libgpod for identity. + + The misleading `device-validation.ts` warning text (`Ensure /Volumes/X/iPod_Control/Device/SysInfo exists with your model number (e.g., "ModelNumStr: MA147")`) has been replaced with a pointer to the canonical fix: `podkit doctor --repair sysinfo-extended`. + +- [`d1147e4`](https://github.com/jvgomg/podkit/commit/d1147e4a65ac103608da3730f530f6deab3cd0b6) Thanks [@jvgomg](https://github.com/jvgomg)! - P4 — device-capability architecture complete. All in-tree migration finished; deprecated shims removed. + + **Breaking changes in `@podkit/core`:** The following symbols have been removed from the public API: `createIpodCapabilities`, `LibgpodDeviceInfo`, `DEVICE_PRESETS`, `DevicePreset`, `getDevicePreset`, and `resolveDeviceCapabilities`. Callers must migrate before upgrading. + + Migration guide: + - `createIpodCapabilities(libgpodInfo)` → `resolveCapabilities(identity)` (preferred, identity-driven) or `resolveIpodModelCapabilities(modelFromLibgpodInfo(libgpodInfo))` for callers that genuinely hold libgpod data. + - `getDevicePreset(deviceType)` → `BUILT_IN_PRESETS[deviceType]` from `@podkit/devices-mass-storage`. + - `DEVICE_PRESETS` → `BUILT_IN_PRESETS` from `@podkit/devices-mass-storage`. + - `resolveDeviceCapabilities(type, overrides)` → `resolveCapabilities(identity, { overrides })` from `@podkit/core`. + - The `core/device/sysinfo-extended` shim path is gone; import `readSysInfoExtended`, `writeSysInfoExtended`, and `ensureSysInfoExtended` from `@podkit/ipod-firmware` directly. + + See ADR-295.07 for the full architectural rationale. + + **New in `@podkit/ipod-firmware`:** SysInfoExtended file I/O is now owned by this package: `readSysInfoExtended`, `writeSysInfoExtended`, `ensureSysInfoExtended`, `SYSINFO_EXTENDED_PATH`, `SYSINFO_DEVICE_DIR`. Diagnostic helpers `compareSysInfoConsistency` and `normaliseFireWireGuid` are also exported. `ParsedFirmware` gains the optional `modelNumber` field (populated from the `ModelNumStr` plist key when present). + + **New in `@podkit/device-types`:** `IpodModel`, `IpodChecksumType`, `IpodGenerationId`, `IpodGenerationIdLike`, `IpodModelSource`, and `IPOD_GENERATION_IDS` are now exported from this package (canonical home). `UsbConnectionInfo` has been removed — use `UsbFingerprint` instead. `IpodIdentity.notSupportedReason` is added for devices identified as iPods that podkit cannot fully support (e.g. iOS-mode devices). `DeviceCapabilities.artworkMaxResolution` is now `number | null` (null when the generation has no known limit or artwork is unsupported). + + **In `@podkit/devices-ipod`:** `IpodGeneration.supported` and `IpodGeneration.artworkMaxResolution: number | null` are new fields on every generation entry. `lookupByFamilyId` and `FAMILY_ID_TO_GENERATION` are now exported. `unsupported.ts` is populated with comprehensive Apple iOS device PIDs to allow early rejection of phones and tablets at the USB identification stage. + + No user-facing CLI behaviour changes. `podkit device scan`, `podkit device info`, and all sync paths behave identically to P3. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`3db3d88`](https://github.com/jvgomg/podkit/commit/3db3d887ae2cd19d01ba2c1f00b8682e783fac84) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix multiple bugs discovered during end-to-end Echo Mini hardware validation + + **Sync pipeline:** + - Create temp directory for optimized-copy operations (not just transcodes), fixing "No such file or directory" FFmpeg failures on mass-storage devices + - Capture last 1000 chars of FFmpeg stderr (instead of first 500) so actual errors aren't swallowed by the version banner + + **Device preset content paths:** + - Pass device preset content paths to adapter even when no user overrides exist, fixing Echo Mini's `musicDir: ''` being ignored and files landing in `Music/` instead of device root + + **Artwork:** + - Read embedded artwork during mass-storage device scan (`skipCovers: false`) so artwork presence is correctly detected, preventing false `artwork-added` upgrades on every sync + - Force `yuvj420p` (4:2:0) pixel format in artwork scale filter — JPEG with 4:4:4 chroma subsampling does not display on the Echo Mini + + **Sync tag and preset detection:** + - Treat `quality=copy` sync tags as in-sync when the classifier would also route the source as a copy, preventing false preset-upgrade detection on FLAC-capable mass-storage devices + - Route lossless sources to transcode (not copy) when quality preset is non-lossless, even if the device natively supports the source codec (e.g., FLAC device with quality=high should produce AAC) + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`7ebb7c5`](https://github.com/jvgomg/podkit/commit/7ebb7c5c0e1c7c3d549196347029d9ce660fcb8b) Thanks [@jvgomg](https://github.com/jvgomg)! - Use configurable device label in eject messages instead of hardcoded 'iPod' + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`1caab19`](https://github.com/jvgomg/podkit/commit/1caab1991d43739aaba3d9ae2e4a5dd6575f331a) Thanks [@jvgomg](https://github.com/jvgomg)! - Hard error on invalid `--fields` names with message listing valid fields; valid fields now listed in `--help` + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`3fe7853`](https://github.com/jvgomg/podkit/commit/3fe785330f8b92c21159ae253456942a92e7c8e2) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix stdout truncation when piping CLI output to another process. Commands that used `process.exit(1)` could terminate before stdout buffers flushed, truncating JSON output (e.g. `podkit init --json | node -e ...`). All error exit paths now use `process.exitCode = 1` and return normally, allowing Node.js to drain streams before exiting. + +- [`26733cc`](https://github.com/jvgomg/podkit/commit/26733cc77fd56681387b29e4241ad05e4d1fd348) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix blank source path in sync output for subsonic collections + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`e58ae80`](https://github.com/jvgomg/podkit/commit/e58ae806a494e3f526a828d4b72dab558ae4b121) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix config not found when running `podkit` under `sudo`. The default config path now resolves the invoking user's home directory via `SUDO_USER`/`DOAS_USER` and `/etc/passwd`, rather than using root's home. + +- [`3e95baf`](https://github.com/jvgomg/podkit/commit/3e95baffc65b683b5e3f80906e9a342245a6e4ce) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix iPod model identification regressing to "Unknown iPod" after `doctor --repair sysinfo-extended` on pre-2006 devices (mini 2G), and tighten the package-boundary contract so consumers compose identity instead of injecting resolution policy. + + The bug: each consumer of `ensureSysInfoExtended` / `readSysInfoExtended` passed a serial-only `resolveModel` callback. When the 3-character serial suffix wasn't in `tables/serials.ts`, the resolver returned undefined and the device was displayed as "Unknown iPod" — even when a SysInfo file with a known `ModelNumStr` was sitting next to the SysInfoExtended on disk. + + The fix: + - **Removed** `ModelResolver` type and the `resolveModel` callback from `@podkit/ipod-firmware`. `readSysInfoExtended` and `ensureSysInfoExtended` now return a flat `SysInfoIdentity` bag (`firewireGuid?, serialNumber?, modelNumStr?, familyId?`). When a SysInfo file is on disk alongside SysInfoExtended, its `ModelNumStr` is read opportunistically. + - **Callers compose** with `resolveIpodModel(bag)` from `@podkit/devices-ipod`, which cascades modelNumStr → serial → productId → familyId → libgpodGeneration. The CLI no longer makes resolution decisions. + - **Added** `SYSINFO_PATH`, `SYSINFO_EXTENDED_PATH`, `SYSINFO_DEVICE_DIR` exported from `@podkit/ipod-firmware` and re-exported from `@podkit/core`. Consumers use these constants instead of duplicating the literal `iPod_Control/Device/...` paths. + - **Added** `S4G: '9804'` entry to `tables/serials.ts` (mini 2G 4GB Pink, sourced from real hardware, serial `JQ5141TFS4G`). + - **Post-write enrichment.** After `ensureSysInfoExtended` writes the file via USB inquiry, it now re-reads via `readSysInfoExtended` so the post-write identity bag includes `modelNumStr` from the SysInfo neighbour. Eliminates the cosmetic regression where the repair-success message showed a less-specific name than the subsequent `doctor` run. + +- [`80fe65a`](https://github.com/jvgomg/podkit/commit/80fe65a022c65da512f571a8abf83f9385a649e6) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix SysInfoExtended SCSI-fallback on macOS for SCSI-only iPods (mini 2G, nano 2G, iPod 5G/5.5G). `device add` and `doctor --repair sysinfo-extended` now correctly fall back from USB → SCSI when the device does not respond to vendor control transfers, instead of failing with a misleading "Could not read device identity from USB" error. + + Internal API changes in `@podkit/ipod-firmware`: + - **Changed:** `ensureSysInfoExtended(mountPoint, fp, options?)` now takes a full `UsbFingerprint` instead of the previous `{ busNumber, deviceAddress }` shape. Required so the macOS SCSI transport can locate the IOService via vendorId/productId/serialNumber. `UsbDeviceAddress` is removed. + - **Added:** `inquireFirmwareDetailed(fp, opts?)` — like `inquireFirmware` but returns `{ firmware, plan, attempts }` so callers can distinguish which transports were attempted. `inquireFirmware` is unchanged for existing consumers. + - **Added:** `EnsureSysInfoExtendedOptions` type with `readFromUsb`, `resolveModel`, `inquireOptions` fields. Replaces the previous positional `(mountPoint, fp, readFromUsb, resolveModel)` signature. + + Internal API changes in `@podkit/core`: + - **Added:** `hasCompleteUsbFingerprint(fp): fp is CompleteUsbDevice` type guard exported from `@podkit/core`. + - **Added:** `CompleteUsbDevice` type — a `UsbFingerprint` with vendorId, productId, bus, devnum guaranteed present (serialNumber optional). + - **Changed:** `resolveUsbDeviceFromPath(path)` now also returns `vendorId` and `productId`. Linux extracts from sysfs `idVendor`/`idProduct`; macOS extracts from `system_profiler` JSON. + + User-facing error messages now differentiate between transport failures: "Could not read device identity from USB and SCSI" / "...from USB" / "...from SCSI" / "...no firmware inquiry transport is available on this system" / "...returned data but it could not be parsed". + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`17eac11`](https://github.com/jvgomg/podkit/commit/17eac114719f93cef40beb58381e534a28ebc35f) Thanks [@jvgomg](https://github.com/jvgomg)! - Move spinners and progress bars to stderr and auto-suppress when stdout is not a TTY. Adds `--no-tty` flag for explicit suppression. Piped output (e.g. `podkit collection music --format json | jq .`) now produces clean stdout without needing `--quiet`. + +- [`f61a83b`](https://github.com/jvgomg/podkit/commit/f61a83b3a2d13612730f174759fd3b86edd42e82) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix `podkit device scan` reporting phantom "Unknown iPod (USB only)" entries for non-iPod USB peripherals (mice, hubs, Thunderbolt docks, Ethernet adapters, USB drives). Each phantom suggested `podkit device init` — a destructive operation that could mutate an unrelated device. + + Root cause was architectural: a single `discoverUsbIpods()` function mixed three concerns — USB enumeration, iPod-domain enrichment, and the function name's implied filter (which it didn't actually do). Refactored into clean layers: + - **`@podkit/core` — pure USB enumeration.** `enumerateUsb()` returns `EnumeratedUsbDevice[]` with vendor/product/serial/bus/devnum/diskIdentifier ONLY. No iPod-domain knowledge. + - **`@podkit/devices-ipod` — iPod classifier.** `classifyAsIpod(dev)` returns `IpodClassification | null` (matches Apple-vendor with iPod or iOS PIDs). + - **`@podkit/devices-mass-storage` — mass-storage classifier.** `classifyAsMassStorage(dev)` returns `MassStorageClassification | null` (matches `USB_PRESET_HINTS` entries like Echo Mini). + - **`@podkit/core` composer.** `classifyUsbDevices()` runs both classifiers and returns recognized devices as a tagged union; drops unknown peripherals. + - **CLI `device scan`** now calls `enumerateUsb()` → `classifyUsbDevices()` → renders by `kind`. No domain logic in the command layer. + + Added `kind: 'mass-storage'` rendering branch so mass-storage DAPs are no longer mis-labeled as "Unknown iPod". + + Removed `discoverUsbIpods` and the leaky `UsbDiscoveredDevice` type (which previously carried iPod-domain fields). Adding a new mass-storage device now means adding one entry to `USB_PRESET_HINTS` — no `@podkit/core` change required. Adding a new iPod generation means updating `@podkit/devices-ipod` tables — no other package changes. + + Also split `usb-discovery.ts` into `usb-enumeration.ts` (bus walk) + `usb-path-resolution.ts` (mount-path → fingerprint resolver) since those two concerns were unrelated. + +- [`6000868`](https://github.com/jvgomg/podkit/commit/6000868830d9437a6fff3c1a77adb254d9579fe7) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix `doctor --repair sysinfo-extended` showing unhelpful "Could not read device identity from USB" with no detail. The native USB binding now throws descriptive errors (e.g. "USB control transfer failed (bus 3, device 4)") instead of returning null silently. Also fix all doctor repair intro messages — they incorrectly said "Repairing X for N tracks" even for non-track operations like SysInfoExtended and orphan cleanup. Intro messages now use each repair's own description. + +- [`4598f8f`](https://github.com/jvgomg/podkit/commit/4598f8f3347cf40b94fdf1585215e5b0f54d9cf6) Thanks [@jvgomg](https://github.com/jvgomg)! - USB firmware inquiry consolidated into @podkit/ipod-firmware (P2 — m-18 device-capability architecture). + + **Breaking change in `@podkit/libgpod-node`:** The `readSysInfoExtendedFromUsb` function has been removed from the package's public exports. All in-tree callers were already routed through `@podkit/ipod-firmware` since P1 — only external consumers of `@podkit/libgpod-node` who called this function directly are affected. + + `@podkit/ipod-firmware` now owns the complete firmware inquiry surface: SCSI (Linux SG_IO + macOS IOKit) and USB (direct libusb-1.0 via koffi FFI). The P1 transitional shim that delegated USB reads to libgpod-node has been replaced by a native TypeScript implementation. No API change is visible to callers of `@podkit/ipod-firmware`. + + `@podkit/libgpod-node` no longer requires libusb at build or runtime. Distro packagers can now build the native binding without `libusb-1.0-0-dev` (Debian/Ubuntu), `libusb-devel` (Fedora/RHEL), or equivalent system packages. The `itdb_usb.c` patch, the `dlsym` shim, and the libusb pkg-config dependency have all been removed from the binding. + + No user-facing CLI behaviour changes. `podkit doctor` inquiry checks, `podkit device scan`, and all sync paths behave identically to P1. + +- Updated dependencies [[`0f3e4dd`](https://github.com/jvgomg/podkit/commit/0f3e4ddae134228b5e874b21db33f74547867b6c), [`036b107`](https://github.com/jvgomg/podkit/commit/036b1077748253385b6f4ff873a7cdb52c54b004), [`89ff40c`](https://github.com/jvgomg/podkit/commit/89ff40c2adedd9fec38ae5ad0eb89b75525642f2), [`c5c0236`](https://github.com/jvgomg/podkit/commit/c5c0236c232cc3fa086fd3937b0e2fbe0f326185), [`bb2e637`](https://github.com/jvgomg/podkit/commit/bb2e6374151605d11baf052c452f10a842e5353e), [`0d4a4c2`](https://github.com/jvgomg/podkit/commit/0d4a4c2bd98667989b9631d981e609bc72e604af), [`513173d`](https://github.com/jvgomg/podkit/commit/513173d1832bf9ca2894214e97d9d65cf02c52a5), [`0cc39d3`](https://github.com/jvgomg/podkit/commit/0cc39d3c62343591127d5c79deed2478f8dc4f60), [`22dddf4`](https://github.com/jvgomg/podkit/commit/22dddf4803f4cfd7b004d80dffd83878a68b10f2), [`348f2c5`](https://github.com/jvgomg/podkit/commit/348f2c53cec06598903b5cf128663d5121c46865), [`7534c2f`](https://github.com/jvgomg/podkit/commit/7534c2f19d81087413af8abbf764fe20cef61384), [`d1147e4`](https://github.com/jvgomg/podkit/commit/d1147e4a65ac103608da3730f530f6deab3cd0b6), [`6747667`](https://github.com/jvgomg/podkit/commit/6747667049cd793fdb13e3d1bc1092651f8e969c), [`8bc3126`](https://github.com/jvgomg/podkit/commit/8bc3126ec415aa836b746ec921b6738abdd9e538), [`01ecedd`](https://github.com/jvgomg/podkit/commit/01ecedde623ff99e94c5cbda75ff9f9c9ecef632), [`03f1046`](https://github.com/jvgomg/podkit/commit/03f1046b70898b0282d0c96927bca60ee0d55eeb), [`14d83e5`](https://github.com/jvgomg/podkit/commit/14d83e5e59eb0a8a801850de775f9fdb4c0e7aa9), [`3db3d88`](https://github.com/jvgomg/podkit/commit/3db3d887ae2cd19d01ba2c1f00b8682e783fac84), [`7ebb7c5`](https://github.com/jvgomg/podkit/commit/7ebb7c5c0e1c7c3d549196347029d9ce660fcb8b), [`34e8bf2`](https://github.com/jvgomg/podkit/commit/34e8bf2341111df1e8f85361b8047eed9f31665a), [`3e95baf`](https://github.com/jvgomg/podkit/commit/3e95baffc65b683b5e3f80906e9a342245a6e4ce), [`bddea04`](https://github.com/jvgomg/podkit/commit/bddea044342ca9027fc95593a35795fd8de1faf4), [`09c4acd`](https://github.com/jvgomg/podkit/commit/09c4acdec349f200a649b2db15fe05345e380a7b), [`22dddf4`](https://github.com/jvgomg/podkit/commit/22dddf4803f4cfd7b004d80dffd83878a68b10f2), [`94c85d2`](https://github.com/jvgomg/podkit/commit/94c85d2a9d6c85875432a0ebecab540a9ebd67d7), [`efa14c6`](https://github.com/jvgomg/podkit/commit/efa14c623e7bda81066bd77142cddb28e4de615d), [`208e482`](https://github.com/jvgomg/podkit/commit/208e482db9730064a25e53e03121bdcfcbea6341), [`bb96778`](https://github.com/jvgomg/podkit/commit/bb96778dde9063267188b2b83535ec279cd5c550), [`f72fa01`](https://github.com/jvgomg/podkit/commit/f72fa0170872fc0a6e5719b4509abae24e6414cd), [`c9c268e`](https://github.com/jvgomg/podkit/commit/c9c268ea4b25b39543e5c53a1928e72b4c31e0c8), [`1ec30ac`](https://github.com/jvgomg/podkit/commit/1ec30acca1109178012db3913a60967a2087fb5b), [`80fe65a`](https://github.com/jvgomg/podkit/commit/80fe65a022c65da512f571a8abf83f9385a649e6), [`52894c1`](https://github.com/jvgomg/podkit/commit/52894c1977bccd51a86929debfbaa7028a19dd61), [`c5cba69`](https://github.com/jvgomg/podkit/commit/c5cba6998283663b42659f02b17b194ab256c137), [`1c3ebc3`](https://github.com/jvgomg/podkit/commit/1c3ebc381276accdb8361f50454b90c75f2391df), [`f61a83b`](https://github.com/jvgomg/podkit/commit/f61a83b3a2d13612730f174759fd3b86edd42e82), [`6000868`](https://github.com/jvgomg/podkit/commit/6000868830d9437a6fff3c1a77adb254d9579fe7), [`4598f8f`](https://github.com/jvgomg/podkit/commit/4598f8f3347cf40b94fdf1585215e5b0f54d9cf6), [`e825ee1`](https://github.com/jvgomg/podkit/commit/e825ee1dd4933ecbfd070dda27f96f43056f0baf)]: + - @podkit/core@0.7.0 + - @podkit/ipod-firmware@0.1.0 + - @podkit/device-types@0.1.0 + - @podkit/devices-mass-storage@0.1.0 + - @podkit/devices-ipod@0.1.0 + - @podkit/libgpod-node@0.2.0 + ## 0.6.0 ### Minor Changes diff --git a/packages/podkit-cli/package.json b/packages/podkit-cli/package.json index 3dd4d844..404404ad 100644 --- a/packages/podkit-cli/package.json +++ b/packages/podkit-cli/package.json @@ -1,6 +1,6 @@ { "name": "podkit", - "version": "0.6.0", + "version": "0.7.0", "type": "module", "bin": { "podkit": "./dist/main.js" diff --git a/packages/podkit-core/CHANGELOG.md b/packages/podkit-core/CHANGELOG.md index db46ed1c..eefd2a67 100644 --- a/packages/podkit-core/CHANGELOG.md +++ b/packages/podkit-core/CHANGELOG.md @@ -1,5 +1,466 @@ # @podkit/core +## 0.7.0 + +### Minor Changes + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`0f3e4dd`](https://github.com/jvgomg/podkit/commit/0f3e4ddae134228b5e874b21db33f74547867b6c) Thanks [@jvgomg](https://github.com/jvgomg)! - Add capability-gated clean artists transform + + Devices now declare whether they use Album Artist for browse navigation via `supportsAlbumArtistBrowsing`. When enabled globally, the `cleanArtists` transform is automatically suppressed on devices that support Album Artist browsing (Rockbox, Echo Mini, generic) and auto-applied on devices that don't (iPod). Per-device overrides still take priority. + + The dry-run summary shows when the transform is skipped (`Clean artists: skipped (device supports Album Artist browsing)`), and warns when it's force-enabled on a capable device. Both `sync --dry-run` and `device info` surface these in text and JSON output. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`036b107`](https://github.com/jvgomg/podkit/commit/036b1077748253385b6f4ff873a7cdb52c54b004) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix mass-storage directory structure to use album artist instead of track artist, and add template-based path system with self-healing relocate. + + **Bug fix:** Mass-storage devices (Echo Mini, Rockbox) now use `albumArtist` for directory grouping, falling back to `artist` when absent. Previously, compilation/various-artist albums had their tracks scattered across separate artist directories instead of being grouped together under the album artist. + + **Path templates:** File paths are now generated from a configurable template string (`{albumArtist}/{album}/{trackNumber} - {title}{ext}` by default). This lays the groundwork for user-customisable folder structures in a future release. + + **Self-healing relocate:** When source metadata changes (e.g. album artist corrected) or the path template changes, the next sync detects the path mismatch and moves files to their correct location via `fs.rename()` — no re-copying of audio data. Relocate operations appear in dry-run output and are tracked as a new `relocate` operation type. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`89ff40c`](https://github.com/jvgomg/podkit/commit/89ff40c2adedd9fec38ae5ad0eb89b75525642f2) Thanks [@jvgomg](https://github.com/jvgomg)! - Add audioNormalization device capability for device-appropriate Sound Check / ReplayGain handling + + Devices now declare their normalization support: 'soundcheck' (iPod), 'replaygain' (Rockbox), or 'none' (Echo Mini, generic). Devices with no normalization support skip soundcheck upgrade detection entirely, and the dry-run output hides or relabels the normalization line accordingly. Configurable via `audioNormalization` in device config. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`c5c0236`](https://github.com/jvgomg/podkit/commit/c5c0236c232cc3fa086fd3937b0e2fbe0f326185) Thanks [@jvgomg](https://github.com/jvgomg)! - Refactor audio normalization from iPod-centric Sound Check to a generic `AudioNormalization` type, and add ReplayGain album gain/peak support + + **Normalization refactoring:** + - Introduce `AudioNormalization` type that preserves source format fidelity (ReplayGain dB, iTunNORM soundcheck integers) without unnecessary round-trip conversions + - Replace scattered `soundcheck`, `soundcheckSource`, `replayGainTrackGain`, `replayGainTrackPeak` fields on `CollectionTrack` with a single `normalization` field + - Replace `soundcheck`, `replayGainTrackGain`, `replayGainTrackPeak` fields on `DeviceTrackInput` with `normalization` + - Conversions now happen at device boundaries: iPod adapter reads soundcheck integers, mass-storage adapter reads dB values directly + - Upgrade detection compares in dB space with 0.1 dB epsilon tolerance, eliminating false positives from integer rounding + - Metadata update diffs show human-readable dB values (e.g., `normalization: -7.5 dB → -6.2 dB`) instead of opaque integers + + **Album gain/peak support (TASK-253):** + - Extract `albumGain` and `albumPeak` from local file metadata and Subsonic API + - Write `REPLAYGAIN_ALBUM_GAIN` and `REPLAYGAIN_ALBUM_PEAK` via FFmpeg metadata flags during transcode + - Write album gain/peak via node-taglib-sharp tag writer for M4A files + - Thread album data through the full sync pipeline for mass-storage devices (Rockbox, etc.) + + **Breaking changes:** + - `CollectionTrack` shape: four normalization fields replaced by single `normalization?: AudioNormalization` + - `SoundCheckSource` type removed, replaced by `NormalizationSource` + - Upgrade reason `'soundcheck-update'` renamed to `'normalization-update'` in JSON output + - `soundCheckTracks` stat renamed to `normalizedTracks` + +- [`0d4a4c2`](https://github.com/jvgomg/podkit/commit/0d4a4c2bd98667989b9631d981e609bc72e604af) Thanks [@jvgomg](https://github.com/jvgomg)! - Disambiguate codec from container: `AudioCodec` value `'ogg'` is renamed to `'vorbis'` + + The `AudioCodec` slot previously used `'ogg'` to mean "OGG Vorbis." That conflated the OGG container with the Vorbis stream codec and could not represent Vorbis-in-OGG vs Opus-in-OGG as distinct device capabilities — Echo Mini (which plays Vorbis but hides `.opus` files) could not be modelled accurately. The codec slot now names the audio stream codec; `'vorbis'` replaces `'ogg'` in device presets and config. + + Configs containing `supportedAudioCodecs = ["…", "ogg", "…"]` under `[devices.*]` are migrated automatically by `podkit migrate` (config version 1 → 2). The migration is purely a string substitution inside the `supportedAudioCodecs` array; comments and surrounding formatting are preserved. + + Also lands as type-level groundwork for the future container-aware sync work: `AudioContainer`, `AUDIO_CONTAINERS`, `CODEC_CANONICAL_CONTAINER`, and an optional `DeviceCapabilities.containerConstraints` field. These are declared and exported but not yet read by the planner; they are placeholders for the upcoming Phase 2 work documented in the container-aware sync PRD. + + `DirectoryAdapter` now uses each `.ogg` file's probed stream codec (already populated by `music-metadata`) to distinguish Vorbis, Opus, and OGG-FLAC — same pattern as the existing AAC/ALAC distinction for `.m4a`. `SubsonicAdapter` additionally checks the API's `contentType` field for Opus-in-`.ogg`. The Subsonic check is best-effort because most Subsonic servers report container MIME (`audio/ogg`) regardless of stream codec; deeper probing is deferred until evidence of real-world impact. + + User-facing reference page added at `docs/reference/codec-support.md` explaining the codec/container model and what each `AudioCodec` value means. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`513173d`](https://github.com/jvgomg/podkit/commit/513173d1832bf9ca2894214e97d9d65cf02c52a5) Thanks [@jvgomg](https://github.com/jvgomg)! - Add configurable codec preference system for multi-device audio format support + + Users can now configure an ordered list of preferred audio codecs globally and per-device. The system walks the list top-to-bottom, selecting the first codec that is both supported by the target device and has an available FFmpeg encoder. This replaces the hardcoded AAC-only transcoding pipeline. + - **Default lossy stack:** opus → aac → mp3 (Rockbox devices get Opus automatically, iPods fall through to AAC) + - **Default lossless stack:** source → flac → alac (lossless files are kept in their original format when possible) + - **Quality presets are codec-aware:** "high" delivers perceptually equivalent quality regardless of codec (e.g., Opus 160 kbps ≈ AAC 256 kbps) + - **Codec change detection:** changing your codec preference re-transcodes affected tracks on the next sync + - **`podkit device info`** shows your codec preference list with supported/unsupported codecs marked + - **`podkit sync --dry-run`** shows which codec will be used and any codec changes + - **`podkit doctor`** warns when FFmpeg is missing an encoder for a preferred codec + + Configure via `config.toml`: + + ```toml + [codec] + lossy = ["opus", "aac", "mp3"] + lossless = ["source", "flac", "alac"] + + [devices.myipod.codec] + lossy = "aac" + ``` + + No configuration is required — existing setups work unchanged with sensible defaults. + +- [`0cc39d3`](https://github.com/jvgomg/podkit/commit/0cc39d3c62343591127d5c79deed2478f8dc4f60) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix track metadata convergence on mass-storage devices and add transfer-mode-aware on-disk tag writes for iPod portable. + + **Bug fix (mass-storage)**: `MassStorageAdapter.updateTrack` previously only wrote `comment`, OGG/Opus artwork, and ReplayGain to disk. All other metadata fields (title, artist, album, albumArtist, genre, year, trackNumber, discNumber, compilation) updated in-memory only — the file's embedded tags on the device were never rewritten. After a relocate or metadata-correction sync the next sync re-detected the same diff every time, looping forever as a zero-byte `update-metadata` op. + + `MassStorageAdapter` now queues every changed textual tag in a single `pendingTagWrites` map and flushes them as one `writeTags(filePath, fields)` call per file via `Promise.allSettled`. Per-file failures are aggregated and re-thrown so the sync executor can categorise them. + + **New behaviour (iPod portable)**: `IpodDeviceAdapter` now mirrors iTunesDB metadata into the on-disk file tags when `transferMode === 'portable'`. This makes files pulled off the iPod self-describing for re-import into a music library. `fast` and `optimized` modes still touch iTunesDB only — the iPod firmware reads metadata from iTunesDB and never falls back to file tags during playback, so paying the tag-rewrite cost in those modes would be wasted work. + + Tag writes are best-effort on iPod portable: failures are surfaced as warnings, not hard errors, because the iTunesDB write (the authoritative store for playback) already succeeded. + + **`addTrack` consistency**: When `transferMode === 'portable'`, both backends now also rewrite tags on first transfer to honour any collection-adapter transforms (e.g. clean-artists, Subsonic-side corrections) that FFmpeg's `-map_metadata 0` would otherwise copy through from the source. + + **On first sync after upgrade**: Existing mass-storage tracks will likely report a `metadata-correction` op on the next sync as stale on-disk tags converge to source values. These are zero-byte writes — no transcoding or transfers happen — but the operation list will look longer than usual for one cycle. + + **Scope notes**: + - Match-key changes (title, artist, album corrections) still produce a remove+add rather than a metadata update. By design: when those fields change, podkit treats it as a different track. + - Virtual-iPod (m-17) inherits the iPod behaviour automatically; no changes needed there. + +- [`22dddf4`](https://github.com/jvgomg/podkit/commit/22dddf4803f4cfd7b004d80dffd83878a68b10f2) Thanks [@jvgomg](https://github.com/jvgomg)! - Add capability adapter and fix iPod generation metadata + - Add `createIpodCapabilities()` adapter — maps libgpod device data to core `DeviceCapabilities`, using libgpod as authority for video/artwork support and supplementing codec support from generation metadata + - Add `toLibgpodGeneration()` mapping from detection-layer IDs (`nano_4g`) to libgpod IDs (`nano_4`) + - Fix artwork resolution: nano 3G→320, nano 4-6G→240, photo→320 (were all incorrectly 176) + - Fix ALAC support: add to 4th gen, Photo, Mini 2G, Touch 1-4, iPhone 1-4, iPad 1 + - Add video profiles for Touch, iPhone, and iPad generations (were missing, preventing video sync) + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`7534c2f`](https://github.com/jvgomg/podkit/commit/7534c2f19d81087413af8abbf764fe20cef61384) Thanks [@jvgomg](https://github.com/jvgomg)! - Add device-aware diagnostics framework to `podkit doctor`. The doctor command now handles mass-storage devices gracefully instead of crashing when pointed at a non-iPod device. Diagnostic checks declare which device types they apply to, and the runner filters them automatically. JSON output now includes a `deviceType` field. + +- [`d1147e4`](https://github.com/jvgomg/podkit/commit/d1147e4a65ac103608da3730f530f6deab3cd0b6) Thanks [@jvgomg](https://github.com/jvgomg)! - P4 — device-capability architecture complete. All in-tree migration finished; deprecated shims removed. + + **Breaking changes in `@podkit/core`:** The following symbols have been removed from the public API: `createIpodCapabilities`, `LibgpodDeviceInfo`, `DEVICE_PRESETS`, `DevicePreset`, `getDevicePreset`, and `resolveDeviceCapabilities`. Callers must migrate before upgrading. + + Migration guide: + - `createIpodCapabilities(libgpodInfo)` → `resolveCapabilities(identity)` (preferred, identity-driven) or `resolveIpodModelCapabilities(modelFromLibgpodInfo(libgpodInfo))` for callers that genuinely hold libgpod data. + - `getDevicePreset(deviceType)` → `BUILT_IN_PRESETS[deviceType]` from `@podkit/devices-mass-storage`. + - `DEVICE_PRESETS` → `BUILT_IN_PRESETS` from `@podkit/devices-mass-storage`. + - `resolveDeviceCapabilities(type, overrides)` → `resolveCapabilities(identity, { overrides })` from `@podkit/core`. + - The `core/device/sysinfo-extended` shim path is gone; import `readSysInfoExtended`, `writeSysInfoExtended`, and `ensureSysInfoExtended` from `@podkit/ipod-firmware` directly. + + See ADR-295.07 for the full architectural rationale. + + **New in `@podkit/ipod-firmware`:** SysInfoExtended file I/O is now owned by this package: `readSysInfoExtended`, `writeSysInfoExtended`, `ensureSysInfoExtended`, `SYSINFO_EXTENDED_PATH`, `SYSINFO_DEVICE_DIR`. Diagnostic helpers `compareSysInfoConsistency` and `normaliseFireWireGuid` are also exported. `ParsedFirmware` gains the optional `modelNumber` field (populated from the `ModelNumStr` plist key when present). + + **New in `@podkit/device-types`:** `IpodModel`, `IpodChecksumType`, `IpodGenerationId`, `IpodGenerationIdLike`, `IpodModelSource`, and `IPOD_GENERATION_IDS` are now exported from this package (canonical home). `UsbConnectionInfo` has been removed — use `UsbFingerprint` instead. `IpodIdentity.notSupportedReason` is added for devices identified as iPods that podkit cannot fully support (e.g. iOS-mode devices). `DeviceCapabilities.artworkMaxResolution` is now `number | null` (null when the generation has no known limit or artwork is unsupported). + + **In `@podkit/devices-ipod`:** `IpodGeneration.supported` and `IpodGeneration.artworkMaxResolution: number | null` are new fields on every generation entry. `lookupByFamilyId` and `FAMILY_ID_TO_GENERATION` are now exported. `unsupported.ts` is populated with comprehensive Apple iOS device PIDs to allow early rejection of phones and tablets at the USB identification stage. + + No user-facing CLI behaviour changes. `podkit device scan`, `podkit device info`, and all sync paths behave identically to P3. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`8bc3126`](https://github.com/jvgomg/podkit/commit/8bc3126ec415aa836b746ec921b6738abdd9e538) Thanks [@jvgomg](https://github.com/jvgomg)! - Add device readiness diagnostic system + - New 6-stage readiness pipeline (USB → Partition → Filesystem → Mount → SysInfo → Database) that checks every stage of device health + - OS error code interpreter translates errno values into actionable explanations + - USB discovery finds iPods even without disk representation (unpartitioned/uninitialized devices) + - Enhanced SysInfo validation detects missing, corrupt, or unrecognized model files + - Diagnostics framework now handles missing database gracefully (checks skip instead of crashing) + +- [`01ecedd`](https://github.com/jvgomg/podkit/commit/01ecedde623ff99e94c5cbda75ff9f9c9ecef632) Thanks [@jvgomg](https://github.com/jvgomg)! - New packages: `@podkit/devices-ipod` (canonical home for iPod generation tables, model lookups, and capability synthesis) and `@podkit/devices-mass-storage` (user-extensible DAP preset framework for Echo Mini, Rockbox, generic, and custom devices). + + Echo Mini is now auto-detected at `device add` — when the USB descriptor matches the known VID/PID (`0x071b`/`0x3203`), no `--type echo-mini` flag is required. + + `enumerateConnectedDevices` is now the recommended way to discover and classify USB devices. It accepts a `providers: DeviceProvider[]` array and returns `EnumeratedDevice[]` carrying both the USB connection info and the provider-produced identity. + + `getCapabilities` in `@podkit/devices-ipod` is libgpod-free. Capability synthesis is purely table-and-firmware-driven; the legacy `createIpodCapabilities` adapter that depended on a live libgpod `LibgpodDeviceInfo` struct is deprecated in `@podkit/core`. Parity is verified across all 29 generations (the 4 that were libgpod `unknown` degenerate cases are now correctly populated from the table). + + Internal re-export shims in `@podkit/core` keep all existing call paths compiling for one release. The shims delegate to `@podkit/devices-ipod` and `@podkit/devices-mass-storage` and will be removed in P4. + +- [`03f1046`](https://github.com/jvgomg/podkit/commit/03f1046b70898b0282d0c96927bca60ee0d55eeb) Thanks [@jvgomg](https://github.com/jvgomg)! - Add `podkit doctor --repair artwork-reset` to clear all artwork from an iPod without needing a source collection. This is a fast alternative to a full rebuild — useful when your source collection isn't available or you just want to clear corrupted artwork quickly. + + Rename `--repair artwork-integrity` to `--repair artwork-rebuild` to better describe what the repair does. The old name no longer works. + +- [`14d83e5`](https://github.com/jvgomg/podkit/commit/14d83e5e59eb0a8a801850de775f9fdb4c0e7aa9) Thanks [@jvgomg](https://github.com/jvgomg)! - `podkit doctor` gains `--no-system` to skip system-scope checks (FFmpeg encoders, libusb availability, udev rule). System checks remain on by default; pass `--no-system` for device-only diagnostics or in tests where the host environment shouldn't influence the result. + + The `sysinfo-consistency` check is redesigned: a missing `SysInfoExtended` file is now `skip` (not `fail`) since absence is not a failure mode. When the file is present it's compared against the live device on two independent axes — FireWireGUID and model generation — and only fails when at least one axis can be evaluated and disagrees. The check picks up live device data via the new `liveIdentity` field on `DiagnosticContext`, which `runDiagnostics` accepts as part of `RunDiagnosticsInput`. + +- [`bddea04`](https://github.com/jvgomg/podkit/commit/bddea044342ca9027fc95593a35795fd8de1faf4) Thanks [@jvgomg](https://github.com/jvgomg)! - Add SCSI firmware inquiry for iPod identification (P1 — m-18 device-capability architecture). + + `@podkit/device-types` (first published release) provides the canonical shared type definitions — `DeviceCapabilities`, `DeviceIdentity`, `ParsedFirmware`, and `DeviceProvider` — used across the podkit monorepo without circular dependencies. + + `@podkit/ipod-firmware` (first published release) implements iPod firmware inquiry via SCSI (Linux SG_IO + macOS IOKit, using koffi FFI) with USB fallback through the existing libgpod-node binding. Devices that previously failed identification over USB — including iPod mini 2G, nano 2G, and some iPod 5G Video configurations — can now be identified via SCSI. The orchestrator probes available transports at startup, prefers USB when both are available, and falls back to SCSI transparently. + + `@podkit/core` now routes `ensureSysInfoExtended` through the new orchestrator with SCSI fallback, and registers two new `podkit doctor` checks: `inquiry-methods` (reports which transports are available on this host) and `sysinfo-consistency` (validates that the on-disk SysInfo file matches the live firmware read). EACCES errors from SCSI include step-by-step recovery instructions. + + `podkit` CLI gains `--repair udev-rule` in `podkit doctor` to install the Linux udev rule that grants non-root `/dev/sg*` access, and surfaces the new doctor checks in the readiness output. + +- [`09c4acd`](https://github.com/jvgomg/podkit/commit/09c4acdec349f200a649b2db15fe05345e380a7b) Thanks [@jvgomg](https://github.com/jvgomg)! - Add canonical IpodModel type for structured device identity + - Add `IpodModel` interface — canonical representation of identified iPod model with `displayName`, `generationId`, `checksumType`, `color`, `capacityGb`, `modelNumber`, and `source` provenance + - Add `resolveIpodModel()` factory — builds an `IpodModel` from USB product ID, SysInfo model number, or serial number suffix + - Add `UsbConnectionInfo` interface — pure USB bus topology data, split from device identity + - Restructure `UsbDiscoveredDevice` to carry `usb: UsbConnectionInfo` + `model?: IpodModel` + - Add `usbModel` and `deviceModel` to `ReadinessResult` — USB-derived and SysInfo-derived models kept separate for mismatch detection + - Update `SysInfoExtendedResult` with structured `model`, `firewireGuid`, `serialNumber` fields + - Clean `checkSysInfo()` return type — new `SysInfoCheckResult` separates stage result from device model + - Add `model` to JSON output for `device scan` and `device info` commands + - `device scan` and `device info` now display richest available model name (color/capacity from SysInfo when available) + - Remove `UsbDeviceInfo` type (replaced by `UsbConnectionInfo` + `IpodModel`) + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`94c85d2`](https://github.com/jvgomg/podkit/commit/94c85d2a9d6c85875432a0ebecab540a9ebd67d7) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix `--delete` to only remove managed files on mass-storage devices, and add orphan file detection via `podkit doctor`. + + **Bug fix:** `--delete` previously removed all unmatched files on mass-storage devices, including user-placed files. It now only removes files that podkit manages (tracked in `.podkit/state.json`), matching iPod behavior where only database tracks are candidates for deletion. + + **Collision detection:** Sync now detects when a planned file write would collide with an existing unmanaged file and reports the conflict before writing. Works in both normal sync and `--dry-run` mode. + + **New diagnostic check:** `podkit doctor` now runs health checks on mass-storage devices. The `orphan-files-mass-storage` check detects unmanaged files in content directories and can clean them up via `podkit doctor --repair orphan-files-mass-storage`. + + **Other improvements:** + - State manifest (`.podkit/state.json`) is now written without pretty-printing to reduce file size on device storage + - Shell completions now include valid repair IDs for the `--repair` option + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`efa14c6`](https://github.com/jvgomg/podkit/commit/efa14c623e7bda81066bd77142cddb28e4de615d) Thanks [@jvgomg](https://github.com/jvgomg)! - Add mass-storage device support for non-iPod portable music players. + + **Supported device types:** Echo Mini, Rockbox, and generic mass-storage DAPs. iPod support is unchanged. + + **New in CLI (`podkit`):** + - `podkit device add --type ` registers mass-storage devices by type and mount path + - `podkit device info/music/video` work with mass-storage devices via `DeviceAdapter` interface + - `podkit device scan` shows configured path-based devices alongside auto-detected iPods + - `podkit sync` routes to the correct adapter (iPod or mass-storage) based on device config + - Video sync now uses capabilities-based gating instead of iPod-only checks + - Safety gates on `device init/reset/clear` (iPod-only commands) for mass-storage devices + - Mount and eject commands show device-appropriate messaging + - Config validation rejects capability overrides on iPod devices (capabilities are auto-detected from generation) + - Shared `openDevice()` function eliminates duplicated device-opening logic across commands + + **New in core (`@podkit/core`):** + - `DeviceAdapter` interface — generic abstraction over device databases (iPod, mass-storage) + - `MassStorageAdapter` — filesystem-based track management with `.podkit/state.json` manifest + - `IpodDeviceAdapter` — thin wrapper making `IpodDatabase` implement `DeviceAdapter` + - Device capability presets for Echo Mini, Rockbox, and generic devices + - `resolveDeviceCapabilities()` merges preset defaults with user config overrides + - `DeviceTrack` type used throughout sync engine (replaces `IPodTrack` casts in execution paths) + - Configurable content path prefixes (`musicDir`, `moviesDir`, `tvShowsDir`) with device-type defaults + - Device presets include default content paths (Echo Mini: root for music; generic/Rockbox: `Music/`, `Video/Movies/`, `Video/Shows/`) + - Manifest v2 stores active content paths; files automatically moved when prefixes change + - Root path support (`/`, `.`, or empty string all normalize to device root) + - Content path duplicate validation (no two content types can share the same prefix) + - Video scanning support for mass-storage devices (.m4v, .mp4, .mov, .avi, .mkv) + + **New in daemon (`@podkit/daemon`):** + - Mass-storage device polling via `PODKIT_MASS_STORAGE_PATHS` env var (colon/comma separated) + - Second `DevicePoller` + `SyncOrchestrator` pair for mass-storage devices + - No-op mount/eject runners (mass-storage devices are externally managed) + - Graceful shutdown handles both iPod and mass-storage sync pipelines + + **Configuration:** + + ```toml + [devices.echo] + type = "echo-mini" + path = "/Volumes/ECHO" + + # Optional capability overrides (mass-storage only) + artworkMaxResolution = 800 + supportedAudioCodecs = ["aac", "mp3", "flac"] + + # Optional content path overrides (mass-storage only) + musicDir = "/" # Place music at device root + moviesDir = "Films" # Custom movies directory + tvShowsDir = "TV Shows" # Custom TV shows directory + ``` + + **Environment variables for content paths:** + - `PODKIT_MUSIC_DIR` — global default music directory + - `PODKIT_MOVIES_DIR` — global default movies directory + - `PODKIT_TV_SHOWS_DIR` — global default TV shows directory + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`208e482`](https://github.com/jvgomg/podkit/commit/208e482db9730064a25e53e03121bdcfcbea6341) Thanks [@jvgomg](https://github.com/jvgomg)! - Embed artwork in OGG/Opus files for mass-storage devices with `artworkSources: ['embedded']`. + + FFmpeg's OGG muxer cannot write image streams (upstream tickets [#4448](https://github.com/jvgomg/podkit/issues/4448), [#9044](https://github.com/jvgomg/podkit/issues/9044), open since 2015), so OGG output was previously stripped of artwork with `-vn`. Mass-storage devices relying on embedded artwork showed no cover art for Opus tracks. + + **What changed:** + - After FFmpeg produces an OGG file (with artwork stripped), the pipeline post-processes it via node-taglib-sharp to embed artwork as a `METADATA_BLOCK_PICTURE` Vorbis comment + - Artwork is resized to the device's `artworkMaxResolution` before embedding, matching the behavior of other formats where FFmpeg handles resize during transcode + - Resize results are cached per-album to avoid redundant FFmpeg image-processing spawns + - New `TagWriter.writePicture()` method and `resizeArtwork()` utility + - Pending picture writes follow the same deferred flush pattern as comment and ReplayGain tag writes (queued by `updateTrack`, flushed by `save()`) + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`bb96778`](https://github.com/jvgomg/podkit/commit/bb96778dde9063267188b2b83535ec279cd5c550) Thanks [@jvgomg](https://github.com/jvgomg)! - Write ReplayGain tags to transcoded files for mass-storage devices with `audioNormalization: 'replaygain'` (e.g., Rockbox). + + Previously, ReplayGain data was only stored as iPod soundcheck values in the iTunes database. Mass-storage devices read volume normalization from file tags, but FLAC→AAC transcoding strips ReplayGain metadata. Tracks on Rockbox devices played without normalization. + + **What changed:** + - ReplayGain tags (`REPLAYGAIN_TRACK_GAIN`, `REPLAYGAIN_TRACK_PEAK`) are injected via FFmpeg `-metadata` flags during transcoding for MP3, FLAC, and OGG/Opus output + - M4A files (where FFmpeg can't write ReplayGain metadata) get tags written via node-taglib-sharp after transfer + - Raw ReplayGain dB/peak values are preserved from collection sources (Subsonic API, local files) through the sync pipeline, avoiding precision loss from soundcheck integer conversion + - Device scan reads ReplayGain from file tags so the sync engine can detect when normalization data changes and needs updating + - Direct-copy operations skip tag writing since source files already have correct tags + - `soundcheckToReplayGainDb()` reverse conversion function added for back-converting when raw values aren't available + + **Bug fix:** `IpodTrackImpl` used `data.soundcheck || undefined` which coerced a valid soundcheck of `0` to `undefined`. Changed to `data.soundcheck ?? undefined`. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`f72fa01`](https://github.com/jvgomg/podkit/commit/f72fa0170872fc0a6e5719b4509abae24e6414cd) Thanks [@jvgomg](https://github.com/jvgomg)! - Refactor sync engine to be fully content-type-agnostic with per-handler operation types. + + **Breaking:** `createMusicHandler()` and `createVideoHandler()` now take a config object at construction instead of using `setTransformsConfig()`/`setExecutionConfig()`. Removed `HandlerDiffOptions`, `HandlerPlanOptions`, `MusicExecutionConfig` types. Renamed `MusicExecutor` to `MusicPipeline`. Removed legacy planner functions (`createMusicPlan`, `planVideoSync` and related helpers). + + **New:** `MusicSyncConfig`, `VideoSyncConfig`, `MusicTrackClassifier`, `VideoTrackClassifier`, `MusicOperationFactory`, `MusicOperation`, `VideoOperation`, `BaseOperation` types. Handlers now own their operation types via `TOp` type parameter on `ContentTypeHandler`. + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`c9c268e`](https://github.com/jvgomg/podkit/commit/c9c268ea4b25b39543e5c53a1928e72b4c31e0c8) Thanks [@jvgomg](https://github.com/jvgomg)! - Internalize raw sync tag functions (`parseSyncTag`, `formatSyncTag`, `writeSyncTag`) from the public API. Sync tag reads now use the typed `DeviceTrack.syncTag` field, and writes use `DeviceAdapter.writeSyncTag()` or the new `update-sync-tag` operation type. Adds `SyncTagUpdate` type and `syncTagsEqual` to the public API. + +- [#59](https://github.com/jvgomg/podkit/pull/59) [`1ec30ac`](https://github.com/jvgomg/podkit/commit/1ec30acca1109178012db3913a60967a2087fb5b) Thanks [@jvgomg](https://github.com/jvgomg)! - Automated iPod device identification via SysInfoExtended. + + Modern iPods (post-2006) ship without a populated `SysInfo` file after iTunes restore. Without it, libgpod treats the device as generic — artwork breaks, ALAC support is unknown, and database checksums fail on Classic 6/7G and Nano 3G+. podkit now reads SysInfoExtended directly from iPod firmware over USB during `device add`, so first-time setup works with no manual tooling. + + **User-visible:** + - `podkit device add` identifies the exact model (e.g. "iPod nano 8GB Black (3rd Generation)") with no input + - `podkit doctor` detects missing SysInfoExtended and offers `--repair sysinfo-extended` + - `podkit sync` works correctly on first run with full capability detection + - Hash72 (Nano 5G) and HashAB (Nano 6G) devices get clear limitation messages + - SysInfoExtended write is gated on user confirmation during `device add` + + **Core (`@podkit/core`):** + - Unified iPod model registry — single table, both `0x120x`/`0x126x` USB ID ranges, 190+ serial-suffix → model mappings, checksum-type classification per generation + - `ensureSysInfoExtended()` orchestrator: check existing → USB read → validate XML → write + - USB discovery now exposes `serialNumber`, `busNumber`, `deviceAddress`; `resolveUsbDeviceFromPath()` on macOS + Linux + - Readiness pipeline: checksum-aware severity (hash58+ devices fail without SysInfoExtended; pre-checksum devices warn) + - `READINESS_RULES` declarative array replaces ad-hoc `determineLevel()` logic + - New `sysinfo-extended` diagnostic check + - Recognizes `P` / `F` model prefixes in SysInfo + + **libgpod-node (`@podkit/libgpod-node`):** + - `readSysInfoExtendedFromUsb()` N-API binding, resolved via `dlsym` at runtime so it loads gracefully on systems where libgpod lacks the symbol + - Prebuild patches upstream libgpod 0.8.3 to move `itdb_usb.c` from `tools/` into the library; libusb 1.0.27 built from source on all 6 platforms + - `--whole-archive` / `-force_load` linker flags preserve the dlsym symbol in the `.node` binary + + **CLI (`podkit`):** + - `device add` attempts SysInfoExtended read after mount, before DB init; enriches model name in summary + - `doctor` adds suggested-actions section, drops destructive sysinfo guidance + - `device scan` and `doctor` show clearer SysInfo readout + +- [`c5cba69`](https://github.com/jvgomg/podkit/commit/c5cba6998283663b42659f02b17b194ab256c137) Thanks [@jvgomg](https://github.com/jvgomg)! - Polish on the convergent-metadata work (TASK-327 follow-up): + - **Tag-write concurrency cap.** `save()` now caps in-flight tag writes at 16 via a small `runWithConcurrency` helper instead of firing every pending write at once. Avoids `EMFILE` on large libraries. + - **Aggregated tag-write errors.** Failure messages now begin with `tag write failed` so the executor's error categorizer classifies them as file-I/O (`copy`) rather than risking a path-keyword mis-classification. + - **WAV/AIFF on mass-storage.** Podkit transcodes WAV and AIFF source files to a managed codec before placing them on a mass-storage device, even when the device firmware can play them. RIFF/IFF tag-writing is unreliable. Presets continue to list these codecs for documentation. iPod is unaffected (libgpod / iTunesDB handle metadata for WAV/AIFF). + - **OGG Vorbis tag round-trip tests.** Now run on builds with libvorbis (skipped automatically when absent). + - **Shared TagFields helpers.** `buildTagFieldsFromInput` and `diffTagFields` replace three duplicate field-by-field walks across adapters. + - **`TransferMode` type unified.** Removed `'fast' | 'optimized' | 'portable'` duplication between `DeviceTrackInput`, `DeviceTrackMetadata`, and the canonical `TransferMode` in `transcode/types.ts`. Drops several inline type casts. + - **Docs.** `transferMode` now has a dedicated section in `docs/reference/config-file.md` explaining the iPod vs mass-storage contract and migration churn. `pathTemplate` (from the prior release) and `PODKIT_PATH_TEMPLATE` are now documented in the config reference and environment-variables reference. + +- [`1c3ebc3`](https://github.com/jvgomg/podkit/commit/1c3ebc381276accdb8361f50454b90c75f2391df) Thanks [@jvgomg](https://github.com/jvgomg)! - Add three-tier transfer mode system controlling how files are prepared for the device. + + **Transfer modes:** + - `fast` (default): optimizes for sync speed — direct-copies compatible files, strips artwork from transcodes + - `optimized`: strips embedded artwork from all file types (including MP3, M4A, ALAC copies) via FFmpeg stream-copy, reducing storage usage without re-encoding + - `portable`: preserves embedded artwork in all files for use outside the iPod ecosystem + + **Configuration:** + - `transferMode` config option (global and per-device) + - `--transfer-mode` CLI flag + - `PODKIT_TRANSFER_MODE` environment variable + + **Selective re-processing:** + - `--force-transfer-mode` flag re-processes only tracks whose transfer mode doesn't match the current setting + - `PODKIT_FORCE_TRANSFER_MODE` environment variable + - Works on all file types including direct copies (unlike `--force-transcode` which only affects transcoded tracks) + + **Device inspection:** + - `podkit device music` and `podkit device video` stats show transfer mode distribution + - Missing transfer field flagged alongside missing artwork hash in sync tag summary + - New `syncTagTransfer` field available in `--tracks --fields` for querying transfer mode data + - Dry-run output shows configured transfer mode + + **Under the hood:** + - Granular operation types: `add-direct-copy`, `add-optimized-copy`, `add-transcode` (and upgrade equivalents) + - Sync tags written to all tracks including direct copies (`quality=copy`) + - `DeviceCapabilities` abstraction for device-aware sync decisions + - Sync tag field `transfer=` tracks which mode was used per track + +- [`e825ee1`](https://github.com/jvgomg/podkit/commit/e825ee1dd4933ecbfd070dda27f96f43056f0baf) Thanks [@jvgomg](https://github.com/jvgomg)! - Replace koffi-based libusb FFI with the `usb` npm package for USB firmware inquiry, eliminating the runtime libusb system dependency. + + The `@podkit/ipod-firmware` USB transport now uses the `usb` npm package, whose prebuilt N-API bindings statically link libusb. End-user binaries embed that prebuild via Bun `--compile`; no system `libusb-1.0` is required at runtime. + + Public-surface changes in `@podkit/ipod-firmware`: + - **Removed:** `loadLibusb`, `LibusbBinding`, `LibusbPtr`, `LibusbLoadResult`, `_resetLibusbCacheForTests`. The koffi-shaped binding interface is gone. + - **Added:** `loadUsb`, `UsbBinding`, `UsbDeviceHandle`, `UsbLoadResult`, `_resetUsbCacheForTests`. Higher-level `withOpenDevice(bus, devnum, fn)` seam — implementations handle enumeration, open, and cleanup internally. + - **Added:** `setLogger(fn | null)`, `FirmwareLogger`, `FirmwareLogEvent`. Library no longer writes to stderr/stdout; consumers install a receiver and decide format/destination. The CLI installs one when `-v` is passed. + - **Added:** `@podkit/ipod-firmware/bundle` subpath export with `bundleUsbNative(nativeModule)` for single-file binary builds. See `agents/ipod-firmware.md` for the staging recipe. + - **Renamed:** `UsbInquiryError.libusbCode` → `UsbInquiryError.libusbStatus`. The new field carries `LIBUSB_TRANSFER_*` status codes (positive enum) from the `usb` npm package, not the negative `LIBUSB_ERROR_*` codes the koffi path returned. + + Doctor's `inquiry-methods` check no longer reports libusb availability — the USB transport is bundled and always present in shipped binaries. The check now reports SCSI transport availability only, which remains user-actionable on Linux (udev permissions) and macOS (iPodDriver.kext). + +### Patch Changes + +- [`348f2c5`](https://github.com/jvgomg/podkit/commit/348f2c53cec06598903b5cf128663d5121c46865) Thanks [@jvgomg](https://github.com/jvgomg)! - Redesign `podkit device add` to be slick and informative. Previously, plugging in a post-2006 iPod (nano 2G, nano 7G, iPod 5G) and running `device add` displayed the device as `Model: Invalid` (libgpod's wording for an empty SysInfo file) and instructed the user to manually write a SysInfo file with `ModelNumStr: MA147` — neither friendly nor accurate. + + The new flow: + 1. **Identity is cascade-resolved** from USB product ID, classic SysInfo, SysInfoExtended, and serial — whichever sources are available. Display reads `Found iPod nano (2nd Generation):` rather than `Model: Invalid`. + 2. **A single combined prompt** asks `Add this iPod as "X" and write SysInfoExtended? [Y/n]` when SysInfoExtended is missing and USB is reachable. Confirming triggers firmware inquiry, writes SysInfoExtended, and persists to config in one step. + 3. **Capabilities are derived from the cascade-resolved generation**, not from libgpod's pessimistic fallback. Negative capabilities cite the reason (`- Video (not supported on iPod nano 4GB Green (2nd Generation))`). + 4. **The follow-up tip** suggests `podkit sync -d --dry-run`, not "go run two more commands". + + New flag `--no-firmware-inquiry` skips the firmware fetch+write when used with `--yes` — for the case where the user wants to defer the write or doesn't have the device connected over USB. + + Internal API changes in `@podkit/core`: + - **Added** `assessIpodIdentity(mountPoint, opts?)` returning `IpodIdentityAssessment` — pure cascade-driven assessment (no writes). Combines all available identification sources and returns `{ model, capabilities, firmwareInquiry: 'present' | 'missing' | 'unwritable', needsChecksum }`. The CLI now composes from this primitive instead of reaching into libgpod for identity. + + The misleading `device-validation.ts` warning text (`Ensure /Volumes/X/iPod_Control/Device/SysInfo exists with your model number (e.g., "ModelNumStr: MA147")`) has been replaced with a pointer to the canonical fix: `podkit doctor --repair sysinfo-extended`. + +- [`6747667`](https://github.com/jvgomg/podkit/commit/6747667049cd793fdb13e3d1bc1092651f8e969c) Thanks [@jvgomg](https://github.com/jvgomg)! - Improve device command output: USB model in scan, SysInfo mismatch detection, summary/issues layout + - `podkit device scan` now shows the USB-detected iPod model (e.g., "iPod Classic 6th generation (USB)") and always runs USB discovery in parallel with disk scanning + - `podkit device scan` and `podkit doctor` detect generation mismatches between SysInfo and USB data, warning when the SysInfo file may have been copied from a different device + - `podkit device info`, `podkit device scan`, and `podkit doctor` now separate compact check summaries from detailed issue explanations — warnings and fix commands appear in a dedicated "Issues" section instead of inline + - New `lookupGenerationByModelNumber()` function in `@podkit/core` for resolving iPod generation from SysInfo model numbers + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`3db3d88`](https://github.com/jvgomg/podkit/commit/3db3d887ae2cd19d01ba2c1f00b8682e783fac84) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix multiple bugs discovered during end-to-end Echo Mini hardware validation + + **Sync pipeline:** + - Create temp directory for optimized-copy operations (not just transcodes), fixing "No such file or directory" FFmpeg failures on mass-storage devices + - Capture last 1000 chars of FFmpeg stderr (instead of first 500) so actual errors aren't swallowed by the version banner + + **Device preset content paths:** + - Pass device preset content paths to adapter even when no user overrides exist, fixing Echo Mini's `musicDir: ''` being ignored and files landing in `Music/` instead of device root + + **Artwork:** + - Read embedded artwork during mass-storage device scan (`skipCovers: false`) so artwork presence is correctly detected, preventing false `artwork-added` upgrades on every sync + - Force `yuvj420p` (4:2:0) pixel format in artwork scale filter — JPEG with 4:4:4 chroma subsampling does not display on the Echo Mini + + **Sync tag and preset detection:** + - Treat `quality=copy` sync tags as in-sync when the classifier would also route the source as a copy, preventing false preset-upgrade detection on FLAC-capable mass-storage devices + - Route lossless sources to transcode (not copy) when quality preset is non-lossless, even if the device natively supports the source codec (e.g., FLAC device with quality=high should produce AAC) + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`7ebb7c5`](https://github.com/jvgomg/podkit/commit/7ebb7c5c0e1c7c3d549196347029d9ce660fcb8b) Thanks [@jvgomg](https://github.com/jvgomg)! - Use configurable device label in eject messages instead of hardcoded 'iPod' + +- [`34e8bf2`](https://github.com/jvgomg/podkit/commit/34e8bf2341111df1e8f85361b8047eed9f31665a) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix incorrect device model data: correct checksum types (nano 7G: hashAB, touch 1G-3G: hash72), fix USB product IDs (0x1205: iPod mini, 0x1209: iPod Video, 0x120a: nano 1G), reclassify model B867 from nano 4G to shuffle 3G, add 15 nano 7G model number variants, add missing touch 4G serial suffixes, and add first crowd-sourced nano 7G serial suffix mapping + +- [`3e95baf`](https://github.com/jvgomg/podkit/commit/3e95baffc65b683b5e3f80906e9a342245a6e4ce) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix iPod model identification regressing to "Unknown iPod" after `doctor --repair sysinfo-extended` on pre-2006 devices (mini 2G), and tighten the package-boundary contract so consumers compose identity instead of injecting resolution policy. + + The bug: each consumer of `ensureSysInfoExtended` / `readSysInfoExtended` passed a serial-only `resolveModel` callback. When the 3-character serial suffix wasn't in `tables/serials.ts`, the resolver returned undefined and the device was displayed as "Unknown iPod" — even when a SysInfo file with a known `ModelNumStr` was sitting next to the SysInfoExtended on disk. + + The fix: + - **Removed** `ModelResolver` type and the `resolveModel` callback from `@podkit/ipod-firmware`. `readSysInfoExtended` and `ensureSysInfoExtended` now return a flat `SysInfoIdentity` bag (`firewireGuid?, serialNumber?, modelNumStr?, familyId?`). When a SysInfo file is on disk alongside SysInfoExtended, its `ModelNumStr` is read opportunistically. + - **Callers compose** with `resolveIpodModel(bag)` from `@podkit/devices-ipod`, which cascades modelNumStr → serial → productId → familyId → libgpodGeneration. The CLI no longer makes resolution decisions. + - **Added** `SYSINFO_PATH`, `SYSINFO_EXTENDED_PATH`, `SYSINFO_DEVICE_DIR` exported from `@podkit/ipod-firmware` and re-exported from `@podkit/core`. Consumers use these constants instead of duplicating the literal `iPod_Control/Device/...` paths. + - **Added** `S4G: '9804'` entry to `tables/serials.ts` (mini 2G 4GB Pink, sourced from real hardware, serial `JQ5141TFS4G`). + - **Post-write enrichment.** After `ensureSysInfoExtended` writes the file via USB inquiry, it now re-reads via `readSysInfoExtended` so the post-write identity bag includes `modelNumStr` from the SysInfo neighbour. Eliminates the cosmetic regression where the repair-success message showed a less-specific name than the subsequent `doctor` run. + +- [`80fe65a`](https://github.com/jvgomg/podkit/commit/80fe65a022c65da512f571a8abf83f9385a649e6) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix SysInfoExtended SCSI-fallback on macOS for SCSI-only iPods (mini 2G, nano 2G, iPod 5G/5.5G). `device add` and `doctor --repair sysinfo-extended` now correctly fall back from USB → SCSI when the device does not respond to vendor control transfers, instead of failing with a misleading "Could not read device identity from USB" error. + + Internal API changes in `@podkit/ipod-firmware`: + - **Changed:** `ensureSysInfoExtended(mountPoint, fp, options?)` now takes a full `UsbFingerprint` instead of the previous `{ busNumber, deviceAddress }` shape. Required so the macOS SCSI transport can locate the IOService via vendorId/productId/serialNumber. `UsbDeviceAddress` is removed. + - **Added:** `inquireFirmwareDetailed(fp, opts?)` — like `inquireFirmware` but returns `{ firmware, plan, attempts }` so callers can distinguish which transports were attempted. `inquireFirmware` is unchanged for existing consumers. + - **Added:** `EnsureSysInfoExtendedOptions` type with `readFromUsb`, `resolveModel`, `inquireOptions` fields. Replaces the previous positional `(mountPoint, fp, readFromUsb, resolveModel)` signature. + + Internal API changes in `@podkit/core`: + - **Added:** `hasCompleteUsbFingerprint(fp): fp is CompleteUsbDevice` type guard exported from `@podkit/core`. + - **Added:** `CompleteUsbDevice` type — a `UsbFingerprint` with vendorId, productId, bus, devnum guaranteed present (serialNumber optional). + - **Changed:** `resolveUsbDeviceFromPath(path)` now also returns `vendorId` and `productId`. Linux extracts from sysfs `idVendor`/`idProduct`; macOS extracts from `system_profiler` JSON. + + User-facing error messages now differentiate between transport failures: "Could not read device identity from USB and SCSI" / "...from USB" / "...from SCSI" / "...no firmware inquiry transport is available on this system" / "...returned data but it could not be parsed". + +- [`52894c1`](https://github.com/jvgomg/podkit/commit/52894c1977bccd51a86929debfbaa7028a19dd61) Thanks [@jvgomg](https://github.com/jvgomg)! - `@podkit/test-fixtures`: expose synthetic-track generators as a library + + Adds a library entry (`src/lib.ts`) exposing `generateMiniFlac`, `generateMiniMp3`, `generateMiniM4a`, `generateMiniOggVorbis`, and `generateMiniOggOpus`. Each helper writes a single short sine-tone file in the requested codec/container with optional metadata. Integration tests that need real audio for tag round-trip coverage now import these from `@podkit/test-fixtures` rather than re-implementing the ffmpeg invocation inline. + + Each generator calls a new `requireEncoder()` guard before invoking ffmpeg. If the host's ffmpeg is missing the codec's encoder, the helper throws a clear error with platform-aware install hints. There is also an explicit `bun run --filter @podkit/test-fixtures check-ffmpeg` script that verifies the full set of required encoders against the host environment in one shot. + + The mass-storage tag writer integration test (`packages/podkit-core/src/device/mass-storage-tag-writer.integration.test.ts`) drops its inline `generateOgg`, `generateOpus`, `generateFlac`, `generateM4a`, `generateMp3` helpers and the `HAS_LIBVORBIS` skip predicate. The OGG Vorbis tests now run unconditionally — they fail loudly with an install hint when libvorbis is absent rather than skipping silently. + + Developer docs (`docs/developers/development.md`) updated to point macOS contributors at the `homebrew-ffmpeg/ffmpeg` tap for full encoder coverage. + +- [`f61a83b`](https://github.com/jvgomg/podkit/commit/f61a83b3a2d13612730f174759fd3b86edd42e82) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix `podkit device scan` reporting phantom "Unknown iPod (USB only)" entries for non-iPod USB peripherals (mice, hubs, Thunderbolt docks, Ethernet adapters, USB drives). Each phantom suggested `podkit device init` — a destructive operation that could mutate an unrelated device. + + Root cause was architectural: a single `discoverUsbIpods()` function mixed three concerns — USB enumeration, iPod-domain enrichment, and the function name's implied filter (which it didn't actually do). Refactored into clean layers: + - **`@podkit/core` — pure USB enumeration.** `enumerateUsb()` returns `EnumeratedUsbDevice[]` with vendor/product/serial/bus/devnum/diskIdentifier ONLY. No iPod-domain knowledge. + - **`@podkit/devices-ipod` — iPod classifier.** `classifyAsIpod(dev)` returns `IpodClassification | null` (matches Apple-vendor with iPod or iOS PIDs). + - **`@podkit/devices-mass-storage` — mass-storage classifier.** `classifyAsMassStorage(dev)` returns `MassStorageClassification | null` (matches `USB_PRESET_HINTS` entries like Echo Mini). + - **`@podkit/core` composer.** `classifyUsbDevices()` runs both classifiers and returns recognized devices as a tagged union; drops unknown peripherals. + - **CLI `device scan`** now calls `enumerateUsb()` → `classifyUsbDevices()` → renders by `kind`. No domain logic in the command layer. + + Added `kind: 'mass-storage'` rendering branch so mass-storage DAPs are no longer mis-labeled as "Unknown iPod". + + Removed `discoverUsbIpods` and the leaky `UsbDiscoveredDevice` type (which previously carried iPod-domain fields). Adding a new mass-storage device now means adding one entry to `USB_PRESET_HINTS` — no `@podkit/core` change required. Adding a new iPod generation means updating `@podkit/devices-ipod` tables — no other package changes. + + Also split `usb-discovery.ts` into `usb-enumeration.ts` (bus walk) + `usb-path-resolution.ts` (mount-path → fingerprint resolver) since those two concerns were unrelated. + +- [`6000868`](https://github.com/jvgomg/podkit/commit/6000868830d9437a6fff3c1a77adb254d9579fe7) Thanks [@jvgomg](https://github.com/jvgomg)! - Fix `doctor --repair sysinfo-extended` showing unhelpful "Could not read device identity from USB" with no detail. The native USB binding now throws descriptive errors (e.g. "USB control transfer failed (bus 3, device 4)") instead of returning null silently. Also fix all doctor repair intro messages — they incorrectly said "Repairing X for N tracks" even for non-track operations like SysInfoExtended and orphan cleanup. Intro messages now use each repair's own description. + +- Updated dependencies [[`bb2e637`](https://github.com/jvgomg/podkit/commit/bb2e6374151605d11baf052c452f10a842e5353e), [`0d4a4c2`](https://github.com/jvgomg/podkit/commit/0d4a4c2bd98667989b9631d981e609bc72e604af), [`d1147e4`](https://github.com/jvgomg/podkit/commit/d1147e4a65ac103608da3730f530f6deab3cd0b6), [`01ecedd`](https://github.com/jvgomg/podkit/commit/01ecedde623ff99e94c5cbda75ff9f9c9ecef632), [`3e95baf`](https://github.com/jvgomg/podkit/commit/3e95baffc65b683b5e3f80906e9a342245a6e4ce), [`bddea04`](https://github.com/jvgomg/podkit/commit/bddea044342ca9027fc95593a35795fd8de1faf4), [`22dddf4`](https://github.com/jvgomg/podkit/commit/22dddf4803f4cfd7b004d80dffd83878a68b10f2), [`1ec30ac`](https://github.com/jvgomg/podkit/commit/1ec30acca1109178012db3913a60967a2087fb5b), [`80fe65a`](https://github.com/jvgomg/podkit/commit/80fe65a022c65da512f571a8abf83f9385a649e6), [`c5cba69`](https://github.com/jvgomg/podkit/commit/c5cba6998283663b42659f02b17b194ab256c137), [`f61a83b`](https://github.com/jvgomg/podkit/commit/f61a83b3a2d13612730f174759fd3b86edd42e82), [`6000868`](https://github.com/jvgomg/podkit/commit/6000868830d9437a6fff3c1a77adb254d9579fe7), [`4598f8f`](https://github.com/jvgomg/podkit/commit/4598f8f3347cf40b94fdf1585215e5b0f54d9cf6), [`e825ee1`](https://github.com/jvgomg/podkit/commit/e825ee1dd4933ecbfd070dda27f96f43056f0baf)]: + - @podkit/ipod-firmware@0.1.0 + - @podkit/device-types@0.1.0 + - @podkit/devices-mass-storage@0.1.0 + - @podkit/devices-ipod@0.1.0 + - @podkit/libgpod-node@0.2.0 + ## 0.6.0 ### Minor Changes diff --git a/packages/podkit-core/package.json b/packages/podkit-core/package.json index f0a8c902..2f8a4a1d 100644 --- a/packages/podkit-core/package.json +++ b/packages/podkit-core/package.json @@ -1,6 +1,6 @@ { "name": "@podkit/core", - "version": "0.6.0", + "version": "0.7.0", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/podkit-daemon/CHANGELOG.md b/packages/podkit-daemon/CHANGELOG.md index 40f26030..de95e7fc 100644 --- a/packages/podkit-daemon/CHANGELOG.md +++ b/packages/podkit-daemon/CHANGELOG.md @@ -1,5 +1,73 @@ # @podkit/daemon +## 0.3.0 + +### Minor Changes + +- [#58](https://github.com/jvgomg/podkit/pull/58) [`efa14c6`](https://github.com/jvgomg/podkit/commit/efa14c623e7bda81066bd77142cddb28e4de615d) Thanks [@jvgomg](https://github.com/jvgomg)! - Add mass-storage device support for non-iPod portable music players. + + **Supported device types:** Echo Mini, Rockbox, and generic mass-storage DAPs. iPod support is unchanged. + + **New in CLI (`podkit`):** + - `podkit device add --type ` registers mass-storage devices by type and mount path + - `podkit device info/music/video` work with mass-storage devices via `DeviceAdapter` interface + - `podkit device scan` shows configured path-based devices alongside auto-detected iPods + - `podkit sync` routes to the correct adapter (iPod or mass-storage) based on device config + - Video sync now uses capabilities-based gating instead of iPod-only checks + - Safety gates on `device init/reset/clear` (iPod-only commands) for mass-storage devices + - Mount and eject commands show device-appropriate messaging + - Config validation rejects capability overrides on iPod devices (capabilities are auto-detected from generation) + - Shared `openDevice()` function eliminates duplicated device-opening logic across commands + + **New in core (`@podkit/core`):** + - `DeviceAdapter` interface — generic abstraction over device databases (iPod, mass-storage) + - `MassStorageAdapter` — filesystem-based track management with `.podkit/state.json` manifest + - `IpodDeviceAdapter` — thin wrapper making `IpodDatabase` implement `DeviceAdapter` + - Device capability presets for Echo Mini, Rockbox, and generic devices + - `resolveDeviceCapabilities()` merges preset defaults with user config overrides + - `DeviceTrack` type used throughout sync engine (replaces `IPodTrack` casts in execution paths) + - Configurable content path prefixes (`musicDir`, `moviesDir`, `tvShowsDir`) with device-type defaults + - Device presets include default content paths (Echo Mini: root for music; generic/Rockbox: `Music/`, `Video/Movies/`, `Video/Shows/`) + - Manifest v2 stores active content paths; files automatically moved when prefixes change + - Root path support (`/`, `.`, or empty string all normalize to device root) + - Content path duplicate validation (no two content types can share the same prefix) + - Video scanning support for mass-storage devices (.m4v, .mp4, .mov, .avi, .mkv) + + **New in daemon (`@podkit/daemon`):** + - Mass-storage device polling via `PODKIT_MASS_STORAGE_PATHS` env var (colon/comma separated) + - Second `DevicePoller` + `SyncOrchestrator` pair for mass-storage devices + - No-op mount/eject runners (mass-storage devices are externally managed) + - Graceful shutdown handles both iPod and mass-storage sync pipelines + + **Configuration:** + + ```toml + [devices.echo] + type = "echo-mini" + path = "/Volumes/ECHO" + + # Optional capability overrides (mass-storage only) + artworkMaxResolution = 800 + supportedAudioCodecs = ["aac", "mp3", "flac"] + + # Optional content path overrides (mass-storage only) + musicDir = "/" # Place music at device root + moviesDir = "Films" # Custom movies directory + tvShowsDir = "TV Shows" # Custom TV shows directory + ``` + + **Environment variables for content paths:** + - `PODKIT_MUSIC_DIR` — global default music directory + - `PODKIT_MOVIES_DIR` — global default movies directory + - `PODKIT_TV_SHOWS_DIR` — global default TV shows directory + +- [`455e115`](https://github.com/jvgomg/podkit/commit/455e115d5f724411f970ed49dda2cca57c7aff2f) Thanks [@jvgomg](https://github.com/jvgomg)! - Support multiple iPods plugged in simultaneously. Each device gets a unique mount point and devices appearing during a sync are queued and synced sequentially after the current sync completes. + +### Patch Changes + +- Updated dependencies [[`0f3e4dd`](https://github.com/jvgomg/podkit/commit/0f3e4ddae134228b5e874b21db33f74547867b6c), [`036b107`](https://github.com/jvgomg/podkit/commit/036b1077748253385b6f4ff873a7cdb52c54b004), [`89ff40c`](https://github.com/jvgomg/podkit/commit/89ff40c2adedd9fec38ae5ad0eb89b75525642f2), [`c5c0236`](https://github.com/jvgomg/podkit/commit/c5c0236c232cc3fa086fd3937b0e2fbe0f326185), [`0d4a4c2`](https://github.com/jvgomg/podkit/commit/0d4a4c2bd98667989b9631d981e609bc72e604af), [`513173d`](https://github.com/jvgomg/podkit/commit/513173d1832bf9ca2894214e97d9d65cf02c52a5), [`0cc39d3`](https://github.com/jvgomg/podkit/commit/0cc39d3c62343591127d5c79deed2478f8dc4f60), [`22dddf4`](https://github.com/jvgomg/podkit/commit/22dddf4803f4cfd7b004d80dffd83878a68b10f2), [`348f2c5`](https://github.com/jvgomg/podkit/commit/348f2c53cec06598903b5cf128663d5121c46865), [`7534c2f`](https://github.com/jvgomg/podkit/commit/7534c2f19d81087413af8abbf764fe20cef61384), [`d1147e4`](https://github.com/jvgomg/podkit/commit/d1147e4a65ac103608da3730f530f6deab3cd0b6), [`6747667`](https://github.com/jvgomg/podkit/commit/6747667049cd793fdb13e3d1bc1092651f8e969c), [`8bc3126`](https://github.com/jvgomg/podkit/commit/8bc3126ec415aa836b746ec921b6738abdd9e538), [`01ecedd`](https://github.com/jvgomg/podkit/commit/01ecedde623ff99e94c5cbda75ff9f9c9ecef632), [`03f1046`](https://github.com/jvgomg/podkit/commit/03f1046b70898b0282d0c96927bca60ee0d55eeb), [`14d83e5`](https://github.com/jvgomg/podkit/commit/14d83e5e59eb0a8a801850de775f9fdb4c0e7aa9), [`3db3d88`](https://github.com/jvgomg/podkit/commit/3db3d887ae2cd19d01ba2c1f00b8682e783fac84), [`7ebb7c5`](https://github.com/jvgomg/podkit/commit/7ebb7c5c0e1c7c3d549196347029d9ce660fcb8b), [`34e8bf2`](https://github.com/jvgomg/podkit/commit/34e8bf2341111df1e8f85361b8047eed9f31665a), [`3e95baf`](https://github.com/jvgomg/podkit/commit/3e95baffc65b683b5e3f80906e9a342245a6e4ce), [`bddea04`](https://github.com/jvgomg/podkit/commit/bddea044342ca9027fc95593a35795fd8de1faf4), [`09c4acd`](https://github.com/jvgomg/podkit/commit/09c4acdec349f200a649b2db15fe05345e380a7b), [`94c85d2`](https://github.com/jvgomg/podkit/commit/94c85d2a9d6c85875432a0ebecab540a9ebd67d7), [`efa14c6`](https://github.com/jvgomg/podkit/commit/efa14c623e7bda81066bd77142cddb28e4de615d), [`208e482`](https://github.com/jvgomg/podkit/commit/208e482db9730064a25e53e03121bdcfcbea6341), [`bb96778`](https://github.com/jvgomg/podkit/commit/bb96778dde9063267188b2b83535ec279cd5c550), [`f72fa01`](https://github.com/jvgomg/podkit/commit/f72fa0170872fc0a6e5719b4509abae24e6414cd), [`c9c268e`](https://github.com/jvgomg/podkit/commit/c9c268ea4b25b39543e5c53a1928e72b4c31e0c8), [`1ec30ac`](https://github.com/jvgomg/podkit/commit/1ec30acca1109178012db3913a60967a2087fb5b), [`80fe65a`](https://github.com/jvgomg/podkit/commit/80fe65a022c65da512f571a8abf83f9385a649e6), [`52894c1`](https://github.com/jvgomg/podkit/commit/52894c1977bccd51a86929debfbaa7028a19dd61), [`c5cba69`](https://github.com/jvgomg/podkit/commit/c5cba6998283663b42659f02b17b194ab256c137), [`1c3ebc3`](https://github.com/jvgomg/podkit/commit/1c3ebc381276accdb8361f50454b90c75f2391df), [`f61a83b`](https://github.com/jvgomg/podkit/commit/f61a83b3a2d13612730f174759fd3b86edd42e82), [`6000868`](https://github.com/jvgomg/podkit/commit/6000868830d9437a6fff3c1a77adb254d9579fe7), [`e825ee1`](https://github.com/jvgomg/podkit/commit/e825ee1dd4933ecbfd070dda27f96f43056f0baf)]: + - @podkit/core@0.7.0 + ## 0.2.2 ### Patch Changes diff --git a/packages/podkit-daemon/package.json b/packages/podkit-daemon/package.json index 4ff1d06b..1cae2431 100644 --- a/packages/podkit-daemon/package.json +++ b/packages/podkit-daemon/package.json @@ -1,7 +1,7 @@ { "name": "@podkit/daemon", "private": true, - "version": "0.2.2", + "version": "0.3.0", "description": "Daemon for auto-detecting iPods and syncing via the podkit CLI", "type": "module", "main": "./dist/main.js", diff --git a/packages/podkit-docker/CHANGELOG.md b/packages/podkit-docker/CHANGELOG.md index ab051353..bc3e9692 100644 --- a/packages/podkit-docker/CHANGELOG.md +++ b/packages/podkit-docker/CHANGELOG.md @@ -1,5 +1,13 @@ # @podkit/docker +## 0.2.4 + +### Patch Changes + +- Updated dependencies [[`0f3e4dd`](https://github.com/jvgomg/podkit/commit/0f3e4ddae134228b5e874b21db33f74547867b6c), [`036b107`](https://github.com/jvgomg/podkit/commit/036b1077748253385b6f4ff873a7cdb52c54b004), [`89ff40c`](https://github.com/jvgomg/podkit/commit/89ff40c2adedd9fec38ae5ad0eb89b75525642f2), [`c5c0236`](https://github.com/jvgomg/podkit/commit/c5c0236c232cc3fa086fd3937b0e2fbe0f326185), [`bb2e637`](https://github.com/jvgomg/podkit/commit/bb2e6374151605d11baf052c452f10a842e5353e), [`0ef210b`](https://github.com/jvgomg/podkit/commit/0ef210be6e5fc38203e5501d33cc1bb978ecc0c6), [`ae995ef`](https://github.com/jvgomg/podkit/commit/ae995ef99174f7381f4eeaeb79cee4e77ddc3136), [`8c6dc1a`](https://github.com/jvgomg/podkit/commit/8c6dc1a3d355efe6542a7f00f8fa05da3225bb42), [`0d4a4c2`](https://github.com/jvgomg/podkit/commit/0d4a4c2bd98667989b9631d981e609bc72e604af), [`56c7ec3`](https://github.com/jvgomg/podkit/commit/56c7ec36fb00b6996beffdce76eb17a23211c628), [`513173d`](https://github.com/jvgomg/podkit/commit/513173d1832bf9ca2894214e97d9d65cf02c52a5), [`0cc39d3`](https://github.com/jvgomg/podkit/commit/0cc39d3c62343591127d5c79deed2478f8dc4f60), [`348f2c5`](https://github.com/jvgomg/podkit/commit/348f2c53cec06598903b5cf128663d5121c46865), [`7534c2f`](https://github.com/jvgomg/podkit/commit/7534c2f19d81087413af8abbf764fe20cef61384), [`d1147e4`](https://github.com/jvgomg/podkit/commit/d1147e4a65ac103608da3730f530f6deab3cd0b6), [`8bc3126`](https://github.com/jvgomg/podkit/commit/8bc3126ec415aa836b746ec921b6738abdd9e538), [`8d017e8`](https://github.com/jvgomg/podkit/commit/8d017e8ede48c98b9a1d1c627b882689c33da61e), [`6747667`](https://github.com/jvgomg/podkit/commit/6747667049cd793fdb13e3d1bc1092651f8e969c), [`01ecedd`](https://github.com/jvgomg/podkit/commit/01ecedde623ff99e94c5cbda75ff9f9c9ecef632), [`03f1046`](https://github.com/jvgomg/podkit/commit/03f1046b70898b0282d0c96927bca60ee0d55eeb), [`14d83e5`](https://github.com/jvgomg/podkit/commit/14d83e5e59eb0a8a801850de775f9fdb4c0e7aa9), [`3db3d88`](https://github.com/jvgomg/podkit/commit/3db3d887ae2cd19d01ba2c1f00b8682e783fac84), [`7ebb7c5`](https://github.com/jvgomg/podkit/commit/7ebb7c5c0e1c7c3d549196347029d9ce660fcb8b), [`1caab19`](https://github.com/jvgomg/podkit/commit/1caab1991d43739aaba3d9ae2e4a5dd6575f331a), [`3fe7853`](https://github.com/jvgomg/podkit/commit/3fe785330f8b92c21159ae253456942a92e7c8e2), [`26733cc`](https://github.com/jvgomg/podkit/commit/26733cc77fd56681387b29e4241ad05e4d1fd348), [`e58ae80`](https://github.com/jvgomg/podkit/commit/e58ae806a494e3f526a828d4b72dab558ae4b121), [`3e95baf`](https://github.com/jvgomg/podkit/commit/3e95baffc65b683b5e3f80906e9a342245a6e4ce), [`bddea04`](https://github.com/jvgomg/podkit/commit/bddea044342ca9027fc95593a35795fd8de1faf4), [`09c4acd`](https://github.com/jvgomg/podkit/commit/09c4acdec349f200a649b2db15fe05345e380a7b), [`94c85d2`](https://github.com/jvgomg/podkit/commit/94c85d2a9d6c85875432a0ebecab540a9ebd67d7), [`efa14c6`](https://github.com/jvgomg/podkit/commit/efa14c623e7bda81066bd77142cddb28e4de615d), [`455e115`](https://github.com/jvgomg/podkit/commit/455e115d5f724411f970ed49dda2cca57c7aff2f), [`34ad4d3`](https://github.com/jvgomg/podkit/commit/34ad4d39104600f1363bd434518b10b3399a652c), [`f72fa01`](https://github.com/jvgomg/podkit/commit/f72fa0170872fc0a6e5719b4509abae24e6414cd), [`1ec30ac`](https://github.com/jvgomg/podkit/commit/1ec30acca1109178012db3913a60967a2087fb5b), [`80fe65a`](https://github.com/jvgomg/podkit/commit/80fe65a022c65da512f571a8abf83f9385a649e6), [`c5cba69`](https://github.com/jvgomg/podkit/commit/c5cba6998283663b42659f02b17b194ab256c137), [`1c3ebc3`](https://github.com/jvgomg/podkit/commit/1c3ebc381276accdb8361f50454b90c75f2391df), [`17eac11`](https://github.com/jvgomg/podkit/commit/17eac114719f93cef40beb58381e534a28ebc35f), [`f61a83b`](https://github.com/jvgomg/podkit/commit/f61a83b3a2d13612730f174759fd3b86edd42e82), [`6000868`](https://github.com/jvgomg/podkit/commit/6000868830d9437a6fff3c1a77adb254d9579fe7), [`4598f8f`](https://github.com/jvgomg/podkit/commit/4598f8f3347cf40b94fdf1585215e5b0f54d9cf6), [`e825ee1`](https://github.com/jvgomg/podkit/commit/e825ee1dd4933ecbfd070dda27f96f43056f0baf)]: + - podkit@0.7.0 + - @podkit/daemon@0.3.0 + ## 0.2.3 ### Patch Changes diff --git a/packages/podkit-docker/package.json b/packages/podkit-docker/package.json index e56f7466..8748f877 100644 --- a/packages/podkit-docker/package.json +++ b/packages/podkit-docker/package.json @@ -1,7 +1,7 @@ { "name": "@podkit/docker", "private": true, - "version": "0.2.3", + "version": "0.2.4", "description": "Docker image for podkit — syncs music collections to iPod devices", "dependencies": { "@podkit/daemon": "workspace:*", diff --git a/packages/test-fixtures/CHANGELOG.md b/packages/test-fixtures/CHANGELOG.md new file mode 100644 index 00000000..3933720e --- /dev/null +++ b/packages/test-fixtures/CHANGELOG.md @@ -0,0 +1,15 @@ +# @podkit/test-fixtures + +## 0.1.0 + +### Minor Changes + +- [`52894c1`](https://github.com/jvgomg/podkit/commit/52894c1977bccd51a86929debfbaa7028a19dd61) Thanks [@jvgomg](https://github.com/jvgomg)! - `@podkit/test-fixtures`: expose synthetic-track generators as a library + + Adds a library entry (`src/lib.ts`) exposing `generateMiniFlac`, `generateMiniMp3`, `generateMiniM4a`, `generateMiniOggVorbis`, and `generateMiniOggOpus`. Each helper writes a single short sine-tone file in the requested codec/container with optional metadata. Integration tests that need real audio for tag round-trip coverage now import these from `@podkit/test-fixtures` rather than re-implementing the ffmpeg invocation inline. + + Each generator calls a new `requireEncoder()` guard before invoking ffmpeg. If the host's ffmpeg is missing the codec's encoder, the helper throws a clear error with platform-aware install hints. There is also an explicit `bun run --filter @podkit/test-fixtures check-ffmpeg` script that verifies the full set of required encoders against the host environment in one shot. + + The mass-storage tag writer integration test (`packages/podkit-core/src/device/mass-storage-tag-writer.integration.test.ts`) drops its inline `generateOgg`, `generateOpus`, `generateFlac`, `generateM4a`, `generateMp3` helpers and the `HAS_LIBVORBIS` skip predicate. The OGG Vorbis tests now run unconditionally — they fail loudly with an install hint when libvorbis is absent rather than skipping silently. + + Developer docs (`docs/developers/development.md`) updated to point macOS contributors at the `homebrew-ffmpeg/ffmpeg` tap for full encoder coverage. diff --git a/packages/test-fixtures/package.json b/packages/test-fixtures/package.json index 17820251..405aac1a 100644 --- a/packages/test-fixtures/package.json +++ b/packages/test-fixtures/package.json @@ -1,6 +1,6 @@ { "name": "@podkit/test-fixtures", - "version": "0.0.0", + "version": "0.1.0", "private": true, "type": "module", "main": "./dist/lib.js",