npm package: @mjakl/pi-dark-or-light
Description: Auto-switch pi between light and dark themes using hierarchical detection
Author: mjakl
Latest version: 1.0.0

# pi-dark-or-light

`pi-dark-or-light` is a [pi](https://github.com/badlogic/pi-mono) extension that automatically chooses a dark or light theme for pi.

It is meant for people who want pi to follow the appearance of their system or terminal without hard-coding a single `theme` value in `settings.json`.

## What it does

- Detects whether your environment is currently **dark** or **light**
- Applies the matching pi theme when a UI session starts
- Only runs in **UI sessions**; it does not do anything in headless/non-UI runs
- Re-checks every **5 seconds**, so appearance changes can be picked up while pi is running
- Lets you map detected `dark` / `light` modes to either pi's built-in themes or your own custom theme names
- Re-applies the detected theme even if pi persists a top-level `theme` setting internally

In short: this extension decides **dark vs light**, and you can optionally decide **which actual theme name** should be used for each mode.

## When the extension is active

This extension manages the theme whenever it is loaded in a UI session.

If pi or another workflow writes a top-level `theme` into `settings.json`, this extension still keeps applying the detected dark/light result on startup and during polling.

## Installation

### Option 1: Install from npm (recommended)

```bash
pi install npm:@mjakl/pi-dark-or-light
```

### Option 2: Install via git

```bash
pi install git:github.com/mjakl/pi-dark-or-light
```

### Option 3: Install from a local checkout

```bash
pi install /path/to/pi-dark-or-light
```

### Option 4: Development or one-off testing

```bash
pi -e /path/to/pi-dark-or-light/src/index.ts
```

Package name: `@mjakl/pi-dark-or-light`

## Quick start

1. Install the extension.
2. Optionally add a `dark-or-light` mapping.

Minimal setup:

```json
{
  "dark-or-light": {
    "dark": "dark",
    "light": "light"
  }
}
```

Example with custom themes:

```json
{
  "dark-or-light": {
    "dark": "tokyo-night",
    "light": "github-light"
  }
}
```

If you do not configure a mapping, the extension uses pi's built-in `dark` and `light` themes.

For details on pi themes themselves, including built-in and custom theme setup, see pi's own [themes documentation](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/themes.md).

## Configuration

The extension reads settings from the same places pi normally uses:

- Global: `~/.pi/agent/settings.json`
- Project: `.pi/settings.json`

Project settings override global settings.

For the extension-specific config, use either:

- `dark-or-light` (recommended)
- `darkOrLight` (also accepted)

### Supported config

```json
{
  "dark-or-light": {
    "dark": "theme-name-for-dark-mode",
    "light": "theme-name-for-light-mode",
    "default": "dark"
  }
}
```

All keys are optional.

- `dark`: theme name to use when detection resolves to dark mode
- `light`: theme name to use when detection resolves to light mode
- `default`: fallback mode to use when no detector returns an answer; allowed values are `dark` and `light`

For example, this is valid too:

```json
{
  "dark-or-light": {
    "dark": "tokyo-night"
  }
}
```

In that case:

- detected `dark` -> `tokyo-night`
- detected `light` -> built-in `light`

You can also change the final fallback mode. For example:

```json
{
  "dark-or-light": {
    "default": "light",
    "light": "github-light"
  }
}
```

In that case, if no detector succeeds, the extension chooses `light`, which then maps to `github-light`.

### Merge behavior

Global and project `dark-or-light` settings are merged field-by-field, with project settings overriding global ones.

Use the **same key spelling** in both files if you rely on merging. In other words, do not mix `dark-or-light` in one file with `darkOrLight` in the other.

Example:

Global settings:

```json
{
  "dark-or-light": {
    "dark": "tokyo-night",
    "light": "github-light"
  }
}
```

Project settings:

```json
{
  "dark-or-light": {
    "dark": "gruvbox-dark"
  }
}
```

Result for that project:

- `dark` -> `gruvbox-dark`
- `light` -> `github-light`

### If a mapped theme cannot be loaded

If you map `dark` or `light` to a custom theme name and pi cannot apply it, the extension falls back to the built-in `dark` or `light` theme for that detected mode.

## Detection order and heuristics

The extension uses a fixed detector chain. The first detector that returns a clear answer wins.

Overall order:

1. macOS native appearance
2. Windows native appearance
3. tmux client theme
4. `DARK_MODE` environment variable
5. `COLORFGBG` heuristic
6. Fallback to configured `default` mode (default: `dark`)

Detectors that do not apply on the current platform are skipped.

## How it decides on each operating system

### macOS

On macOS, the extension first asks the OS directly:

- Runs `osascript`
- Queries System Events for the current **dark mode** setting
- `true` -> `dark`
- `false` -> `light`

If that does not return a usable answer, it falls through to the shared terminal-based detectors below.

**macOS order:**

1. Native macOS appearance via `osascript`
2. tmux `#{client_theme}`
3. `DARK_MODE`
4. `COLORFGBG`
5. Fallback to configured `default` mode (default: `dark`)

### Windows

On Windows, the extension first checks the user theme setting in the registry:

- Reads `HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize`
- Uses `AppsUseLightTheme`
- `0` -> `dark`
- `1` -> `light`

If that fails, it falls through to the shared terminal-based detectors below.

**Windows order:**

1. Native Windows appearance via `AppsUseLightTheme`
2. tmux `#{client_theme}`
3. `DARK_MODE`
4. `COLORFGBG`
5. Fallback to configured `default` mode (default: `dark`)

### Linux and other Unix-like systems

There is currently **no desktop-environment-specific Linux detector** in this extension.

That is intentional: Linux appearance settings vary a lot across GNOME, KDE, sway, Hyprland, remote shells, containers, and headless sessions. Instead of guessing, the extension uses terminal-oriented signals that are more portable.

**Linux / other Unix order:**

1. tmux `#{client_theme}`
2. `DARK_MODE`
3. `COLORFGBG`
4. Fallback to configured `default` mode (default: `dark`)

## Shared detectors

### tmux

If pi is running inside tmux, the extension tries:

```text
tmux display-message -p #{client_theme}
```

Accepted values are normalized case-insensitively:

- `dark`
- `light`
- `1` / `true` -> `dark`
- `0` / `false` -> `light`

This detector is only attempted when the environment suggests pi is running in tmux (`TMUX` is set or `TERM_PROGRAM=tmux`).

### `DARK_MODE`

If the `DARK_MODE` environment variable is set, the extension accepts these values:

- `dark`, `1`, `true` -> `dark`
- `light`, `0`, `false` -> `light`

This is useful when you want to force the result from a shell profile, wrapper script, launcher, or platform-specific automation.

### `COLORFGBG`

If no stronger detector succeeds, the extension uses `COLORFGBG`, similar to pi's built-in heuristic.

It parses the **second** `;`-separated field of `COLORFGBG`. In the common two-part form, that field is the background color index.

- parses the second `;`-separated field
- if that color index is `< 8`, it chooses `dark`
- otherwise it chooses `light`

Examples:

- `COLORFGBG=15;0` -> second field `0` -> `dark`
- `COLORFGBG=0;15` -> second field `15` -> `light`

If your terminal emits a more unusual multi-part `COLORFGBG` value, this detector may not apply and the extension will continue down the chain.

### Final fallback

If none of the detectors above produce an answer, the extension falls back to the configured `dark-or-light.default` mode.

Default if unset:

- `dark`

Allowed values for `dark-or-light.default`:

- `dark`
- `light`

## Behavior summary

- Applies a theme on UI session start
- Polls every 5 seconds for changes
- Ignores detector failures and keeps going down the chain
- Uses project settings over global settings
- Merges `dark-or-light` config between global and project settings when both use the same config key spelling

## Recommended setup

If you want pi to follow your environment automatically:

1. Configure `dark-or-light` if you want custom theme names
2. Let the extension choose the current mode from OS/terminal signals

If you want a fixed theme instead, disable or remove this extension.

## Acknowledgements

This extension was influenced by pi's own macOS theme example:

- [`mac-system-theme.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/examples/extensions/mac-system-theme.ts)

## License

MIT