# Configuration Config lives at `~/.config/slk/config.toml`. ## Full example ```toml [general] default_workspace = "work" # the slug, not the team ID use_slack_sections = true # use real Slack sidebar sections (default). # set false to use [sections.*] globs instead. keep_focus_on_list = false # keep focus on the list when you select a # channel / open a thread (default false). # See "Browsing without losing focus" below. [appearance] theme = "dracula" timestamp_format = "3:04 PM" image_protocol = "auto" # auto | kitty | sixel | halfblock | off max_image_rows = 20 # cap inline image height in terminal rows panel_borders = true # draw the box border around each pane; # false = a thin left accent bar instead [animations] enabled = true smooth_scrolling = true typing_indicators = true [notifications] enabled = true on_mention = true on_dm = true on_keyword = ["deploy", "incident"] quiet_hours = "22:00-08:00" # planned [cache] message_retention_days = 30 max_db_size_mb = 500 max_image_cache_mb = 200 identity_ttl_days = 7 # re-fetch a user/bot's name + avatar after N days # so renames and new avatars show up; 0 = never refresh # Glob-based channel sections — only consulted when use_slack_sections # is false (globally or per-workspace), or when Slack's section API is # unreachable. Otherwise slk reads the user's actual Slack sections. [sections.Alerts] channels = ["alerts", "ops", "*-alerts"] order = 1 # Channels can carry an optional ":" suffix to pin their order # within the section. Lower numbers sort higher. Entries without a # suffix fall after annotated ones, in the order they appear. # This syntax is only honored when use_slack_sections = false; # in Slack-native mode, channel order comes from Slack. [sections.Engineering] channels = ["eng-general:1", "eng-alerts:2", "eng-*"] order = 2 # Per-workspace settings: keyed by a slug you choose at --add-workspace # time. team_id ties the slug to the underlying Slack workspace. [workspaces.work] team_id = "T01ABCDEF" order = 1 # rail position; 1-based, used by 1-9 keys theme = "dracula" # overrides [appearance].theme use_slack_sections = false # this workspace uses [sections.*] globs; # other workspaces still use Slack sections [workspaces.work.sections.Alerts] channels = ["alerts", "*-alerts"] order = 1 [workspaces.work.sections.Engineering] channels = ["eng-*", "deploys"] order = 2 # A second workspace with no per-workspace sections — falls back to # the global [sections.*] above. [workspaces.side] team_id = "T02XYZ" order = 2 # Inline color overrides on top of the active theme [theme] primary = "#4A9EFF" accent = "#50C878" background = "#1A1A2E" text = "#E0E0E0" ``` ## External commands Run a program against the selected message with the **`x`** key (pick from a list), or automatically when an incoming message matches a trigger. ```toml # Manual: select a message, press `x`, pick this command. [[external_commands]] name = "Create task" argv = ["/home/me/bin/slk-task"] # run directly (no shell) [[external_commands]] name = "OCR image" argv = ["tesseract", "-", "-"] capture_output = true # show stdout in an overlay (else a toast) [[external_commands]] name = "Edit in editor" argv = ["nvim"] interactive = true # suspend the TUI and attach the terminal confirm = true # ask before running # Auto: runs in the background when an incoming message matches. [[external_commands]] name = "Notify on mention" argv = ["/home/me/bin/notify"] on_mention = true # fires when you are @-mentioned [[external_commands]] name = "Page on deploy fail" argv = ["/home/me/bin/page"] match = "deploy failed" # case-insensitive substring # match_regex = "deploy (failed|errored)" # alternative: RE2 regex channels = ["alerts", "ops"] # optional: only these channel names include_self = false # set true to also fire on your own messages ``` **How the message reaches the command.** `argv` is executed directly (no shell, so no quoting hazards). The message text is piped to **stdin**, and metadata is provided via environment variables: | Variable | Value | |----------|-------| | `SLK_TEXT` | message text (also on stdin) | | `SLK_USER` / `SLK_USER_ID` | author display name / ID | | `SLK_TS` | message timestamp | | `SLK_CHANNEL` / `SLK_CHANNEL_NAME` | channel ID / name | | `SLK_PERMALINK` | archive link (when known) | | `SLK_IMAGE_PATHS` | newline-separated on-disk paths of the message's cached images | | `SLK_IMAGE_COUNT` | number of image paths | **Execution flags** (manual or auto): `capture_output` shows stdout in a scrollable overlay instead of a completion toast; `interactive` releases the terminal to the command (editors / TUI tools) and restores slk on exit; `confirm` prompts first. Auto-runs are always in the background (never interactive). **Auto-triggers.** A command with `on_mention`, `match`, and/or `match_regex` also runs automatically on incoming messages (across all workspaces and channels), firing if **any** of its triggers match. `channels` limits it to named channels. Your own messages are skipped unless `include_self = true`. A command can be both manual (`x`) and auto. ## Browsing without losing focus By default, picking a channel from the sidebar — or opening a thread — moves keyboard focus into the content pane, so you can immediately `j/k` through messages, react, and reply. The trade-off: to try another channel you have to `Tab`/`h` back to the sidebar first. Set `keep_focus_on_list = true` under `[general]` to keep focus on the list instead. Selecting a channel or opening a thread still loads the content, but keyboard focus stays put — so you can keep walking the list with `j/k` and `Enter` without tabbing back each time. When you want to read or reply, step into the content with `Tab`, `l`, or `→`. This applies to all three list→content jumps: sidebar → channel, threads view → thread, and a channel message → its thread. The default is `false` (focus follows selection). ## Section resolution When `use_slack_sections = true` (the default) and Slack's section endpoint is reachable, slk reads the user's actual sidebar sections — names, emoji, linked-list order, and channel membership — directly from Slack and keeps them live via WebSocket events. Any `[sections.*]` or `[workspaces..sections.*]` blocks in `config.toml` are ignored in this mode (a one-line info note is emitted to the debug log on first connect so the shadowing isn't silent). Set `use_slack_sections = false` globally, or per-workspace, to opt into glob-based sections instead. Per-workspace `[workspaces..sections.*]` blocks fully replace the global `[sections.*]` for that workspace. Workspaces that define no sections of their own fall back to the global table. ### Ordering channels within a section Each entry in a section's `channels` list may carry an optional `:` suffix where `N` is a non-negative integer. Channels matched by an annotated pattern sort ahead of channels matched by un-annotated patterns; among annotated channels, lower `N` wins; un-annotated channels keep the order Slack returned them in. ```toml [sections.Engineering] channels = ["eng-general:1", "eng-alerts:2", "eng-*"] order = 2 ``` In the example above, `#eng-general` is pinned to the top of the Engineering section, followed by `#eng-alerts`, followed by every other `eng-*` channel in Slack-API order. This syntax is only honored when `use_slack_sections = false` (or when Slack's section endpoint is unreachable and slk falls back to glob mode). In Slack-native mode, channel order within a section comes from Slack and the `:` suffix is ignored along with the rest of the `[sections.*]` block. ### v1 limitations of Slack-native sections v1 is read-only — section editing still happens in the official client; slk reflects the results. Your **Starred** conversations (Slack's `stars` section) are surfaced read-only and pinned to the top of the sidebar: star or unstar in the official client and slk reflects it live. Other system sections (`slack_connect`, `salesforce_records`, `agents`) remain hidden. Because this comes from Slack's section data, the Starred section only appears in Slack-native mode (`use_slack_sections = true`, the default); it is not available in glob-section mode. Sections with more than 10 channels may be returned only partially by Slack's API on initial load; the missing channels temporarily fall into the catch-all bucket and migrate into their correct section as WebSocket events fire or the workspace reconnects. A debug-log warning identifies which sections were truncated. ## Workspace order The `order` field controls workspace position in the rail and the mapping for the `1`–`9` digit keys. Positive values sort ascending (lowest first); workspaces without an `order` (or with `order = 0`) sort after explicitly ordered ones, alphabetically by slug. Tokens on disk that have no `[workspaces.]` block at all sort last, alphabetically by team ID. The order is stable across runs. Previously the rail order depended on which workspace's WebSocket connected first; it is now deterministic regardless of network timing, even without an explicit `order` set. Legacy configs that key the block by raw team ID (`[workspaces.T01ABCDEF]`) keep working unchanged. ## Terminal-palette themes (`ANSI Dark`, `ANSI Light`) Two built-in themes use ANSI 16 color codes exclusively rather than fixed RGB values. They inherit the user's terminal color palette, so changing your terminal colorscheme (light/dark, solarized, accessibility palettes, etc.) immediately changes slk's UI colors to match. ```toml [appearance] theme = "ANSI Dark" # or "ANSI Light" ``` Pick the variant whose background matches your terminal's background. **Trade-off:** selection-row highlights and compose-input tints are still computed as RGB approximations, so the tint regions of those elements use truecolor rather than your palette. The rest of the UI honors the palette. ## Custom themes Drop `.toml` files into `~/.config/slk/themes/`: ```toml name = "My Theme" [colors] primary = "#BD93F9" accent = "#50FA7B" warning = "#FFB86C" error = "#FF5555" background = "#282A36" surface = "#343746" surface_dark = "#21222C" text = "#F8F8F2" text_muted = "#6272A4" border = "#44475A" # Optional sidebar/rail overrides — lets you have a darker sidebar with a # lighter message pane (Slack's default look). Fall back to # background/text/text_muted/surface_dark when omitted. sidebar_background = "#19171D" sidebar_text = "#D1D2D3" sidebar_text_muted = "#9A9B9E" rail_background = "#19171D" ``` Every built-in theme now sets a channels-panel (sidebar) background that is perceptibly distinct from the message pane. When writing a custom theme, set `sidebar_background` to a clearly darker (or, on near-black themes, a slightly lighter) shade than `background` for the same effect. Switch themes live with `Ctrl+y`. ## Data paths (XDG) | Path | Contents | |---|---| | `~/.config/slk/` | Configuration, custom themes | | `~/.local/share/slk/` | SQLite cache, tokens | | `~/.cache/slk/` | Avatars, image cache |