Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e0ca6d1
feat: add centralised path helper
DinoLeung Jul 24, 2026
cb8adba
Merge branch 'fix/upddate-flake' into xdg-config-path
DinoLeung Jul 24, 2026
6260779
chore: cargo fmt path helper
DinoLeung Jul 24, 2026
f8843ec
chore: use xdg config path for client config
DinoLeung Jul 24, 2026
1085069
feat: add cache and state paths to path helper
DinoLeung Jul 25, 2026
46e98b0
chore: refactor to read and wirte from xdg paths
DinoLeung Jul 25, 2026
dcd2113
chore: document XDG-aware config paths
DinoLeung Jul 26, 2026
3f4aa8a
chore: document XDG-aware config paths in plugins
DinoLeung Jul 26, 2026
04b9460
feat: add runtime state persistence model
DinoLeung Jul 27, 2026
9de4fc7
feat: move runtime state out of config
DinoLeung Jul 28, 2026
7b8140d
Merge branch 'LargeModGames:main' into xdg-config-path
DinoLeung Jul 28, 2026
bf31e42
feat: move generated app state into XDG state/cache dirs
DinoLeung Jul 28, 2026
a3d4444
docs: clarify absolute-only XDG path handling
DinoLeung Jul 28, 2026
f454876
fix: preserve runtime volume and layout state
DinoLeung Jul 28, 2026
09a82c7
test: cover runtime-backed plugin config snapshot
DinoLeung Jul 28, 2026
81168de
fix: dedupe shared radio station sanitization
DinoLeung Jul 28, 2026
0d403d9
chore: reuse state radio station sanitizer in dispatch
DinoLeung Jul 30, 2026
26a0d5f
chore: use platform-neutral fallback path joins
DinoLeung Jul 30, 2026
a102b38
fix: create state dir with private permissions before token cache
DinoLeung Jul 30, 2026
bee9afd
fix: create private dirs for token and streaming credential caches
DinoLeung Jul 30, 2026
863be30
fix: merge runtime state saves without clobbering fields
DinoLeung Jul 31, 2026
b20d9c4
fix: migrate legacy config runtime state before rewrite
DinoLeung Jul 31, 2026
c0b8cbe
chore: cover legacy free-source migration
DinoLeung Jul 31, 2026
cccc3d0
fix: harden legacy state path migration
DinoLeung Jul 31, 2026
d3bc833
docs: clarify XDG cache migration behavior
DinoLeung Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Changed

- **Generated app state now uses XDG state/cache directories**: `config.yml` stays in the app config directory for user-authored settings, while runtime-managed state (`state.yml`), listening history, last playback session, and Spotify token caches move to the app state directory. Native streaming credentials and audio cache move to the app cache directory. Existing config-dir runtime fields, radio favorites, listening history, and playback-session files are migrated on first use when the new path is still empty, preserving free-source startup and existing in-app radio favorites during upgrade. Existing Spotify token caches and native streaming caches are not migrated automatically.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Determine whether the streaming cache path goes through the migration helper
rg -n -B6 -A10 'get_default_cache_path' src/infra/player/streaming.rs
rg -n 'migrate_legacy_path_if_unclaimed|state_file_path_with_legacy_config_rename' --type=rust

Repository: LargeModGames/spotatui

Length of output: 2197


🏁 Script executed:

#!/bin/bash
set -u

echo "== Rust migration symbols =="
rg -n 'migrate_legacy_path_if_unclaimed|state_file_path_with_legacy_config_rename|config/streaming_cache|cache/streaming_cache|streaming_cache' --type=rust || true

echo
echo "== File outlines for likely path/migration files =="
fd -e rs 'paths|migrations' src || true

echo
echo "== Relevant source snippets =="
for f in $(fd -e rs 'paths|migrations|streaming' src); do
  echo "--- $f"
  rg -n -B4 -A12 'migrate_legacy_path_if_unclaimed|state_file_path_with_legacy_config_rename|config/streaming_cache|cache/streaming_cache|streaming_cache|get_default_cache_path|app_cache_dir' "$f" || true
done

Repository: LargeModGames/spotatui

Length of output: 397


🏁 Script executed:

#!/bin/bash
set -u

echo "== Candidate Rust files =="
git ls-files '*.rs' | sed -n '1,120p'

echo
echo "== Search all tracked Rust files for symbols =="
python3 - <<'PY'
import subprocess, pathlib, re
files = subprocess.check_output(['git','ls-files','*.rs'], text=True).splitlines()
patterns = [
    'migrate_legacy_path_if_unclaimed',
    'state_file_path_with_legacy_config_rename',
    'config/streaming_cache',
    'cache/streaming_cache',
    'streaming_cache',
    'get_default_cache_path',
    'app_cache_dir',
    'ensure_private_dir',
]
for pat in patterns:
    print(f'\n-- {pat} --')
    for path in files:
        try:
            text = pathlib.Path(path).read_text(errors='replace')
        except Exception:
            continue
        for i, line in enumerate(text.splitlines(), 1):
            if pat in line:
                start=max(1,i-6); end=min(len(text.splitlines()), i+10)
                lines=text.splitlines()
                print(f'[{path}:{i}]')
                for j in range(start,end+1):
                    if j <= len(lines):
                        print(f'  {j}: {lines[j-1]}')
                print()
PY

echo
echo "== CHANGELOG snippet =="
sed -n '1,40p' CHANGELOG.md 2>/dev/null || true

echo
echo "== Migration/test symbols in tests only =="
find . -path '*/tests/*' -name '*.rs' -o -path '*/tests/*' -name '*.toml' | sort | sed -n '1,160p'

Repository: LargeModGames/spotatui

Length of output: 37142


🏁 Script executed:

#!/bin/bash
set -u

echo "== Relevant migration helper implementation =="
sed -n '30,120p' src/core/migrations.rs

echo
echo "== Migration callers across Rust =="
rg -n 'state_file_path_with_legacy_config_rename\(|state_file_path_with_legacy_config_rename\(\s*$|get_default_cache_path\(\)|new_cache_only|resolve_streaming_credentials|ensure_streaming_credentials_cached|Cache::new' --type=rust

echo
echo "== Full migration test case =="
sed -n '540,568p' src/core/migrations.rs

Repository: LargeModGames/spotatui

Length of output: 3151


Correct the native streaming cache migration claim.

This entry says native streaming caches are not migrated automatically, but legacy_path_migration_moves_directory_when_target_is_empty migrates config/streaming_cache to <cache>/streaming_cache when the cache target is empty. Tell users that legacy native streaming credentials and audio cache can migrate; otherwise they may expect a re-login that does not happen.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` at line 7, Update the CHANGELOG entry to state that legacy
native streaming credentials and audio cache may be migrated automatically when
their new cache locations are empty, instead of claiming native streaming caches
are never migrated. Preserve the existing note that Spotify token caches are not
migrated automatically.


## [v0.40.3] 2026-07-27

### Added
Expand Down
11 changes: 7 additions & 4 deletions PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ spotatui plugin remove <name> # uninstall
spotatui plugin new <name> # scaffold a new plugin to start from
```

Plugins are cloned into `~/.config/spotatui/plugins/<name>/` and loaded at startup. Restart
spotatui after installing, and bind any commands the plugin registers under `plugin_commands` in
`config.yml`.
Plugins are cloned into `plugins/<name>/` under the spotatui app config directory
(`$XDG_CONFIG_HOME/spotatui` when `XDG_CONFIG_HOME` is set to an absolute path,
or `~/.config/spotatui` when it is unset or not absolute) and loaded at startup.
Restart spotatui after installing, and bind any commands the plugin registers
under `plugin_commands` in `config.yml`.

Plugins are not sandboxed and run with full app privileges and network access, so only install
ones you trust. See [Trust and safety](docs/scripting.md#trust-and-safety).

You can also drop a single `.lua` file into `~/.config/spotatui/plugins/` by hand.
You can also drop a single `.lua` file into the app config directory's `plugins/`
folder by hand.

## First-party examples

Expand Down
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,13 @@ Prefer setting the password via the `SPOTATUI_SUBSONIC_PASSWORD` environment var

### Internet Radio

Search the radio-browser.info directory in-app (Enter plays a station directly), and press the save key (`F` by default) to keep a station in your sidebar. Saved stations live under `behavior.radio_stations`; the playbar shows a `LIVE` badge with the stream's now-playing title.
Search the radio-browser.info directory in-app (Enter plays a station directly), and press the save key (`F` by default) to keep a station in your sidebar. Stations can also be preconfigured in `config.yml`; stations saved in-app live in `state.yml`. The playbar shows a `LIVE` badge with the stream's now-playing title.

### YouTube

Requires the [`yt-dlp`](https://github.com/yt-dlp/yt-dlp) binary (`ffmpeg` recommended). No Google account, API key, or cookies — search and playback are anonymous. If playback breaks after a YouTube change, updating yt-dlp (`yt-dlp -U`) is the fix; no spotatui update needed.

**Local YouTube playlists** live in `~/.config/spotatui/youtube_playlists.yml`, a plain human-editable file you can back up or share. Create one from the sidebar, add tracks with `w`, and play a playlist as a queue with `Enter`.
**Local YouTube playlists** live in the spotatui config directory as `youtube_playlists.yml`, a plain human-editable file you can back up or share. Create one from the sidebar, add tracks with `w`, and play a playlist as a queue with `Enter`.

## Native Streaming

Expand All @@ -219,15 +219,15 @@ See the [Native Streaming Wiki](https://github.com/LargeModGames/spotatui/wiki/N

## Configuration

The config file is at `${HOME}/.config/spotatui/config.yml`. You can also configure spotatui in-app by pressing `Alt-,` to open Settings.
The config file is at `$XDG_CONFIG_HOME/spotatui/config.yml` when `XDG_CONFIG_HOME` is set to an absolute path, falling back to `${HOME}/.config/spotatui/config.yml` when it is unset or not absolute. You can also configure spotatui in-app by pressing `Alt-,` to open Settings.

Nearly everything is customizable: keybindings, themes, icons, playbar button labels, status-line and window-title format templates, table columns (reorder/rename/resize), default sorting per screen, startup screen, and layout (sidebar/playbar position). Invalid values fall back to defaults with a logged warning — a config typo never blocks startup.

- Customization guide: [`docs/configuration.md`](docs/configuration.md), with a commented [`examples/config.example.yml`](examples/config.example.yml)
- Full config reference: [Configuration Wiki](https://github.com/LargeModGames/spotatui/wiki/Configuration)
- Built-in themes (Spotify, Dracula, Nord, …): [Themes Wiki](https://github.com/LargeModGames/spotatui/wiki/Themes)

spotatui also stores local listening history at `${HOME}/.config/spotatui/history/listens.jsonl`, which powers `spotatui history recap`. Short or skipped plays are stored but excluded from recap totals.
spotatui also stores local listening history at `$XDG_STATE_HOME/spotatui/history/listens.jsonl` when `XDG_STATE_HOME` is set to an absolute path, falling back to `${HOME}/.local/state/spotatui/history/listens.jsonl` when it is unset or not absolute. This powers `spotatui history recap`. Short or skipped plays are stored but excluded from recap totals.

### Discord Rich Presence

Expand All @@ -243,7 +243,7 @@ You can also override the app ID via `SPOTATUI_DISCORD_APP_ID`, or disable it in

### Anonymous Song Counter

spotatui includes an opt-in global counter showing how many songs have been played by all users worldwide (the badge and chart at the top of this README). It is **completely anonymous** — no personal information, song names, artists, or listening history is collected; it only sends a simple increment when a new song starts. It is enabled by default and can be disabled with `enable_global_song_count: false` in `~/.config/spotatui/config.yml`. This is purely a fun community metric with zero tracking of individual users.
spotatui includes an opt-in global counter showing how many songs have been played by all users worldwide (the badge and chart at the top of this README). It is **completely anonymous** — no personal information, song names, artists, or listening history is collected; it only sends a simple increment when a new song starts. It is enabled by default and can be disabled with `enable_global_song_count: false` in `config.yml`. This is purely a fun community metric with zero tracking of individual users.

### GitHub Profile Widget

Expand Down Expand Up @@ -326,13 +326,17 @@ Follow the spotifyd documentation to get set up. After that:
If you used the original `spotify-tui` before:

- The binary name changed from `spt` to `spotatui`.
- Config paths changed: `~/.config/spotify-tui/` → `~/.config/spotatui/`.
- Config paths changed: `~/.config/spotify-tui/` -> `$XDG_CONFIG_HOME/spotatui/` when `XDG_CONFIG_HOME` is set to an absolute path, or `~/.config/spotatui/` when it is unset or not absolute.

You can copy your existing config:

```bash
mkdir -p ~/.config/spotatui
cp -r ~/.config/spotify-tui/* ~/.config/spotatui/
case "${XDG_CONFIG_HOME:-}" in
/*) config_home="$XDG_CONFIG_HOME" ;;
*) config_home="$HOME/.config" ;;
esac
mkdir -p "$config_home/spotatui"
cp -r ~/.config/spotify-tui/* "$config_home/spotatui/"
```

You may be asked to re-authenticate with Spotify the first time.
Expand Down
30 changes: 24 additions & 6 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

spotatui reads `config.yml` from the app config directory:

- Linux / macOS: `~/.config/spotatui/config.yml`
- Windows: `C:\Users\<you>\.config\spotatui\config.yml`
- `$XDG_CONFIG_HOME/spotatui/config.yml` when `XDG_CONFIG_HOME` is set to an absolute path.
- `${HOME}/.config/spotatui/config.yml` otherwise.

You can also point spotatui at a specific config file with `--config <path>`.

All fields are optional; omitted values use the built-in defaults. A complete, commented example lives in [`examples/config.example.yml`](../examples/config.example.yml).

Simple values (numbers, toggles, icons, positions) can also be changed live in the in-app **Settings** screen (see the hint in the top-right of the UI). Structured config — `format:` templates, `tables:` columns, and `playbar_control_labels` — is file-only. Edit the file while the app is closed: saving from the Settings screen rewrites the `behavior`, `theme`, and `keybindings` sections, but your `format:`, `tables:`, and `plugin_commands:` sections survive in-app saves untouched.

Machine-managed runtime state lives separately in `$XDG_STATE_HOME/spotatui/state.yml` when `XDG_STATE_HOME` is set to an absolute path, or `${HOME}/.local/state/spotatui/state.yml` when it is unset or not absolute. This includes volume, shuffle, active source, seen announcements, resized pane dimensions, and saved radio stations. The Spotify OAuth token cache and local listening history also live under the app state directory. Native streaming credentials and audio cache live under `$XDG_CACHE_HOME/spotatui/streaming_cache` when `XDG_CACHE_HOME` is set to an absolute path, or `${HOME}/.cache/spotatui/streaming_cache` when it is unset or not absolute. `sync_token` remains user config and is stored in `config.yml`.

## Safe by default

A typo in `config.yml` never prevents the app from starting. Structural mistakes — an unknown sort field, a bad template placeholder, an invalid column id, an icon that is too wide — are logged as warnings and the affected value falls back to its built-in default. Warnings go to the log file whose path is printed at startup (`/tmp/spotatui_logs/spotatuilog<pid>`).
Expand All @@ -35,7 +39,7 @@ behavior:

# Volume
volume_increment: 10 # step for + / - (0..=100, fatal if outside)
volume_percent: 100 # startup volume
volume_percent: 100 # initial volume default; saved runtime volume wins once present

# Scrolling
table_scroll_padding: 5 # rows kept visible below the selection before
Expand Down Expand Up @@ -82,20 +86,34 @@ Valid fields per screen:

`default` keeps the order the API returns (playlist order, date saved, play order). A field that is not valid for that screen falls back to `default` with a warning.

## Internet Radio

Preconfigured stations can be declared in `config.yml`:

```yaml
behavior:
radio_stations:
- name: SomaFM Groove Salad
url: https://ice1.somafm.com/groovesalad-128-mp3
```

Stations saved from inside the app are stored in `state.yml`. The sidebar merges configured stations first and app-saved stations second, deduped by stream URL. To remove a configured station, edit `config.yml`; the in-app remove action only removes app-saved stations from `state.yml`.

## Layout

```yaml
behavior:
sidebar_position: left # left | right | hidden
playbar_position: bottom # bottom | top
sidebar_width_percent: 20 # 0 hides the sidebar entirely
library_height_percent: 30
playbar_height_rows: 6 # 0 hides the playbar
sidebar_width_percent: 20 # initial sidebar width default; saved runtime size wins once present
library_height_percent: 30 # initial library height default; saved runtime size wins once present
playbar_height_rows: 6 # initial playbar height default, 0..=50; 0 hides it; saved runtime size wins once present
small_terminal_width: 150
small_terminal_height: 45
```

- `sidebar_position: hidden` gives the content the full width, but the sidebar auto-reveals while the Library or Playlists panel has keyboard focus or is hovered, so it never becomes unreachable.
- `sidebar_width_percent`, `library_height_percent`, and `playbar_height_rows` are configured initial defaults. Runtime resize changes use `{` / `}` for sidebar width, `(` / `)` for playbar height, `(` / `)` while hovering Library or Playlists for the library/sidebar split, and `|` to reset sizes; those changes persist in `state.yml` and are not overwritten by configured defaults after state exists. Configured `playbar_height_rows` is capped at 50 rows when applied at startup or when resetting the layout.
- `small_terminal_width` / `small_terminal_height` are the responsive-layout breakpoints. At or above `small_terminal_width` columns the app uses the wide layout (search box inside the sidebar); below it the search box gets its own full-width top row. `enforce_wide_search_bar: true` forces the full-width search row regardless of width.
- Unknown position strings fall back to the default with a warning.
- Mouse hit-testing follows every arrangement automatically.
Expand Down
4 changes: 3 additions & 1 deletion docs/keybindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ Press `?` in spotatui to see the help menu with all keybindings.

## Customizing Keybindings

Edit `~/.config/spotatui/config.yml`:
Edit `config.yml` in the spotatui app config directory (`$XDG_CONFIG_HOME/spotatui`
when `XDG_CONFIG_HOME` is set to an absolute path, or `~/.config/spotatui` when
it is unset or not absolute):

```yaml
keybindings:
Expand Down
14 changes: 10 additions & 4 deletions docs/native-streaming.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@ Native streaming uses 320 kbps by default. To select a different quality, set
streaming_bitrate: 320 # 96, 160, or 320 kbps
```

`client.yml` is in the same app config directory as `config.yml` (for example,
`~/.config/spotatui/client.yml` on Linux and macOS). This setting controls the
librespot native player directly; the Spotify app may still describe a Connect
device's quality as "Automatic".
`client.yml` is in the spotatui app config directory (for example,
`$XDG_CONFIG_HOME/spotatui/client.yml` when `XDG_CONFIG_HOME` is set to an
absolute path, or `~/.config/spotatui/client.yml` when it is unset or not
absolute). This setting controls the librespot native player directly; the
Spotify app may still describe a Connect device's quality as "Automatic".

Native-streaming credentials and audio cache are stored in the app cache
directory, for example `$XDG_CACHE_HOME/spotatui/streaming_cache` when
`XDG_CACHE_HOME` is set to an absolute path, or
`~/.cache/spotatui/streaming_cache` when it is unset or not absolute.

## Notes

Expand Down
24 changes: 14 additions & 10 deletions docs/scripting.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ feature, which is enabled in the default build.

## File locations

Plugins are loaded from your config directory (`~/.config/spotatui/`) at startup, in this order:
Plugins are loaded from your app config directory (`$XDG_CONFIG_HOME/spotatui`
when `XDG_CONFIG_HOME` is set to an absolute path, or `~/.config/spotatui`
when it is unset or not absolute) at startup, in this order:

1. `init.lua`, if present.
2. Single-file plugins: every `plugins/*.lua` file, sorted by filename.
Expand Down Expand Up @@ -54,10 +56,11 @@ spotatui plugin remove <name> # uninstall
spotatui plugin new <name> # scaffold a new plugin to start from
```

`add` clones the repository into `~/.config/spotatui/plugins/<name>/` (a shallow clone) and
records it in `~/.config/spotatui/plugins.lock`. `update` fast-forwards each clone to the remote's
latest commit. Restart spotatui after installing or updating for changes to take effect, and bind
any commands the plugin registers under `plugin_commands` in `config.yml`.
`add` clones the repository into `plugins/<name>/` under the spotatui app config
directory (a shallow clone) and records it in `plugins.lock` in that same
directory. `update` fast-forwards each clone to the remote's latest commit.
Restart spotatui after installing or updating for changes to take effect, and
bind any commands the plugin registers under `plugin_commands` in `config.yml`.

Single-file plugins you drop into `plugins/` by hand are not tracked in the lockfile; `plugin list`
shows them under "untracked".
Expand All @@ -71,9 +74,9 @@ your config directory:
spotatui plugin new my-plugin
```

This writes `~/.config/spotatui/plugins/my-plugin/main.lua` (with a `require_api` guard, a sample
command, and a suggested key binding) plus a `README.md`. Edit it, then `git init` and push to
share it.
This writes `plugins/my-plugin/main.lua` under the spotatui app config directory
(with a `require_api` guard, a sample command, and a suggested key binding) plus
a `README.md`. Edit it, then `git init` and push to share it.

A shareable plugin is a git repository with a `main.lua` (or `init.lua`) entry point at its root:

Expand Down Expand Up @@ -289,8 +292,9 @@ tick. If the app stalls past several interval periods, the interval fires once a
### Persistent storage

Each plugin gets a private key-value store persisted as plain JSON at
`~/.config/spotatui/plugin-data/<plugin>.json`. Values must be JSON-serializable (tables,
strings, numbers, booleans); functions and userdata raise.
`plugin-data/<plugin>.json` under the spotatui app config directory. Values must
be JSON-serializable (tables, strings, numbers, booleans); functions and userdata
raise.

- `spotatui.storage_get(key)` - the stored value, or `nil`.
- `spotatui.storage_set(key, value)` - store a value. `nil` removes the key.
Expand Down
4 changes: 3 additions & 1 deletion docs/themes.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ spotatui comes with several built-in theme presets. Access them via `Alt-,` > Th

## Custom Themes

You can create custom themes in `~/.config/spotatui/config.yml`:
You can create custom themes in `config.yml` in the spotatui app config directory
(`$XDG_CONFIG_HOME/spotatui` when `XDG_CONFIG_HOME` is set to an absolute path,
or `~/.config/spotatui` when it is unset or not absolute):

```yaml
theme:
Expand Down
18 changes: 14 additions & 4 deletions examples/plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,28 @@ privileges, so read anything you install from elsewhere (see
Single-file plugins go straight into `plugins/`:

```bash
cp track-notifier.lua ~/.config/spotatui/plugins/
case "${XDG_CONFIG_HOME:-}" in
/*) config_home="$XDG_CONFIG_HOME" ;;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this is more shell than the thing it installs.

The five-line case block is now in both README snippets and six plugin header comments, replacing a one-line cp. ${XDG_CONFIG_HOME:-$HOME/.config} covers the unset case inline; the relative-value case is rare enough to state once in PLUGINS.md. CodeRabbit asked for correctness here and got it, but the example headers are the first thing a plugin author reads.

*) config_home="$HOME/.config" ;;
esac
mkdir -p "$config_home/spotatui/plugins"
cp track-notifier.lua "$config_home/spotatui/plugins/"
```

Directory plugins (a folder with a `main.lua` entry point) are copied as a whole:

```bash
cp -r session-stats ~/.config/spotatui/plugins/
case "${XDG_CONFIG_HOME:-}" in
/*) config_home="$XDG_CONFIG_HOME" ;;
*) config_home="$HOME/.config" ;;
esac
mkdir -p "$config_home/spotatui/plugins"
cp -r session-stats "$config_home/spotatui/plugins/"
```

Restart spotatui after installing. Plugins that register commands need a key binding; add one to
`~/.config/spotatui/config.yml` under `plugin_commands` (each plugin documents a suggested key in
its header comment).
`config.yml` in the spotatui app config directory under `plugin_commands` (each
plugin documents a suggested key in its header comment).

To install a plugin published as a git repository, use the built-in installer instead:

Expand Down
9 changes: 7 additions & 2 deletions examples/plugins/accent-cycler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
-- Theme overrides from set_theme are runtime-only; they reset when spotatui restarts.
--
-- Install (single file):
-- cp accent-cycler.lua ~/.config/spotatui/plugins/
-- case "${XDG_CONFIG_HOME:-}" in
-- /*) config_home="$XDG_CONFIG_HOME" ;;
-- *) config_home="$HOME/.config" ;;
-- esac
-- mkdir -p "$config_home/spotatui/plugins"
-- cp accent-cycler.lua "$config_home/spotatui/plugins/"
--
-- Suggested binding, in ~/.config/spotatui/config.yml:
-- Suggested binding, in config.yml in the spotatui app config directory:
-- plugin_commands:
-- cycle_accent: "ctrl-y"

Expand Down
7 changes: 6 additions & 1 deletion examples/plugins/now-playing-webhook.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
-- webhooks, home-automation triggers, or a "what am I listening to" endpoint.
--
-- Install (single file):
-- cp now-playing-webhook.lua ~/.config/spotatui/plugins/
-- case "${XDG_CONFIG_HOME:-}" in
-- /*) config_home="$XDG_CONFIG_HOME" ;;
-- *) config_home="$HOME/.config" ;;
-- esac
-- mkdir -p "$config_home/spotatui/plugins"
-- cp now-playing-webhook.lua "$config_home/spotatui/plugins/"
-- Edit WEBHOOK_URL below first.

local WEBHOOK_URL = "https://example.com/webhook"
Expand Down
9 changes: 7 additions & 2 deletions examples/plugins/queue-browser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
-- async data reads, timers, storage, navigation).
--
-- Install (single file):
-- cp queue-browser.lua ~/.config/spotatui/plugins/
-- case "${XDG_CONFIG_HOME:-}" in
-- /*) config_home="$XDG_CONFIG_HOME" ;;
-- *) config_home="$HOME/.config" ;;
-- esac
-- mkdir -p "$config_home/spotatui/plugins"
-- cp queue-browser.lua "$config_home/spotatui/plugins/"
--
-- Suggested binding, in ~/.config/spotatui/config.yml:
-- Suggested binding, in config.yml in the spotatui app config directory:
-- plugin_commands:
-- queue_browser: "ctrl-b"
--
Expand Down
Loading
Loading