Commit Graph

25 Commits

Author SHA1 Message Date
Faza Iman Imron f373776d4c feat(plugins): add retained event pub/sub over IPC (#303)
* feat(ipc): add retained event subscriptions

* feat(plugins): expose namespaced event publishing

* feat(app): wire plugin events to IPC

* docs(plugins): document local event pubsub

* fix(ipc): clear request deadline for subscriptions

* fix(luaplugin): install event publisher before plugins load

* fix(luaplugin): clear retained events after publishers stop

* fix(ipc): keep event sequence gap-free when retention is rejected

* fix(luaplugin): break table cycles and cap depth in Lua value conversion

* docs(plugins): document conversion limits and subscription stream rules

* refactor(ipc): report a missing server with a sentinel error

* fix(luaplugin): fold event namespace into one topic segment

* docs(site): mention plugin event publishing and subscriptions

* fix(luaplugin): keep event namespaces unique across plugins

* fix(luaplugin): release event namespace by installed filename

* refactor(ipc): render the not-running message in the command layer

* docs(plugins): clarify namespace prefixes and JSON conversion limits
2026-08-18 17:19:28 +02:00
Bjarne Øverli 05b6986ac0 fix: increase radio playback buffer 2026-08-13 19:32:50 +02:00
Bjarne Øverli 60ca8b0fc6 style: format shell completion option 2026-08-11 19:22:22 +02:00
Sergey Savelev 164f5f8aec feat: the ability to generate bash autocomplete has been enabled (#288)
CI / go (push) Has been cancelled
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
2026-07-29 19:02:49 +02:00
82Sam 53b2240a7b feat: Progressive playlist loading for youtube music (#287)
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
2026-07-27 22:43:31 +02:00
Bjarne Øverli c110b228cd Perf 2026-07-10 21:46:50 +02:00
Bjarne Øverli a5922d072e feat: improve local playlist management 2026-07-06 17:39:27 +02:00
Bjarne Øverli 8dad762349 fix: support go install module path 2026-07-05 13:59:18 +02:00
Sergio Rubio e71b086b80 hook up qobuz cli command 2026-06-13 22:58:28 +02:00
Eric van Riet Paap c9c6faf655 Support low-power mode in config (#250)
* Suspend ALSA speaker on stop/pause to save ~2% CPU

* Reduce renderer wakeups in low-power mode

* Support low-power mode in config
2026-05-27 20:23:42 +02:00
Bjarne Øverli e7dc6d447b ipc: add Theme to status response and stream visualizer bands
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.
2026-05-14 19:13:13 +02:00
Zander 41356e44ab Add NetEase Cloud Music provider (#222)
* netease: add provider

* netease: address review feedback

---------

Co-authored-by: bjarneo <bjarneo@users.noreply.github.com>
2026-05-09 18:14:16 +02:00
Bjarne Øverli 761c921dd7 Add headless daemon mode (--daemon / -d)
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.
2026-05-06 22:45:52 +02:00
Bjarne Øverli 587bf37576 Add Recently Played history backed by a virtual local playlist 2026-05-06 22:29:37 +02:00
tallsam 620d1b20f8 Add Emby provider (#207)
* 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>
2026-05-05 18:09:42 +02:00
Bjarne Øverli d4b596f19f Add SoundCloud as a first-class provider
- 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.
2026-05-05 17:06:40 +02:00
Bjarne Øverli 05786d04a0 Add --low-power flag to disable visualizer
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.
2026-05-04 19:05:35 +02:00
Bjarne Øverli a2613a45a6 Add interactive setup wizard for remote providers
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.
2026-05-01 20:23:11 +02:00
Bjarne Øverli c430f581bf Simplify Spotify credentials helpers
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.
2026-04-27 20:26:04 +02:00
Bjarne Øverli babcdc862b Add 'cliamp spotify reset' subcommand
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.
2026-04-27 20:15:48 +02:00
bjarneo 90ca8b5d31 File-based logging with intent-based applog facade (#192)
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.
2026-04-25 19:40:21 +02:00
Bjarne Øverli c5a9da9b6e feat(plugins): add exec, keymap, and CLI command APIs
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
- cliamp.exec.run(): allowlisted subprocess spawn for plugins,
  gated by permissions = {"exec"}
- p:bind([desc,] fn): plugin-registered TUI keybindings, gated by
  permissions = {"keymap"}; entries with a description appear in
  the Ctrl+K overlay. Core-reserved keys rejected at bind time.
- p:command(name, fn): shell-invokable plugin commands, dispatched
  over IPC via `cliamp plugins call <plugin> <command> [args...]`
  and listed via `cliamp plugins commands`.
- cliamp.fs: added mkdir/listdir; ~/Music/cliamp/ writable.
2026-04-23 22:11:51 +02:00
Bjarne Øverli 13e42cb6e2 Replace * binding with Ctrl+B and rename favorite to bookmark
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.
2026-04-19 12:21:14 +02:00
Bjarne Øverli 9c076fb811 Add CLI remote control for shuffle, repeat, mono, speed, EQ, and audio device
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.
2026-04-02 16:12:57 +02:00
bjarneo 32cb03c687 Pr 158 review (#160)
* 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>
2026-04-02 15:46:17 +02:00