---
slug: "mjakl-pi-dark-or-light"
source_type: "readme"
source_url: "https://cdn.jsdelivr.net/gh/mjakl/pi-dark-or-light@main/README.md"
repo: "https://github.com/mjakl/pi-dark-or-light"
source_file: "README.md"
branch: "main"
---
# pi-dark-or-light

**Automatically switch Pi between configured dark and light themes.**

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

## User Guide

### Why pi-dark-or-light

Terminals, desktop environments, and multiplexers can change between dark and light appearances. This extension detects the current mode and applies the matching Pi theme.

In short: the extension decides **dark vs light**, and you optionally decide **which theme name** each mode should use.

### Features

- **Dark/light detection** — resolves the current appearance from OS, tmux, environment, terminal, or fallback signals.
- **Theme mapping** — map detected `dark` and `light` modes to built-in or custom Pi theme names.
- **UI-only behavior** — runs only in interactive UI sessions; headless/non-UI runs are left alone.
- **Live polling** — re-checks every 5 seconds so appearance changes can be picked up while Pi is running.
- **Terminal background query** — supports xterm-compatible `OSC 11`, useful in multiplexers such as Herdr when `COLORFGBG` is absent.
- **Project override support** — reads global and project Pi settings.

### Install

Install from npm:

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

Install from git:

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

Or install from a local checkout:

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

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

### Quick start

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 Pi theme setup, including built-in and custom themes, see Pi's [themes documentation](https://github.com/earendil-works/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.

Use either config key:

- `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 when no detector returns an answer; allowed values are `dark` and `light`.

Partial config is valid. For example:

```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:

```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`.

### 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.

### Recommended setup

If you want Pi to follow your environment automatically:

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

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

---

## Technical Reference

These sections document config merging, detector order, and local development details.

### When the extension is active

The 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 applies the detected dark/light result on startup and during polling.

### Config merge behavior

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

Use the same key spelling in both files if you rely on merging. 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"
  }
}
```

Example project settings:

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

Result for that project:

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

### Detection order

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. terminal default background query (`OSC 11`)
6. `COLORFGBG` heuristic
7. configured `default` mode, defaulting to `dark`

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

### Operating-system-specific order

#### macOS

On macOS, the extension first asks the OS directly by running `osascript` and querying System Events for the current dark mode setting.

- `true` -> `dark`
- `false` -> `light`

macOS order:

1. Native macOS appearance via `osascript`
2. tmux `#{client_theme}`
3. `DARK_MODE`
4. terminal default background query (`OSC 11`)
5. `COLORFGBG`
6. configured `default` mode, defaulting to `dark`

#### Windows

On Windows, the extension first checks:

```text
HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize
```

It reads `AppsUseLightTheme`:

- `0` -> `dark`
- `1` -> `light`

Windows order:

1. Native Windows appearance via `AppsUseLightTheme`
2. tmux `#{client_theme}`
3. `DARK_MODE`
4. terminal default background query (`OSC 11`)
5. `COLORFGBG`
6. configured `default` mode, defaulting to `dark`

#### Linux and other Unix-like systems

There is no desktop-environment-specific Linux detector. Linux appearance settings vary across GNOME, KDE, sway, Hyprland, remote shells, containers, and headless sessions, so the extension uses terminal-oriented signals instead.

Linux / other Unix order:

1. tmux `#{client_theme}`
2. `DARK_MODE`
3. terminal default background query (`OSC 11`)
4. `COLORFGBG`
5. configured `default` mode, defaulting to `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 attempted only when the environment suggests Pi is running in tmux (`TMUX` is set or `TERM_PROGRAM=tmux`).

#### `DARK_MODE`

If `DARK_MODE` is set, the extension accepts:

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

This is useful from shell profiles, wrapper scripts, launchers, or platform-specific automation.

#### Terminal default background (`OSC 11`)

If no explicit environment override is set, the extension asks the current terminal for its default background color:

```text
ESC ] 11 ; ? ESC \\
```

Terminals that support this xterm-compatible query answer with an RGB color such as:

```text
ESC ] 11 ; rgb:fafa/fafa/fafa ESC \\
```

The extension classifies bright backgrounds as `light` and dark backgrounds as `dark`.

If the terminal or multiplexer does not answer quickly, the detector times out and the chain continues.

Herdr note: Herdr 0.7.0 captures the host terminal background when the client attaches. If you change your terminal theme while keeping the same Herdr client attached, Herdr may keep answering with the old color until you detach and reattach.

#### `COLORFGBG`

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

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

- background color index `< 8` -> `dark`
- background color index `>= 8` -> `light`

Examples:

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

Unusual multi-part `COLORFGBG` values may not apply and the detector chain will continue.

### Final fallback

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

Default if unset: `dark`.

Allowed values: `dark`, `light`.

### Local development

Install dependencies and run the type check:

```bash
npm install
npm run typecheck
```

One-off local testing:

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

## Acknowledgements

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

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

## License

MIT
