Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/SETTINGS.md

Raw
Rendered preview

Extension settings contract

Every repository extension that reads configuration declares it through its own committed src/pi-ext-settings.ts and remains usable when installed without the rest of pi-ext. scripts/codegen/templates/pi-ext-settings.ts generates these local copies through mise run //:codegen for the extensions listed in the pi-ext-settings target of scripts/codegen/targets.json. Root and extension check tasks compare generated files byte-for-byte, and the template's own tests live in scripts/codegen/pi-ext-settings.test.ts. Debug selection is separate and follows the debug contract.

Declarations

A SettingDeclaration names a key, a parse function, a default, an optional flag, and an optional env variable. For example:

import {
  parseBooleanSetting,
  registerSettingFlag,
  resolveSetting,
  type SettingDeclaration,
} from "./src/pi-ext-settings.ts";

const ENABLE_SETTING: SettingDeclaration<boolean> = {
  key: "nushell.enable",
  parse: parseBooleanSetting,
  default: false,
  flag: { name: "nushell", type: "boolean", description: "Enable Nushell tools at startup" },
  env: "PI_NUSHELL_ENABLE",
};

registerSettingFlag(pi, ENABLE_SETTING); // at extension load
const setting = resolveSetting(pi, ctx, ENABLE_SETTING); // at session_start or first use

Keys start with the extension directory name and use camelCase leaves, such as git-safe.cloneTimeoutMs. An extension may declare its whole block, such as ultra, when one parser validates it as a unit. Renamed keys get no aliases; a stale key is invalid rather than silently ignored.

Resolution

Each declaration resolves independently in this order: flag, env, trusted project .pi/settings.json, user ~/.pi/agent/settings.json, default. Pass the real ctx.isProjectTrusted(); untrusted project settings are never read. Settings needed before any session exists, such as keyboard shortcuts, resolve at load with process.cwd() and trust false, so they come from env and user settings only. When project and user values are both plain objects, they deep-merge with project winning per key; arrays and scalars replace. Resolve once per session or operation, not on hot paths.

Sources

Flags suit startup toggles only. Pi flags are boolean or string, and a boolean flag can only turn behavior on. registerSettingFlag registers a flag without a default so an omitted flag defers to lower sources; never call pi.registerFlag or pi.getFlag directly. Environment variables keep established names, including third-party ones such as FIREFOX_BIN. New variables are PI_<SLUG>_<NAME>, uppercasing the slug and replacing hyphens with underscores. An empty environment value counts as set. Negated variables such as PI_MOCKUP_NO_OPEN invert the boolean parser for the env source only. Credentials and tokens stay in environment variables or Pi's credential store and never become settings.

Validation

Settings files accept JSON types only; numeric and boolean strings are accepted only from flags and env. Use parseBooleanSetting and parseNumberSetting, then add range checks; timer and subprocess timeouts are capped at MAX_TIMER_MS. A parser returns undefined or throws an Error to reject a value; thrown messages appear in the reported error. An invalid value never falls through to a lower source. Report it once through ctx.ui.notify with warning severity and use the built-in default, or fail the operation when the setting is a safety limit. Never write diagnostics to stdout.

Tests and documentation

Each participating extension keeps __tests__/settings.test.ts covering the default, each declared source, trusted project over user, ignored untrusted project, and invalid values without fallthrough. Tests assert observable behavior where practical and use test/harness with its sandboxed home; emit session_shutdown before disposing a session. Each extension README documents its keys, env variables, flags, defaults, valid ranges, trust, and invalid-value behavior.

Enforcement

scripts/extensions.mjs rejects direct getGlobalSettings, getProjectSettings, registerFlag, and getFlag calls outside generated src/pi-ext-*.ts files. SettingsManager.create remains allowed for building child agent sessions.

# Extension settings contract

Every repository extension that reads configuration declares it through its own committed `src/pi-ext-settings.ts` and remains usable when installed without the rest of `pi-ext`.
`scripts/codegen/templates/pi-ext-settings.ts` generates these local copies through `mise run //:codegen` for the extensions listed in the `pi-ext-settings` target of `scripts/codegen/targets.json`.
Root and extension `check` tasks compare generated files byte-for-byte, and the template's own tests live in `scripts/codegen/pi-ext-settings.test.ts`.
Debug selection is separate and follows the [debug contract](DEBUG.md).

## Declarations

A `SettingDeclaration` names a `key`, a `parse` function, a `default`, an optional `flag`, and an optional `env` variable.
For example:

```ts
import {
  parseBooleanSetting,
  registerSettingFlag,
  resolveSetting,
  type SettingDeclaration,
} from "./src/pi-ext-settings.ts";

const ENABLE_SETTING: SettingDeclaration<boolean> = {
  key: "nushell.enable",
  parse: parseBooleanSetting,
  default: false,
  flag: { name: "nushell", type: "boolean", description: "Enable Nushell tools at startup" },
  env: "PI_NUSHELL_ENABLE",
};

registerSettingFlag(pi, ENABLE_SETTING); // at extension load
const setting = resolveSetting(pi, ctx, ENABLE_SETTING); // at session_start or first use
```

Keys start with the extension directory name and use camelCase leaves, such as `git-safe.cloneTimeoutMs`.
An extension may declare its whole block, such as `ultra`, when one parser validates it as a unit.
Renamed keys get no aliases; a stale key is invalid rather than silently ignored.

## Resolution

Each declaration resolves independently in this order: flag, env, trusted project `.pi/settings.json`, user `~/.pi/agent/settings.json`, default.
Pass the real `ctx.isProjectTrusted()`; untrusted project settings are never read.
Settings needed before any session exists, such as keyboard shortcuts, resolve at load with `process.cwd()` and trust `false`, so they come from env and user settings only.
When project and user values are both plain objects, they deep-merge with project winning per key; arrays and scalars replace.
Resolve once per session or operation, not on hot paths.

## Sources

Flags suit startup toggles only.
Pi flags are boolean or string, and a boolean flag can only turn behavior on.
`registerSettingFlag` registers a flag without a default so an omitted flag defers to lower sources; never call `pi.registerFlag` or `pi.getFlag` directly.
Environment variables keep established names, including third-party ones such as `FIREFOX_BIN`.
New variables are `PI_<SLUG>_<NAME>`, uppercasing the slug and replacing hyphens with underscores.
An empty environment value counts as set.
Negated variables such as `PI_MOCKUP_NO_OPEN` invert the boolean parser for the env source only.
Credentials and tokens stay in environment variables or Pi's credential store and never become settings.

## Validation

Settings files accept JSON types only; numeric and boolean strings are accepted only from flags and env.
Use `parseBooleanSetting` and `parseNumberSetting`, then add range checks; timer and subprocess timeouts are capped at `MAX_TIMER_MS`.
A parser returns `undefined` or throws an `Error` to reject a value; thrown messages appear in the reported error.
An invalid value never falls through to a lower source.
Report it once through `ctx.ui.notify` with warning severity and use the built-in default, or fail the operation when the setting is a safety limit.
Never write diagnostics to stdout.

## Tests and documentation

Each participating extension keeps `__tests__/settings.test.ts` covering the default, each declared source, trusted project over user, ignored untrusted project, and invalid values without fallthrough.
Tests assert observable behavior where practical and use `test/harness` with its sandboxed home; emit `session_shutdown` before disposing a session.
Each extension README documents its keys, env variables, flags, defaults, valid ranges, trust, and invalid-value behavior.

## Enforcement

`scripts/extensions.mjs` rejects direct `getGlobalSettings`, `getProjectSettings`, `registerFlag`, and `getFlag` calls outside generated `src/pi-ext-*.ts` files.
`SettingsManager.create` remains allowed for building child agent sessions.