CI / build (darwin, macos-14) (push) Has been cancelled
CI / build (windows, windows-2025) (push) Has been cancelled
Deploy to GitHub Pages / deploy (push) Has been cancelled
* feat: the ability to generate bash autocomplete has been enabled
* docs(cli): document shell completion support
* docs(site): add shell completion feature
Fetches first 20 tracks from YT music when list=URL is parsed and
plays,remaining tracks are fetched in the background and added in
batches of 20.
Added the --expand-playlist/--no-expand-playlist CLI flags and the
expand_playlist key in the configs. This is switched on by default
Exposes the active theme name + resolved hex colors on the existing
status response, and adds a new bands command plus a cliamp visstream
subcommand that holds a single IPC connection open and emits one
NDJSON frame per tick (default 30 fps).
The server's per-connection read deadline is now refreshed per
request so long-lived stream clients are not killed at a fixed
wall clock.
Release / build (amd64, darwin, macos-latest) (push) Has been cancelled
Release / build (amd64, linux, ubuntu-latest) (push) Has been cancelled
Release / build (amd64, windows, windows-latest) (push) Has been cancelled
Release / build (arm64, darwin, macos-latest) (push) Has been cancelled
Release / build (arm64, linux, ubuntu-latest) (push) Has been cancelled
Release / release (push) Has been cancelled
Release / update-homebrew (push) Has been cancelled
Runs cliamp without a TUI: serves the existing IPC over its Unix socket,
auto-advances tracks on Drained(), exits cleanly on SIGINT/SIGTERM. Unlocks
Waybar/Polybar modules, hotkey scripts, systemd units, and cron timers.
UI-only commands (theme, vis) return an error in this mode.
playTrack releases the daemon mutex during the blocking Play/PlayYTDL
setup so concurrent IPC requests (e.g. cliamp status) don't stall for the
1-3s of HTTP/yt-dlp probe time. Player internals are already thread-safe.
Docs: docs/headless.md with use cases (systemd, Waybar, Hyprland, cron,
SSH, embedded), linked from README and remote-control.md.
* Add Emby provider
Adds a new provider for Emby Media Server, mirroring the Jellyfin
provider but with Emby-specific API behaviour:
- Authorization header uses the 'Emby' scheme (Jellyfin uses 'MediaBrowser')
- Ping uses GET /System/Info — Emby API keys are server-level and return
500 on /Users/Me, which Jellyfin's Ping calls
- UserID() falls back from /Users/Me to GET /Users for API key auth,
preferring a user whose name matches the configured username
- Full test coverage: client (MusicLibraries, Albums, Tracks, StreamURL,
password auth, NowPlaying, Scrobble, Ping, API key user fallback) and
provider (Name, Playlists, Tracks, CanReportPlayback)
Also adds:
- 'E' keybinding to switch to Emby from anywhere in the UI
- cliamp setup wizard support (token / username+password picker)
- [emby] config section with same fields as [jellyfin]
- docs/emby.md, updates to docs/cli.md, docs/configuration.md,
docs/keybindings.md, config.toml.example, and site/index.html
* Address CodeRabbit review: emby provider fixes
- postJSON: wrap json.Marshal error with path context
- UserID: return explicit error when configured user name not found in /Users
- AlbumList: clamp negative offset to 0
- Playlists: return copy of cache slice to prevent external mutation
- setup.go: wrap Emby ping error with "emby: validation:" prefix
- docs/emby.md: fix Quick start blurb (references /System/Info, not /Users/Me)
- site/index.html: add E key to Provider Browser quick-switch row
* Convert new Emby tests to table-driven style
* Wrap all bare errors in client.go with operation context
* Wrap provider-level browse errors with operation context
* Clarify that 'user' affects API key auth as well as password login
* Add optional username field to Emby API key setup mode
* Fix Emby empty-state hint to cover both auth modes
* Tweak Emby empty-state hint wording
* Fix Emby empty-state hint wording
* Return defensive copies from Playlists/Tracks cache; fix docs em dash
* Add emby to --provider flag valid values
* emby: drop double-prefixed errors and align cache returns with Jellyfin
Provider methods were wrapping client errors with `fmt.Errorf("emby: <op>: %w", err)`,
but client.go already prefixes every error with `emby: <path>:`. End-users saw
messages like `emby: artists: emby: /Items: http status 401`. Drop the redundant
package prefix from provider.go; keep the operation context.
Also drop the per-call defensive copies (`copyTracks`, the playlist slice
clone). The existing Jellyfin provider — which shares this same caching shape —
returns cached slices and maps directly, and no consumer in ui/model/ mutates
the returned tracks. Aliasing through `ProviderMeta` is theoretically possible
but would be a caller bug to fix at the caller, not papered over per-provider.
Aligning Emby with the Jellyfin pattern keeps the two providers behaviorally
identical and removes per-fetch allocations.
---------
Co-authored-by: Sam Hassell <yeehah@protonmail.com>
Co-authored-by: bjarneo <bjarneo@users.noreply.github.com>
Co-authored-by: Bjarne Øverli <bjarne.oeverli@gmail.com>
- New external/soundcloud/ package implementing playlist.Provider +
provider.Searcher via yt-dlp's scsearch: protocol and resolve.
- Empty-user browse view seeds curated genre playlists (Trending,
Hip-Hop, Electronic, House, Lo-Fi, Indie, Pop) since SoundCloud's
official chart endpoints all 404 through yt-dlp at present.
- [soundcloud] config block with user (profile browse) and
cookies_from (browser session for Go+ / private content).
Mirrors ytmusic's cookies_from pattern; closes the OAuth gap left
by SoundCloud shutting their developer program in 2014.
- resolve.SetYTDLCookiesFrom propagates --cookies-from-browser to
every flat-playlist call; player path picks it up too via
player.SetYTDLCookiesFrom.
- Ctrl+F now opens search directly when a provider implements
Searcher; empty-state hint surfaces this. Removed the stale
Ctrl+R-to-refresh-only message.
- Player surfaces yt-dlp's actual exit error (HTTP 404, region
block, DRM) instead of bare EOF, plus a status notification
("Couldn't play X — track is gated, restricted, or unavailable.")
when stream playback fails. yt-dlp invocations now use --quiet so
errors aren't drowned in download progress.
- Capital C switches to SoundCloud (mnemonic for "Cloud"; S was
taken by Spotify). Added to keymap, reserved-keys list, and the
Ctrl+K overlay.
- New docs/soundcloud.md mirrors the other per-provider guides;
configuration.md, yt-dlp.md, keybindings.md, README.md, and
site/index.html updated in lockstep.
Sets cfg.Visualizer = "none" so the tick loop falls back to TickSlow
(5 FPS for time/seek display only). Targets battery, slow terminals,
and SSH sessions where the spectrum redraw + FFT pipeline is the
dominant CPU cost. Press v in the player to cycle the visualizer
back on at any time.
Introduces 'cliamp setup', a Bubbletea+Lipgloss TUI that walks users
through configuring Navidrome, Plex, Jellyfin, Spotify, and YouTube
Music. Each provider step links to where to find credentials,
validates the connection live (Plex/Jellyfin/Navidrome Ping), and
writes the resulting [section] block to ~/.config/cliamp/config.toml
without disturbing the rest of the file.
Adds Navidrome's Ping() via subsonic ping.view to match Plex/Jellyfin
patterns. Surfaces the wizard from the README quick-start, the site's
install section, configuration.md, cli.md, and a one-line callout at
the top of each per-provider doc.
Three small cleanups from review:
1. Move CredsPath / DeleteCreds into an untagged creds.go so the
Windows stub no longer needs its own copy. Drops the credsPath
wrapper and the duplicate path-build/remove logic.
2. Change DeleteCreds to (bool, error) so the reset subcommand can
distinguish "removed" from "did not exist" without a separate
os.Stat round-trip — deleteCreds already swallowed ErrNotExist,
so the pre-check was redundant.
3. Trim two over-narration comments. The fallbackMaxAttempts intent
is now a single inline note; the invalid_grant block comment is
reduced to one line about why we delete on this signal.
Exposes the credential cleanup that the auto-detection in
newSessionFromStored already performs internally, as an explicit
recovery action for users who hit a state the detector misses.
Removes ~/.config/cliamp/spotify_credentials.json, prints the path
that was removed, and tells the user to relaunch and sign in.
Updates the Spotify and CLI docs with the new command and refines
the user-facing error messages on stale-auth code paths to point at
'cliamp spotify reset' as a concrete next step.
Release / build (amd64, darwin, macos-latest) (push) Has been cancelled
Release / build (amd64, linux, ubuntu-latest) (push) Has been cancelled
Release / build (amd64, windows, windows-latest) (push) Has been cancelled
Release / build (arm64, darwin, macos-latest) (push) Has been cancelled
Release / build (arm64, linux, ubuntu-latest) (push) Has been cancelled
Release / release (push) Has been cancelled
Release / update-homebrew (push) Has been cancelled
* feat: file-based logging with intent-based applog facade
Closes#176.
Adds a slog-backed file logger at ~/.config/cliamp/cliamp.log with a
configurable level (log_level config key, --log-level CLI flag) and
refactors applog into an intent-based facade with three tiers:
- Debug/Info/Warn/Error: file only
- Status: footer only (transient UI feedback)
- UserWarn/UserError: both file and footer
The footer ring buffer is preserved unchanged; the file sink is layered
behind an atomic.Pointer[*slog.Logger] so log calls stay lock-free.
Migrated all 11 spotify call sites: failures saving credentials and the
auth-callback server error to UserError, reconnect/rate-limit warnings
to UserWarn, 're-authenticated successfully' to Info+Status.
Plugin-side logging and log rotation deferred to follow-ups.
* docs(site): add diagnostic logging feature card
Keeps site/index.html in sync with docs/configuration.md after the
log_level config key was added in abfdc37.
* Address CodeRabbit review
- config: silently fall back to default for invalid log_level in TOML,
matching the loader's behavior for other keys (volume, repeat, etc.)
- spotify: extract duplicate re-auth message literal into a const
- main: return applied level from initLogging so the startup log records
the level that's actually in effect, not the raw config string
* Drop Enabled gate from UserWarn/UserError
The footer needs the formatted string regardless of file-log level, so
the gate was paying for two fmt.Sprintf sites and a branch in exchange
for skipping a sub-nanosecond slog dispatch. Diagnostic-only methods
(Debug/Info/Warn/Error/logf) keep the gate where it actually avoids the
Sprintf cost.
* applog: use t.Cleanup for test logger close
Resolves three errcheck violations from `defer closeFn()` by switching
to `t.Cleanup(func() { _ = closeFn() })`. The explicit underscore
documents the discard intent and t.Cleanup is the idiomatic place to
register test resource teardown.
Layout-unstable * is remapped to Ctrl+B (layout-stable Ctrl+letter).
Playlist-track favorites are renamed to bookmarks throughout: struct
field, playlist/provider APIs, CLI subcommands (playlist bookmark/
bookmarks), TOML key, JSON output. The TOML reader still accepts the
legacy favorite key for backward compatibility with existing playlists.
The unrelated radio-station FavoriteToggler is left untouched.
Runtime control of playback settings that were previously only
accessible via UI keybindings. All commands use the existing IPC
Unix socket protocol.
New commands:
cliamp shuffle [on|off|toggle]
cliamp repeat [off|all|one|cycle]
cliamp mono [on|off|toggle]
cliamp speed <ratio>
cliamp eq <preset> / cliamp eq --band <0-9> <dB>
cliamp device <name|list>
Status response now includes shuffle, repeat, mono, speed, and EQ
preset fields. Site and docs updated with new remote control section.
* feat: CLI playlist management, Unix socket IPC, and SSH streaming
Three features that make cliamp agent-native and scriptable:
1. `cliamp playlist {list,create,add,show,remove,delete}` — manage TOML
playlists from the CLI with --json output and --ssh HOST for remote
directory walking
2. Unix socket IPC at ~/.config/cliamp/cliamp.sock — play/pause/next/prev/
stop/status/volume/seek/load/queue commands over JSON-RPC, following
the MPRIS prog.Send() pattern
3. SSH streaming via ssh:// URL scheme — pipe audio from remote machines
through the existing decoder pipeline, zero filesystem mounts needed
Zero new Go dependencies. All existing tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(ui): SSH track glyph + duration metadata fallback
- Show ↗ prefix on SSH-streamed tracks in the playlist view
- Store/read duration_secs in local TOML playlists
- Fall back to track metadata duration when player reports 0
(piped SSH streams can't determine total length from audio frames)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: album separators, favorites, playlist resume, enrich command
- Album group separators in playlist view (triggers on album field change)
- Favorites system: * key toggles ★, [★ N] count in header, CLI `playlist favorite`
- [67/123] position counter in playlist header
- Playlist resume: saves and restores playlist + track + position across sessions
- `cliamp playlist enrich` probes duration via afinfo and derives album from path
- `ConnectTimeout=5` on all SSH commands to prevent stale hangs
- `SavePlaylist()` exported on local provider
- `*` added to keymap overlay and bottom help bar
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: persist TUI favorites to disk, fix truncation, add cross-playlist favorites view
- TUI * key now persists favorites to TOML via FavoriteSetter interface
- ★ prefix deducted from truncation budget (prevents line overflow)
- JSON output includes duration_secs and favorite fields
- `cliamp playlist favorites` lists all ★ tracks across all playlists
- FavoriteSetter interface in provider/interfaces.go
- Bottom help bar: [*]Fav (no extra ★ symbol)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: accent-color favorites, atomic TOML writes, strict --index parsing
Kotharat design review + silent failure audit fixes:
- ★ renders in accent color (yellow) independently from line style — scannable in long playlists
- savePlaylist uses atomic write (tmp + rename) — no data loss on crash
- --index parsing uses strconv.Atoi — rejects fractional/malformed input
- Bounds check on PlaylistFavorite re-read — no panic on concurrent modification
- Help bar priority 80 → 75 for [*]Fav — yields to navigation hints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(ipc): remote theme switching via cliamp theme <name>
- `cliamp theme list` — list all available themes (built-in + custom)
- `cliamp theme <name>` — change theme in running TUI via IPC
- ThemeMsg with reply channel for error reporting ("theme not found")
- TUI handler reloads themes from disk before applying (picks up new custom themes)
- Added Name field to IPC Request for theme name
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(theme): clean up theme CLI UX from review findings
- `cliamp theme` (no args) prints usage cleanly instead of printing playback state
- `theme list` matching is case-insensitive (prevents routing confusion)
- Documented LoadAll() precedent in ThemeMsg handler (matches openThemePicker pattern)
- Updated CLAUDE.md and agent docs with theme commands
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: Blade Runner visualizers, theme-conditional UI chrome, IPC vis command
Two new visualizer modes inspired by the film's on-screen electronics:
- Esper: phosphor grid scan with CRT afterglow decay (4-level persistence)
- VoightKampff: concentric iris rings with bass-driven bellows indicator
Theme-conditional UI chrome (active when neon-blade-runner theme is set):
- Seek bar: ▰▱ blocks with ◆ thumb (Voight-Kampff analog meter)
- Album separators: ── ◈ ALBUM ◈ ── (LAPD terminal data headers)
- SSH track glyph: ◉ (replicant eye) instead of ↗
- Time format: T+MM:SS (mission elapsed time)
- Position counter: [SUBJ 003/123] (interrogation notation)
- Loading spinner: cycles ENHANCE / SCANNING / PROCESSING
- Title flicker: 2% chance per frame of 2-3 char CRT glitch cluster
- Track transition: 4-frame ░▒▓█ static burst
IPC visualizer command:
- cliamp vis <name> — switch visualizer by name
- cliamp vis next — cycle to next mode (same as v key)
- cliamp vis list — list all modes with * active marker
Also adds neon-blade-runner-amber theme (1982 LAPD terminal amber phosphor).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Refactor PR: urfave/cli v3, consolidate duplications, fix SSH+ffmpeg bug
CLI framework:
- Replace hand-rolled flag parser with urfave/cli v3 command tree
- Move command definitions to commands.go, keep main.go focused on TUI startup
- Rename --volume/--theme flags to --vol/--start-theme to avoid subcommand collisions
- Hide unhelpful (default: 0) on int flags where 0 means auto/config
Code consolidation (cmd/playlist.go):
- Use player.SupportedExts instead of duplicate audioExtensions (adds 4 missing formats)
- Use resolve.CollectAudioFiles instead of duplicate walkDir
- Use playlist.TrackFromFilename instead of local copy (includes sanitizeTag)
- Add bulk AddTracks() to avoid N+1 file open/close per track
- Add provider.Exists() using os.Stat instead of parsing all TOML files
- Extract newProvider() and collectLocalAudio() helpers
Architecture fixes:
- Unify MPRIS/IPC message types via internal/control package with type aliases
- Remove ~30 lines of duplicated handler cases in update.go
- Add SSH+ffmpeg guard in pipeline.go (was silently passing ssh:// to ffmpeg)
- Create internal/sshurl for proper URL parsing with port support
- Replace macOS-only afinfo with cross-platform ffprobe for remote duration probing
- Add consistent StrictHostKeyChecking=yes to all SSH call sites
- Fix StrictHostKeyChecking docs to match code (yes, not accept-new)
IPC fixes:
- Remove TOCTOU os.Stat before DialTimeout in ipc/client.go
- Remove unnecessary 64KB scanner buffer in ipc/server.go
- Add nil-guards on msg.Reply for ThemeMsg and StatusRequestMsg
- Change ipcSend/overridesFromFlags to return errors instead of os.Exit
Removals:
- Remove CLAUDE.md
- Remove Esper and Voight-Kampff visualizers
- Remove neon-blade-runner theme-conditional chrome
- Delete hand-rolled ParseFlags and helpers from config/flags.go
* Remove review file
* Add Local provider pill, L keybinding, default radio startup
- Register Local as a provider pill tab (after Radio)
- Add L keybinding to switch to Local provider for browsing TOML playlists
- Load 3 cliamp radio streams directly on default startup (no async M3U)
- Skip playlist resume on default startup so radio streams aren't replaced
- Only allow position resume (SetResume) when CLI args given, never ResumePlaylist
- Update keybinding docs, keymap overlay, and site
---------
Co-authored-by: Tom di Mino <contact@tomdimino.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>