# Themes and Pi packages

> Theme export requirements, package manifest validation, and how shareable Pi packages bundle extensions, skills, templates, and themes.

- Repository: earendil-works/pi
- GitHub: https://github.com/earendil-works/pi
- Human docs: https://grok-wiki.com/public/docs/earendil-works-pi-7860a70e44d1
- Complete Markdown: https://grok-wiki.com/public/docs/earendil-works-pi-7860a70e44d1/llms-full.txt

## Source Files

- `packages/coding-agent/README.md`
- `packages/coding-agent/package.json`
- `packages/coding-agent/test/suite/regressions/5596-missing-theme-export.test.ts`
- `packages/coding-agent/test/suite/regressions/7187-malformed-package-manifest.test.ts`
- `packages/coding-agent/examples/extensions/README.md`

---

---
title: "Themes and Pi packages"
description: "Theme export requirements, package manifest validation, and how shareable Pi packages bundle extensions, skills, templates, and themes."
---

Pi themes are JSON color packs for the interactive TUI and HTML session export. Pi packages are installable bundles that declare extensions, skills, prompt templates, and themes under a `package.json` `pi` manifest (or conventional directories), managed with `pi install` / `pi remove` / `pi update` / `pi list` / `pi config` and persisted in `settings.packages`.

## Themes

### Load locations

Pi discovers themes from these sources (later discovery still dedupes by theme `name`; first name wins within a load pass):

| Source | Path / mechanism |
|--------|------------------|
| Built-in | `dark`, `light` (shipped theme JSON) |
| Global | `~/.pi/agent/themes/*.json` |
| Project | `.pi/themes/*.json` (after project trust) |
| Settings | `themes` array (files or directories) |
| Packages | `themes/` or `pi.themes` from installed packages |
| CLI | `--theme <path>` (repeatable) |

Disable discovery with `--no-themes`.

Select the active theme in `/settings` or `settings.json`:

```json
{
  "theme": "my-theme"
}
```

On first run, pi probes the terminal background and defaults to `dark` or `light`. Auto light/dark selection uses a slash form in settings (`lightTheme/darkTheme`); theme **file** `name` values must not contain `/` because that character is reserved for that setting.

Active custom theme files hot-reload when edited on disk.

### Theme JSON shape

```json
{
  "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
  "name": "my-theme",
  "vars": {
    "primary": "#00aaff",
    "secondary": 242
  },
  "colors": {
    "accent": "primary"
  },
  "export": {
    "pageBg": "#18181e",
    "cardBg": "#1e1e24",
    "infoBg": "#3c3728"
  }
}
```

| Field | Required | Rules |
|-------|----------|--------|
| `name` | Yes | Unique display/id name; must not contain `/` |
| `colors` | Yes | All required color tokens (51 required; see schema) |
| `vars` | No | Reusable palette; colors may reference var names |
| `export` | No | Optional HTML export backgrounds |
| `$schema` | No | Editor validation only |

**Color value formats:** `#RRGGBB` hex, 256-color integer `0–255`, var name string, or `""` for terminal default.

**Optional color tokens with fallbacks:**

| Token | Fallback when omitted |
|-------|------------------------|
| `thinkingMax` | `thinkingXhigh` |
| `scrollbarThumb` | `selectedBg` |

Invalid JSON or missing required tokens throw load errors that list missing color keys and point at the built-in themes as references. Prefer the schema at `src/modes/interactive/theme/theme-schema.json` for the full required token set.

### HTML export colors

Session HTML export (`/export`, `AgentSession.exportToHtml`, RPC export) resolves CSS from the active theme:

1. Prefer explicit `export.pageBg`, `export.cardBg`, `export.infoBg` from the theme file.
2. Those values resolve through the same `vars` references and 256→hex conversion as TUI colors.
3. If `export` is omitted or a field is unset/empty, export derives `pageBg` / `cardBg` / `infoBg` from `userMessageBg` (with fixed fallbacks if that color cannot be parsed).

Built-in `dark` / `light` themes ship explicit `export` blocks (for example dark uses `#18181e` / `#1e1e24` / `#3c3728`).

### Missing configured theme during export

If `settings.theme` names a theme that is not loadable, export does **not** fail. `exportToHtml` only passes a theme name when `getThemeByName` succeeds; otherwise it exports with the active runtime theme (fallback), while leaving the configured setting value unchanged.

```text
settings.theme = "missing-theme"
        │
        ▼
getThemeByName("missing-theme") ──fail──► themeName = undefined
        │
        ▼
exportSessionToHtml(... active Theme object ...)
```

### CLI theme flags

| Flag | Effect |
|------|--------|
| `--theme <path>` | Load additional theme path (repeatable) |
| `--no-themes` | Disable theme discovery |

---

## Pi packages

### What a package is

A Pi package is a directory (npm, git clone, or local path) that contributes any of:

| Resource | Conventional dir | Manifest key | File pattern |
|----------|------------------|--------------|--------------|
| Extensions | `extensions/` | `pi.extensions` | `.ts`, `.js` |
| Skills | `skills/` | `pi.skills` | `SKILL.md` trees / `.md` skills |
| Prompt templates | `prompts/` | `pi.prompts` | `.md` |
| Themes | `themes/` | `pi.themes` | `.json` |

Packages are **provider-neutral** portable skill/extension catalogs: they do not require a hosted connector or a specific model vendor. They run with full local privileges—review third-party source before install.

### Package manager commands

```bash
pi install npm:@foo/bar@1.0.0
pi install git:github.com/user/repo@v1
pi install https://github.com/user/repo
pi install /absolute/path/to/package
pi install ./relative/path/to/package

pi remove npm:@foo/bar
pi list
pi update                 # update pi only
pi update --all           # pi + packages
pi update --extensions    # packages only
pi update npm:@foo/bar    # one package
pi config                 # enable/disable resources
```

| Option | Meaning |
|--------|---------|
| `-l` / `--local` | Write project settings (`.pi/settings.json`) and install under project tree |
| `--approve` / `--no-approve` | Trust or ignore project-local settings for this command |
| `-e` / `--extension <source>` | Ephemeral install for current run only (temp dir) |

Default install target is user settings (`~/.pi/agent/settings.json`). Project packages install after project trust; missing project packages can be installed on startup when trusted.

### Install locations

| Source | User scope | Project scope |
|--------|------------|---------------|
| npm | `~/.pi/agent/npm/node_modules/<name>` | `.pi/npm/node_modules/<name>` |
| git | `~/.pi/agent/git/<host>/<path>` | `.pi/git/<host>/<path>` |
| local | Path as configured (not copied) | Path as configured (not copied) |

Temporary (`-e`) installs use agent temp extension folders under the agent dir.

Offline: `--offline` or `PI_OFFLINE=1` disables startup network package update checks (and related install/update telemetry).

### Package sources

| Kind | Spec examples | Notes |
|------|---------------|-------|
| npm | `npm:pkg`, `npm:@scope/pkg@1.2.3` | Exact versions are pinned and skipped by bulk package updates |
| git | `git:github.com/user/repo@v1`, `ssh://…`, `https://…` | Refs are pinned; update reconciles clone to configured ref |
| local | `/abs/path`, `./rel/path` | File → single extension; directory → package rules |

`npmCommand` in settings pins the npm argv used for lookup/install (for example `["mise", "exec", "node@20", "--", "npm"]`).

---

## Package manifest validation

### `package.json` `pi` key

```json
{
  "name": "my-package",
  "keywords": ["pi-package"],
  "pi": {
    "extensions": ["./extensions"],
    "skills": ["./skills"],
    "prompts": ["./prompts"],
    "themes": ["./themes"]
  }
}
```

Paths are relative to the package root. Entries may use globs (`*`, `?`) and override patterns (`!`, `+`, `-`). Tag packages with the `pi-package` keyword for gallery discoverability. Optional gallery fields on `pi`: `video` (MP4), `image` (PNG/JPEG/GIF/WebP).

### Malformed field handling

`readPiManifest` only accepts resource fields that are **arrays of strings**. Invalid shapes are ignored field-by-field; valid sibling fields still load.

```text
pi: {
  skills: "./skills",          ← ignored (string, not string[])
  prompts: ["./prompts"]       ← kept
}
→ skills not loaded; prompts load
```

If `package.json` is missing, unreadable, or has no object `pi`, the manager falls back to conventional directories (`extensions/`, `skills/`, `prompts/`, `themes/`) when present.

### Manifest vs convention discovery

```text
package root
    │
    ├─ package.json with valid pi.* array(s)
    │      → expand globs, apply !/+/- overrides, collect files
    │
    └─ no pi manifest
           → load each existing conventional resource directory
```

Extensions also support smart discovery: `pi.extensions` paths, or `index.ts` / `index.js` in a directory, or flat `.ts`/`.js` files under `extensions/`.

---

## Package filtering in settings

`settings.packages` entries may be a source string or a filter object:

```json
{
  "packages": [
    "npm:simple-pkg",
    {
      "source": "npm:my-package",
      "extensions": ["extensions/*.ts", "!extensions/legacy.ts"],
      "skills": [],
      "prompts": ["prompts/review.md"],
      "themes": ["+themes/legacy.json"]
    }
  ]
}
```

| Filter form | Effect |
|-------------|--------|
| Omit key | Load all resources of that type allowed by the package |
| `[]` | Disable all of that type (still tracked as disabled) |
| globs / `!pattern` | Include / exclude |
| `+path` | Force-include exact path relative to package root |
| `-path` | Force-exclude exact path |
| `autoload: false` | Delta mode: start empty (or apply as delta over a global copy of the same package) and only apply listed patterns |

User filters **layer on** the manifest; they narrow already-allowed files rather than replacing the package definition.

### Pattern evaluation order

For normal filters:

1. Includes (or all files if no plain includes)
2. `!` excludes
3. `+` force-includes
4. `-` force-excludes

---

## Dependencies inside packages

| Dependency kind | Placement |
|-----------------|-----------|
| Runtime third-party libs | `dependencies` (installed via `npm install` on package install) |
| Core pi packages used only as imports | `peerDependencies` with `"*"`: `@earendil-works/pi-ai`, `@earendil-works/pi-agent-core`, `@earendil-works/pi-coding-agent`, `@earendil-works/pi-tui`, `typebox` — do not bundle them |
| Other pi packages whose resources you re-export | `dependencies` + `bundledDependencies`, then list `node_modules/...` paths in the `pi` manifest |

Packages load with separate module roots so independent installs do not share Node module identity.

---

## Resource precedence and enablement

When the same resource name collides, load order uses precedence (highest first):

1. Project settings entry
2. Project auto-discovered
3. User settings entry
4. User auto-discovered
5. Package resource

Use `pi config` (global by default; Tab or `-l` for project) to enable/disable package and local resources without uninstalling. `/reload` reloads themes along with extensions, skills, prompts, and context files.

Project vs user package identity:

| Type | Identity key |
|------|----------------|
| npm | package name |
| git | repository URL without ref |
| local | resolved absolute path |

Project entry wins over global for the same identity unless the project entry sets `autoload: false` (then it applies as a delta over global).

---

## Author checklist

<Steps>
  <Step title="Add a theme">
    Create `~/.pi/agent/themes/my-theme.json` (or package `themes/`) with required `name` + `colors`. Optionally set `export` for HTML. Select via `/settings` or `"theme": "my-theme"`.
  </Step>
  <Step title="Bundle a package">
    Add `keywords: ["pi-package"]` and a `pi` object (or conventional dirs). Keep core pi packages as peer deps; put runtime deps under `dependencies`.
  </Step>
  <Step title="Install and verify">
    `pi install ./my-package` or `npm:…` / `git:…`, then `pi list` and `pi config` to confirm resources. In interactive mode, open `/settings` for themes and use `/reload` after edits.
  </Step>
  <Step title="Export with theme colors">
    Run `/export` or `exportToHtml`. Prefer explicit `export` fields; if the configured theme is missing, export still succeeds with the active fallback theme.
  </Step>
</Steps>

---

## Failure modes

| Symptom | Cause / resolution |
|---------|-------------------|
| Theme load error listing missing tokens | Incomplete `colors`; compare against built-in themes / schema |
| `Invalid theme name … cannot contain "/"` | Use slash only in settings auto-theme form, not in JSON `name` |
| Package skills missing, prompts present | Manifest field not a string array (malformed field dropped) |
| Package does nothing | No valid `pi` arrays and no conventional dirs; check paths |
| Project packages not loading | Project not trusted; use trust flow or `--approve` on package commands |
| Pinned npm/git not moving on `pi update --extensions` | Expected for exact versions / pinned refs; reinstall with new version/ref |
| Third-party package security | Extensions and skills run with full system access—review before install |

---

## Related pages

<CardGroup>
  <Card title="Skills" href="/skills">
    SKILL.md rules, naming, and collision precedence for skill resources packages can ship.
  </Card>
  <Card title="Extensions" href="/extensions">
    TypeScript extension registration and lifecycle used by package-delivered extensions.
  </Card>
  <Card title="Prompt templates" href="/prompt-templates">
    Prompt template discovery and session wiring for package `prompts/` trees.
  </Card>
  <Card title="Settings" href="/settings">
    `packages`, `themes`, and related settings load/merge behavior.
  </Card>
  <Card title="Extension examples" href="/extension-examples">
    Reference extension packages and local extension layouts.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Runtime failures that can interact with installs, reloads, and session export.
  </Card>
</CardGroup>
