pi-mono/packages/coding-agent/schemas/settings.schema.json
Petr Houška 8bc94b19ff
feat(coding-agent): add PI_SELF_UPDATE_COMMAND and selfUpdateCommand for pi update (#11629)
Refs #7756, #5607.

Problem: when a wrapper or another package manager provides Pi (mise, a
`PNPM_HOME` wrapper such as nub, Nix), `pi update` cannot update it and
prints "pi cannot self-update this installation." Users with several
machines have to remember which tool updates Pi on which machine.

Example:

```sh
export PI_SELF_UPDATE_COMMAND="mise use -g npm:{packageName}@{version}"
pi update
# Updating pi with mise use -g npm:@earendil-works/pi-coding-agent@1.4.0...
# Updated pi from 1.3.0 to 1.4.0
```

or in `~/.pi/agent/settings.json`:

```json
{ "selfUpdateCommand": ["nix", "profile", "upgrade", "pi"] }
```

Behavior:

- `pi update` still checks pi.dev first. The command runs only when a
newer release exists, or with `--force`, and the release note prints
first.
- Placeholders come from that release: `{packageName}`, `{version}`,
`{installSpec}` (`<packageName>@<version>`). A package rename announced
by pi.dev flows into them.
- `PI_SELF_UPDATE_COMMAND` takes precedence and is split on whitespace
(no quoting). The setting is argv, for arguments with spaces, and is
read only from global settings, so a project's `.pi/settings.json`
cannot inject a command.
- The configured command replaces Pi's own update logic for every
install method, including installer-managed installs: it is explicit
user intent.
- Exit code 0 does not prove an update. Pi reports "Updated pi from X to
Y" when its own `package.json` now shows Y (in-place updates) or when
`pi --version` from PATH reports Y (wrappers such as mise or Nix that
install each version into a new directory). Otherwise it warns: ``Ran
<cmd>, but `pi --version` reports X, not Y.``
- "cannot self-update this installation" now mentions the env var and
setting.

Docs: `docs/cli.md` (new "Custom update command" section),
`docs/settings.md`, `docs/environment-variables.md`, `--help` env list,
regenerated `schemas/settings.schema.json`.

Tests: unit tests for argv resolution and placeholders in
`test/config.test.ts`; CLI tests in `test/package-command-paths.test.ts`
for env precedence over the setting and the managed install, global-only
setting gated by the version check, and command failure. `npm run check`
passes.

Manual end-to-end check with real mise 2026.10.23 in a sandboxed HOME,
running this branch as the mise-installed 1.3.0 (`PI_PACKAGE_DIR`
pointing at mise's install):

| Scenario | Result |
|---|---|
| `mise use -g npm:{packageName}@{version}` | mise installs 1.4.0;
`Updated pi from 1.3.0 to 1.4.0` |
| Already 1.4.0 | `pi is already up to date (v1.4.0)`, mise not run |
| In-place `npm install -g --prefix <dir> {installSpec}` via the setting
| `Updated pi from 1.3.0 to 1.4.0` |
| Command `true` (exit 0, installs nothing) | ``Ran true, but `pi
--version` reports 1.3.0, not 1.4.0.`` |

This PR is AI-generated.
2026-10-30 22:30:39 +01:00

800 lines
20 KiB
JSON

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://pi.dev/schemas/settings.schema.json",
"$comment": "This file is generated from TypeScript source. Do not edit it manually; run npm run generate:schemas.",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"description": "JSON Schema reference."
},
"lastChangelogVersion": {
"type": "string"
},
"extensionChangelogVersions": {
"type": "object",
"patternProperties": {
"^.*$": {
"type": "string"
}
},
"description": "Last seen changelog version per extension package."
},
"defaultProvider": {
"type": "string"
},
"defaultModel": {
"type": "string"
},
"defaultThinkingLevel": {
"$ref": "#/$defs/ModelThinkingLevel"
},
"modelThinkingLevels": {
"type": "object",
"patternProperties": {
"^.*$": {
"$ref": "#/$defs/ModelThinkingLevel"
}
},
"description": "Per-model default thinking level overrides keyed by \"provider/modelId\"."
},
"transport": {
"anyOf": [
{
"type": "string",
"const": "auto"
},
{
"type": "string",
"const": "sse"
},
{
"type": "string",
"const": "websocket"
},
{
"type": "string",
"const": "websocket-cached"
}
],
"default": "auto"
},
"sessionShareDestination": {
"anyOf": [
{
"type": "string",
"const": "auto"
},
{
"type": "string",
"const": "gist"
},
{
"type": "string",
"const": "radius"
}
],
"description": "Destination used by /share. \"auto\" uses Radius when authenticated and otherwise creates a GitHub gist.",
"default": "auto"
},
"steeringMode": {
"anyOf": [
{
"type": "string",
"const": "all"
},
{
"type": "string",
"const": "one-at-a-time"
}
],
"default": "one-at-a-time"
},
"followUpMode": {
"anyOf": [
{
"type": "string",
"const": "all"
},
{
"type": "string",
"const": "one-at-a-time"
}
],
"default": "one-at-a-time"
},
"theme": {
"type": "string"
},
"compaction": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": true
},
"reserveTokens": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"default": 16384
},
"keepRecentTokens": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"default": 20000
},
"modelOverrides": {
"type": "object",
"patternProperties": {
"^.*$": {
"type": "object",
"properties": {
"reserveTokens": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"keepRecentTokens": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
}
}
},
"description": "Per-model overrides keyed by exact \"provider/modelId\" strings."
}
}
},
"branchSummary": {
"type": "object",
"properties": {
"reserveTokens": {
"type": "number",
"description": "Tokens reserved for the prompt and LLM response.",
"default": 16384
},
"skipPrompt": {
"type": "boolean",
"description": "When true, skips the \"Summarize branch?\" prompt and defaults to no summary.",
"default": false
}
}
},
"retry": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": true
},
"maxRetries": {
"type": "number",
"default": 3
},
"baseDelayMs": {
"type": "number",
"description": "Exponential backoff base delay in milliseconds: 2s, 4s, 8s.",
"default": 2000
},
"maxAgentDelayMs": {
"type": "number",
"default": 60000
},
"provider": {
"type": "object",
"properties": {
"timeoutMs": {
"type": "number",
"description": "SDK or provider request timeout in milliseconds."
},
"maxRetries": {
"type": "number",
"description": "SDK or provider retry attempts."
},
"maxRetryDelayMs": {
"type": "number",
"description": "Maximum server-requested delay before failing.",
"default": 60000
}
}
},
"maxDelayMs": {
"type": "number",
"description": "Legacy retry delay setting. Use provider.maxRetryDelayMs instead.",
"deprecated": true
}
}
},
"hideThinkingBlock": {
"type": "boolean",
"default": false
},
"showCacheMissNotices": {
"type": "boolean",
"description": "Show cache cost and provider recovery notices.",
"default": false
},
"externalEditor": {
"type": "string",
"description": "Command for Ctrl+G external editor; takes precedence over VISUAL and EDITOR."
},
"shellPath": {
"type": "string",
"description": "Custom shell path, for example for Cygwin on Windows, with support for leading ~ expansion."
},
"quietStartup": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string",
"const": "header"
}
],
"description": "When true, hide all startup output. When \"header\", keep only the startup header.",
"default": false
},
"defaultProjectTrust": {
"anyOf": [
{
"type": "string",
"const": "ask"
},
{
"type": "string",
"const": "always"
},
{
"type": "string",
"const": "never"
}
],
"description": "Global setting only.",
"default": "ask"
},
"shellCommandPrefix": {
"type": "string",
"description": "Prefix prepended to every bash command, for example to enable shell aliases."
},
"npmCommand": {
"type": "array",
"items": {
"type": "string"
},
"description": "Command used for npm package lookup and installation, in argv form such as [\"mise\", \"exec\", \"node@20\", \"--\", \"npm\"]."
},
"selfUpdateCommand": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"description": "Command `pi update` runs to update Pi itself, in argv form such as [\"mise\", \"use\", \"-g\", \"npm:{packageName}@{version}\"]. Placeholders: {packageName}, {version}, {installSpec}. PI_SELF_UPDATE_COMMAND takes precedence. Global setting only."
},
"collapseChangelog": {
"type": "boolean",
"description": "Show the condensed changelog after update; use /changelog for the full changelog.",
"default": false
},
"enableInstallTelemetry": {
"type": "boolean",
"description": "Send an anonymous version and update ping after changelog-detected updates.",
"default": true
},
"enableAnalytics": {
"type": "boolean",
"description": "Opt in to analytics data sharing.",
"default": false
},
"trackingId": {
"type": "string",
"description": "Analytics tracking identifier, generated when analytics is enabled."
},
"packages": {
"type": "array",
"items": {
"anyOf": [
{
"type": "string",
"description": "Load all resources from the package."
},
{
"type": "object",
"required": [
"source"
],
"properties": {
"source": {
"type": "string"
},
"autoload": {
"type": "boolean",
"description": "When false, start empty and only apply explicit resource patterns."
},
"extensions": {
"type": "array",
"items": {
"type": "string"
}
},
"skills": {
"type": "array",
"items": {
"type": "string"
}
},
"prompts": {
"type": "array",
"items": {
"type": "string"
}
},
"themes": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
],
"description": "Package source for npm or git packages. Use the string form to load all resources or the object form to filter resources."
},
"description": "npm or git package sources, as strings or objects with resource filtering."
},
"extensions": {
"type": "array",
"items": {
"type": "string"
},
"description": "Local extension file paths or directories."
},
"deviceId": {
"type": "string",
"description": "Stable installation UUID, generated when authentication first needs it. Global only."
},
"skills": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "object",
"properties": {
"enableSkillCommands": {
"type": "boolean"
},
"customDirectories": {
"type": "array",
"items": {
"type": "string"
}
}
},
"deprecated": true
}
],
"description": "Local skill file paths or directories."
},
"prompts": {
"type": "array",
"items": {
"type": "string"
},
"description": "Local prompt template file paths or directories."
},
"themes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Local theme file paths or directories."
},
"enableSkillCommands": {
"type": "boolean",
"description": "Register skills as /skill:name commands.",
"default": true
},
"terminal": {
"type": "object",
"properties": {
"showImages": {
"type": "boolean",
"description": "Show images when the terminal supports them.",
"default": true
},
"imageWidthCells": {
"type": "number",
"description": "Preferred inline image width in terminal cells.",
"default": 60
},
"clearOnShrink": {
"type": "boolean",
"description": "Clear empty rows when content shrinks.",
"default": false
},
"showTerminalProgress": {
"type": "boolean",
"description": "Show OSC 9;4 terminal progress indicators.",
"default": false
},
"hyperlinks": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string",
"const": "auto"
}
]
},
"images": {
"anyOf": [
{
"type": "string",
"const": "kitty"
},
{
"type": "string",
"const": "iterm2"
},
{
"type": "string",
"const": "auto"
},
{
"type": "boolean",
"const": false
}
]
},
"trueColor": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string",
"const": "auto"
}
]
}
}
},
"images": {
"type": "object",
"properties": {
"autoResize": {
"type": "boolean",
"description": "Resize images to 2000x2000 maximum for better model compatibility.",
"default": true
},
"blockImages": {
"type": "boolean",
"description": "When true, prevents all images from being sent to LLM providers.",
"default": false
}
}
},
"enabledModels": {
"type": "array",
"items": {
"type": "string"
},
"description": "Model patterns for cycling, in the same format as the --models CLI flag."
},
"defaultTools": {
"type": "array",
"items": {
"type": "string"
},
"description": "Initial tool selection. Plain names replace the inherited selection; +name and -name entries add or remove tools."
},
"doubleEscapeAction": {
"anyOf": [
{
"type": "string",
"const": "fork"
},
{
"type": "string",
"const": "tree"
},
{
"type": "string",
"const": "none"
}
],
"description": "Action for double-escape with an empty editor.",
"default": "tree"
},
"treeFilterMode": {
"anyOf": [
{
"type": "string",
"const": "default"
},
{
"type": "string",
"const": "no-tools"
},
{
"type": "string",
"const": "user-only"
},
{
"type": "string",
"const": "labeled-only"
},
{
"type": "string",
"const": "all"
}
],
"description": "Default filter when opening /tree.",
"default": "default"
},
"thinkingBudgets": {
"type": "object",
"properties": {
"minimal": {
"type": "number"
},
"low": {
"type": "number"
},
"medium": {
"type": "number"
},
"high": {
"type": "number"
}
},
"description": "Custom token budgets for thinking levels."
},
"editorPaddingX": {
"type": "number",
"description": "Horizontal padding for the input editor.",
"default": 0
},
"outputPad": {
"anyOf": [
{
"type": "number",
"const": 0
},
{
"type": "number",
"const": 1
}
],
"description": "Horizontal padding for transcript content.",
"default": 1
},
"autocompleteMaxVisible": {
"type": "number",
"description": "Maximum visible items in the autocomplete dropdown.",
"default": 5
},
"showHardwareCursor": {
"type": "boolean",
"description": "Show the terminal cursor while still positioning it for IME."
},
"markdown": {
"type": "object",
"properties": {
"codeBlockIndent": {
"type": "string",
"default": " "
},
"mermaid": {
"anyOf": [
{
"type": "string",
"const": "off"
},
{
"type": "string",
"const": "final"
},
{
"type": "string",
"const": "streaming"
}
],
"default": "streaming"
}
}
},
"warnings": {
"type": "object",
"properties": {
"anthropicExtraUsage": {
"type": "boolean",
"default": true
}
}
},
"codemode": {
"type": "object",
"properties": {
"mode": {
"anyOf": [
{
"type": "string",
"const": "on"
},
{
"type": "string",
"const": "only"
}
],
"description": "How codemode presents tools. \"on\" keeps direct tools declared and annotates tools callable from scripts; codemode lists only tools without direct exposure. \"only\" lists every script-callable tool in codemode and does not declare active direct tools.",
"default": "on"
},
"inlineBudget": {
"type": "number",
"minimum": 0,
"description": "Estimated tokens available for inline codemode tool declarations.",
"default": 3000
}
}
},
"sessionDir": {
"type": "string",
"description": "Custom session storage directory, in the same format as the --session-dir CLI flag."
},
"httpProxy": {
"type": "string",
"description": "Proxy URL applied as HTTP_PROXY and HTTPS_PROXY for Pi-managed HTTP clients."
},
"httpIdleTimeoutMs": {
"anyOf": [
{
"type": "number",
"minimum": 0
},
{
"type": "string",
"const": "disabled"
}
],
"description": "HTTP header or body idle timeout in milliseconds; 0 or \"disabled\" disables it."
},
"cacheWarming": {
"anyOf": [
{
"type": "string",
"const": "off"
},
{
"type": "string",
"const": "streaming"
},
{
"type": "string",
"const": "idle"
}
],
"description": "Cache-warming profile. \"idle\" also warms between agent runs. Global only because each refresh costs money.",
"default": "streaming"
},
"websocketConnectTimeoutMs": {
"anyOf": [
{
"type": "number",
"minimum": 0
},
{
"type": "string",
"const": "disabled"
}
],
"description": "WebSocket connect or open handshake timeout in milliseconds; 0 or \"disabled\" disables it."
},
"tuiMode": {
"anyOf": [
{
"type": "string",
"const": "regular"
},
{
"type": "string",
"const": "fullscreen"
}
],
"default": "fullscreen"
},
"fullscreenExitOutput": {
"anyOf": [
{
"type": "string",
"const": "transcript"
},
{
"type": "string",
"const": "resume-hint"
}
],
"description": "No effect in regular TUI mode.",
"default": "transcript"
},
"fullscreenScrollbar": {
"anyOf": [
{
"type": "string",
"const": "auto"
},
{
"type": "string",
"const": "always"
},
{
"type": "string",
"const": "hidden"
}
],
"description": "No effect in regular TUI mode.",
"default": "auto"
},
"fullscreenCopyOnSelect": {
"type": "boolean",
"description": "No effect in regular TUI mode.",
"default": true
},
"fullscreenWheelScrollLines": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"const": "auto"
}
],
"description": "Lines scrolled per wheel event in fullscreen mode; numeric values are clamped from 1 to 100.",
"default": "auto"
},
"queueMode": {
"anyOf": [
{
"type": "string",
"const": "all"
},
{
"type": "string",
"const": "one-at-a-time"
}
],
"description": "Legacy setting migrated to steeringMode.",
"deprecated": true
},
"websockets": {
"type": "boolean",
"description": "Legacy setting migrated to transport.",
"deprecated": true
}
},
"additionalProperties": true,
"$defs": {
"ModelThinkingLevel": {
"enum": [
"off",
"minimal",
"low",
"medium",
"high",
"xhigh",
"max"
]
}
}
}