chore: autogenerate settings documentation (#12451)

This commit is contained in:
cornmander
2025-11-02 20:42:49 -05:00
committed by GitHub
parent 462c7d3502
commit 5062fadf87
12 changed files with 2546 additions and 119 deletions

View File

@@ -90,6 +90,12 @@ jobs:
- name: 'Run Prettier'
run: 'node scripts/lint.js --prettier'
- name: 'Build docs prerequisites'
run: 'npm run predocs:settings'
- name: 'Verify settings docs'
run: 'npm run docs:settings -- --check'
- name: 'Run sensitive keyword linter'
run: 'node scripts/lint.js --sensitive-keywords'

View File

@@ -36,6 +36,11 @@ overridden by higher numbers):
Gemini CLI uses JSON settings files for persistent configuration. There are four
locations for these files:
> **Tip:** JSON-aware editors can use autocomplete and validation by pointing to
> the generated schema at `schemas/settings.schema.json` in this repository.
> When working outside the repo, reference the hosted schema at
> `https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json`.
- **System defaults file:**
- **Location:** `/etc/gemini-cli/system-defaults.json` (Linux),
`C:\ProgramData\gemini-cli\system-defaults.json` (Windows) or
@@ -89,6 +94,8 @@ contain other project-specific files related to Gemini CLI's operation, such as:
Settings are organized into categories. All settings should be placed within
their corresponding top-level category object in your `settings.json` file.
<!-- SETTINGS-AUTOGEN:START -->
#### `general`
- **`general.preferredEditor`** (string):
@@ -96,11 +103,11 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `undefined`
- **`general.vimMode`** (boolean):
- **Description:** Enable Vim keybindings.
- **Description:** Enable Vim keybindings
- **Default:** `false`
- **`general.disableAutoUpdate`** (boolean):
- **Description:** Disable automatic updates.
- **Description:** Disable automatic updates
- **Default:** `false`
- **`general.disableUpdateNag`** (boolean):
@@ -108,34 +115,55 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `false`
- **`general.checkpointing.enabled`** (boolean):
- **Description:** Enable session checkpointing for recovery.
- **Description:** Enable session checkpointing for recovery
- **Default:** `false`
- **Requires restart:** Yes
- **`general.enablePromptCompletion`** (boolean):
- **Description:** Enable AI-powered prompt completion suggestions while
typing.
- **Default:** `false`
- **Requires restart:** Yes
- **`general.retryFetchErrors`** (boolean):
- **Description:** Retry on "exception TypeError: fetch failed sending
request" errors.
- **Default:** `false`
- **`general.debugKeystrokeLogging`** (boolean):
- **Description:** Enable debug logging of keystrokes to the console.
- **Default:** `false`
- **`general.sessionRetention.enabled`** (boolean):
- **Description:** Enable automatic session cleanup.
- **Description:** Enable automatic session cleanup
- **Default:** `false`
- **`general.sessionRetention.maxAge`** (string):
- **Description:** Maximum age of sessions to keep (e.g., "30d", "7d", "24h",
"1w")
- **Default:** `undefined`
- **`general.sessionRetention.maxCount`** (number):
- **Description:** Alternative: Maximum number of sessions to keep (most
recent)
- **Default:** `undefined`
- **`general.sessionRetention.minRetention`** (string):
- **Description:** Minimum retention period (safety limit, defaults to "1d")
- **Default:** `"1d"`
#### `output`
- **`output.format`** (string):
- **`output.format`** (enum):
- **Description:** The format of the CLI output.
- **Default:** `"text"`
- **Values:** `"text"`, `"json"`, `"stream-json"`
- **Values:** `"text"`, `"json"`
#### `ui`
- **`ui.theme`** (string):
- **Description:** The color theme for the UI. See [Themes](../cli/themes.md)
for available options.
- **Description:** The color theme for the UI. See the CLI themes guide for
available options.
- **Default:** `undefined`
- **`ui.customThemes`** (object):
@@ -143,20 +171,21 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `{}`
- **`ui.hideWindowTitle`** (boolean):
- **Description:** Hide the window title bar.
- **Description:** Hide the window title bar
- **Default:** `false`
- **Requires restart:** Yes
- **`ui.showStatusInTitle`** (boolean):
- **Description:** Show Gemini CLI status and thoughts in the terminal window
title.
title
- **Default:** `false`
- **`ui.hideTips`** (boolean):
- **Description:** Hide helpful tips in the UI.
- **Description:** Hide helpful tips in the UI
- **Default:** `false`
- **`ui.hideBanner`** (boolean):
- **Description:** Hide the application banner.
- **Description:** Hide the application banner
- **Default:** `false`
- **`ui.hideContextSummary`** (boolean):
@@ -164,10 +193,6 @@ their corresponding top-level category object in your `settings.json` file.
input.
- **Default:** `false`
- **`ui.hideFooter`** (boolean):
- **Description:** Hide the footer from the UI.
- **Default:** `false`
- **`ui.footer.hideCWD`** (boolean):
- **Description:** Hide the current working directory path in the footer.
- **Default:** `false`
@@ -180,8 +205,16 @@ their corresponding top-level category object in your `settings.json` file.
- **Description:** Hide the model name and context usage in the footer.
- **Default:** `false`
- **`ui.footer.hideContextPercentage`** (boolean):
- **Description:** Hides the context window remaining percentage.
- **Default:** `true`
- **`ui.hideFooter`** (boolean):
- **Description:** Hide the footer from the UI
- **Default:** `false`
- **`ui.showMemoryUsage`** (boolean):
- **Description:** Display memory usage information in the UI.
- **Description:** Display memory usage information in the UI
- **Default:** `false`
- **`ui.showLineNumbers`** (boolean):
@@ -190,32 +223,34 @@ their corresponding top-level category object in your `settings.json` file.
- **`ui.showCitations`** (boolean):
- **Description:** Show citations for generated text in the chat.
- **Default:** `true`
- **Default:** `false`
- **`ui.useFullWidth`** (boolean):
- **Description:** Use the entire width of the terminal for output.
- **Default:** `false`
- **`ui.customWittyPhrases`** (array):
- **Description:** Custom witty phrases to display during loading. When
provided, the CLI cycles through these instead of the defaults.
- **Default:** `[]`
- **`ui.accessibility.disableLoadingPhrases`** (boolean):
- **Description:** Disable loading phrases for accessibility.
- **Description:** Disable loading phrases for accessibility
- **Default:** `false`
- **Requires restart:** Yes
- **`ui.accessibility.screenReader`** (boolean):
- **Description:** Show plaintext interactive view that is more screen reader
friendly.
- **Description:** Render output in plain-text to be more screen reader
accessible
- **Default:** `false`
- **`ui.customWittyPhrases`** (array of strings):
- **Description:** A list of custom phrases to display during loading states.
When provided, the CLI will cycle through these phrases instead of the
default ones.
- **Default:** `[]`
- **Requires restart:** Yes
#### `ide`
- **`ide.enabled`** (boolean):
- **Description:** Enable IDE integration mode.
- **Description:** Enable IDE integration mode
- **Default:** `false`
- **Requires restart:** Yes
- **`ide.hasSeenNudge`** (boolean):
- **Description:** Whether the user has seen the IDE integration nudge.
@@ -224,8 +259,9 @@ their corresponding top-level category object in your `settings.json` file.
#### `privacy`
- **`privacy.usageStatisticsEnabled`** (boolean):
- **Description:** Enable collection of usage statistics.
- **Description:** Enable collection of usage statistics
- **Default:** `true`
- **Requires restart:** Yes
#### `model`
@@ -239,32 +275,26 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `-1`
- **`model.summarizeToolOutput`** (object):
- **Description:** Enables or disables the summarization of tool output. You
can specify the token budget for the summarization using the `tokenBudget`
setting. Note: Currently only the `run_shell_command` tool is supported. For
example `{"run_shell_command": {"tokenBudget": 2000}}`
- **Description:** Enables or disables summarization of tool output. Configure
per-tool token budgets (for example {"run_shell_command": {"tokenBudget":
2000}}). Currently only the run_shell_command tool supports summarization.
- **Default:** `undefined`
- **`model.compressionThreshold`** (number):
- **Description:** Sets the threshold for chat history compression as a
fraction of the model's total token limit. This is a value between 0 and 1
that applies to both automatic compression and the manual `/compress`
command. For example, a value of `0.6` will trigger compression when the
chat history exceeds 60% of the token limit.
- **Description:** The fraction of context usage at which to trigger context
compression (e.g. 0.2, 0.3).
- **Default:** `0.2`
- **Requires restart:** Yes
- **`model.skipNextSpeakerCheck`** (boolean):
- **Description:** Skip the next speaker check.
- **Default:** `false`
- **`model.enableShellOutputEfficiency`** (boolean):
- **Description:** Optimizes shell tool commands for token efficiency.
- **Default:** `true`
#### `context`
- **`context.fileName`** (string or array of strings):
- **Description:** The name of the context file(s).
- **`context.fileName`** (string | string[]):
- **Description:** The name of the context file or files to load into memory.
Accepts either a single string or an array of strings.
- **Default:** `undefined`
- **`context.importFormat`** (string):
@@ -280,42 +310,51 @@ their corresponding top-level category object in your `settings.json` file.
Missing directories will be skipped with a warning.
- **Default:** `[]`
- **`context.loadFromIncludeDirectories`** (boolean):
- **Description:** Controls the behavior of the `/memory refresh` command. If
set to `true`, `GEMINI.md` files should be loaded from all directories that
are added. If set to `false`, `GEMINI.md` should only be loaded from the
current directory.
- **`context.loadMemoryFromIncludeDirectories`** (boolean):
- **Description:** Controls how /memory refresh loads GEMINI.md files. When
true, include directories are scanned; when false, only the current
directory is used.
- **Default:** `false`
- **`context.fileFiltering.respectGitIgnore`** (boolean):
- **Description:** Respect .gitignore files when searching.
- **Description:** Respect .gitignore files when searching
- **Default:** `true`
- **Requires restart:** Yes
- **`context.fileFiltering.respectGeminiIgnore`** (boolean):
- **Description:** Respect .geminiignore files when searching.
- **Description:** Respect .geminiignore files when searching
- **Default:** `true`
- **Requires restart:** Yes
- **`context.fileFiltering.enableRecursiveFileSearch`** (boolean):
- **Description:** Whether to enable searching recursively for filenames under
the current tree when completing `@` prefixes in the prompt.
- **Description:** Enable recursive file search functionality when completing
@ references in the prompt.
- **Default:** `true`
- **Requires restart:** Yes
- **`context.fileFiltering.disableFuzzySearch`** (boolean):
- **Description:** Disable fuzzy search when searching for files.
- **Default:** `false`
- **Requires restart:** Yes
#### `tools`
- **`tools.sandbox`** (boolean or string):
- **Description:** Sandbox execution environment (can be a boolean or a path
string).
- **`tools.sandbox`** (boolean | string):
- **Description:** Sandbox execution environment. Set to a boolean to enable
or disable the sandbox, or provide a string path to a sandbox profile.
- **Default:** `undefined`
- **Requires restart:** Yes
- **`tools.shell.enableInteractiveShell`** (boolean):
- **Description:** Enables interactive terminal for running shell commands. If
an interactive session cannot be started, it will fall back to a standard
shell.
- **Description:** Use node-pty for an interactive shell experience. Fallback
to child_process still applies.
- **Default:** `true`
- **Requires restart:** Yes
- **`tools.shell.pager`** (string):
- **Description:** The pager command to use for shell output. Defaults to
`cat`.
- **Default:** `"cat"`
- **`tools.shell.showColor`** (boolean):
- **Description:** Show color in shell output.
@@ -326,42 +365,37 @@ their corresponding top-level category object in your `settings.json` file.
considered safe (e.g., read-only operations).
- **Default:** `false`
- **`tools.core`** (array of strings):
- **Description:** This can be used to restrict the set of built-in tools
[with an allowlist](../cli/enterprise.md#restricting-tool-access). See
[Built-in Tools](../core/tools-api.md#built-in-tools) for a list of core
tools. The match semantics are the same as `tools.allowed`.
- **`tools.core`** (array):
- **Description:** Restrict the set of built-in tools with an allowlist. Match
semantics mirror tools.allowed; see the built-in tools documentation for
available names.
- **Default:** `undefined`
- **Requires restart:** Yes
- **`tools.exclude`** (array of strings):
- **`tools.allowed`** (array):
- **Description:** Tool names that bypass the confirmation dialog. Useful for
trusted commands (for example ["run_shell_command(git)",
"run_shell_command(npm test)"]). See shell tool command restrictions for
matching details.
- **Default:** `undefined`
- **Requires restart:** Yes
- **`tools.exclude`** (array):
- **Description:** Tool names to exclude from discovery.
- **Default:** `undefined`
- **`tools.allowed`** (array of strings):
- **Description:** A list of tool names that will bypass the confirmation
dialog. This is useful for tools that you trust and use frequently. For
example, `["run_shell_command(git)", "run_shell_command(npm test)"]` will
skip the confirmation dialog to run any `git` and `npm test` commands. See
[Shell Tool command restrictions](../tools/shell.md#command-restrictions)
for details on prefix matching, command chaining, etc.
- **Default:** `undefined`
- **Requires restart:** Yes
- **`tools.discoveryCommand`** (string):
- **Description:** Command to run for tool discovery.
- **Default:** `undefined`
- **Requires restart:** Yes
- **`tools.callCommand`** (string):
- **Description:** Defines a custom shell command for calling a specific tool
that was discovered using `tools.discoveryCommand`. The shell command must
meet the following criteria:
- It must take function `name` (exactly as in
[function declaration](https://ai.google.dev/gemini-api/docs/function-calling#function-declarations))
as the first command line argument.
- It must read function arguments as JSON on `stdin`, analogous to
[`functionCall.args`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#functioncall).
- It must return function output as JSON on `stdout`, analogous to
[`functionResponse.response.content`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#functionresponse).
- **Description:** Defines a custom shell command for invoking discovered
tools. The command must take the tool name as the first argument, read JSON
arguments from stdin, and emit JSON results on stdout.
- **Default:** `undefined`
- **Requires restart:** Yes
- **`tools.useRipgrep`** (boolean):
- **Description:** Use ripgrep for file content search instead of the fallback
@@ -371,36 +405,51 @@ their corresponding top-level category object in your `settings.json` file.
- **`tools.enableToolOutputTruncation`** (boolean):
- **Description:** Enable truncation of large tool outputs.
- **Default:** `true`
- **Requires restart:** Yes
- **`tools.truncateToolOutputThreshold`** (number):
- **Description:** Truncate tool output if it is larger than this many
characters. Set to -1 to disable.
- **Default:** `20000`
- **Default:** `4000000`
- **Requires restart:** Yes
- **`tools.truncateToolOutputLines`** (number):
- **Description:** The number of lines to keep when truncating tool output.
- **Default:** `1000`
- **Requires restart:** Yes
- **`tools.enableMessageBusIntegration`** (boolean):
- **Description:** Enable policy-based tool confirmation via message bus
integration. When enabled, tools will automatically respect policy engine
integration. When enabled, tools automatically respect policy engine
decisions (ALLOW/DENY/ASK_USER) without requiring individual tool
implementations.
- **Default:** `false`
- **Requires restart:** Yes
- **`tools.enableHooks`** (boolean):
- **Description:** Enable the hooks system for intercepting and customizing
Gemini CLI behavior. When enabled, hooks configured in settings will execute
at appropriate lifecycle events (BeforeTool, AfterTool, BeforeModel, etc.).
Requires MessageBus integration.
- **Default:** `false`
- **Requires restart:** Yes
#### `mcp`
- **`mcp.serverCommand`** (string):
- **Description:** Command to start an MCP server.
- **Default:** `undefined`
- **Requires restart:** Yes
- **`mcp.allowed`** (array of strings):
- **Description:** An allowlist of MCP servers to allow.
- **`mcp.allowed`** (array):
- **Description:** A list of MCP servers to allow.
- **Default:** `undefined`
- **Requires restart:** Yes
- **`mcp.excluded`** (array of strings):
- **Description:** A denylist of MCP servers to exclude.
- **`mcp.excluded`** (array):
- **Description:** A list of MCP servers to exclude.
- **Default:** `undefined`
- **Requires restart:** Yes
#### `useSmartEdit`
@@ -411,38 +460,50 @@ their corresponding top-level category object in your `settings.json` file.
#### `useWriteTodos`
- **`useWriteTodos`** (boolean):
- **Description:** Enable the write_todos tool.
- **Description:** Enable the write_todos_list tool.
- **Default:** `false`
#### `security`
- **`security.disableYoloMode`** (boolean):
- **Description:** Disable YOLO mode, even if enabled by a flag.
- **Default:** `false`
- **Requires restart:** Yes
- **`security.folderTrust.enabled`** (boolean):
- **Description:** Setting to track whether Folder trust is enabled.
- **Default:** `false`
- **Requires restart:** Yes
- **`security.auth.selectedType`** (string):
- **Description:** The currently selected authentication type.
- **Default:** `undefined`
- **Requires restart:** Yes
- **`security.auth.enforcedType`** (string):
- **Description:** The required auth type (useful for enterprises).
- **Description:** The required auth type. If this does not match the selected
auth type, the user will be prompted to re-authenticate.
- **Default:** `undefined`
- **Requires restart:** Yes
- **`security.auth.useExternal`** (boolean):
- **Description:** Whether to use an external authentication flow.
- **Default:** `undefined`
- **Requires restart:** Yes
#### `advanced`
- **`advanced.autoConfigureMemory`** (boolean):
- **Description:** Automatically configure Node.js memory limits.
- **Description:** Automatically configure Node.js memory limits
- **Default:** `false`
- **Requires restart:** Yes
- **`advanced.dnsResolutionOrder`** (string):
- **Description:** The DNS resolution order.
- **Default:** `undefined`
- **Requires restart:** Yes
- **`advanced.excludedEnvVars`** (array of strings):
- **`advanced.excludedEnvVars`** (array):
- **Description:** Environment variables to exclude from project context.
- **Default:** `["DEBUG","DEBUG_MODE"]`
@@ -452,10 +513,56 @@ their corresponding top-level category object in your `settings.json` file.
#### `experimental`
- **`experimental.extensionManagement`** (boolean):
- **Description:** Enable extension management features.
- **Default:** `true`
- **Requires restart:** Yes
- **`experimental.extensionReloading`** (boolean):
- **Description:** Enables extension loading/unloading within the CLI session.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.useModelRouter`** (boolean):
- **Description:** Enable model routing to route requests to the best model
based on complexity.
- **Default:** `true`
- **Requires restart:** Yes
- **`experimental.codebaseInvestigatorSettings.enabled`** (boolean):
- **Description:** Enable the Codebase Investigator agent.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.codebaseInvestigatorSettings.maxNumTurns`** (number):
- **Description:** Maximum number of turns for the Codebase Investigator
agent.
- **Default:** `15`
- **Requires restart:** Yes
- **`experimental.codebaseInvestigatorSettings.maxTimeMinutes`** (number):
- **Description:** Maximum time for the Codebase Investigator agent (in
minutes).
- **Default:** `5`
- **Requires restart:** Yes
- **`experimental.codebaseInvestigatorSettings.thinkingBudget`** (number):
- **Description:** The thinking budget for the Codebase Investigator agent.
- **Default:** `-1`
- **Requires restart:** Yes
- **`experimental.codebaseInvestigatorSettings.model`** (string):
- **Description:** The model to use for the Codebase Investigator agent.
- **Default:** `"gemini-2.5-pro"`
- **Requires restart:** Yes
#### `hooks`
- **`hooks`** (object):
- **Description:** Hook configurations for intercepting and customizing agent
behavior.
- **Default:** `{}`
<!-- SETTINGS-AUTOGEN:END -->
#### `mcpServers`

View File

@@ -27,6 +27,9 @@
"auth:docker": "gcloud auth configure-docker us-west1-docker.pkg.dev",
"auth": "npm run auth:npm && npm run auth:docker",
"generate": "node scripts/generate-git-commit-info.js",
"predocs:settings": "npm run build --workspace @google/gemini-cli-core",
"schema:settings": "tsx ./scripts/generate-settings-schema.ts",
"docs:settings": "tsx ./scripts/generate-settings-doc.ts",
"build": "node scripts/build.js",
"build-and-start": "npm run build && npm run start",
"build:vscode": "node scripts/build_vscode_companion.js",

View File

@@ -7,6 +7,8 @@
import { describe, it, expect } from 'vitest';
import {
getSettingsSchema,
SETTINGS_SCHEMA_DEFINITIONS,
type SettingCollectionDefinition,
type SettingDefinition,
type Settings,
type SettingsSchema,
@@ -335,4 +337,50 @@ describe('SettingsSchema', () => {
).toBe(true);
});
});
it('has JSON schema definitions for every referenced ref', () => {
const schema = getSettingsSchema();
const referenced = new Set<string>();
const visitDefinition = (definition: SettingDefinition) => {
if (definition.ref) {
referenced.add(definition.ref);
expect(SETTINGS_SCHEMA_DEFINITIONS).toHaveProperty(definition.ref);
}
if (definition.properties) {
Object.values(definition.properties).forEach(visitDefinition);
}
if (definition.items) {
visitCollection(definition.items);
}
if (definition.additionalProperties) {
visitCollection(definition.additionalProperties);
}
};
const visitCollection = (collection: SettingCollectionDefinition) => {
if (collection.ref) {
referenced.add(collection.ref);
expect(SETTINGS_SCHEMA_DEFINITIONS).toHaveProperty(collection.ref);
return;
}
if (collection.properties) {
Object.values(collection.properties).forEach(visitDefinition);
}
if (collection.type === 'array' && collection.properties) {
Object.values(collection.properties).forEach(visitDefinition);
}
};
Object.values(schema).forEach(visitDefinition);
// Ensure definitions map doesn't accumulate stale entries.
Object.keys(SETTINGS_SCHEMA_DEFINITIONS).forEach((key) => {
if (!referenced.has(key)) {
throw new Error(
`Definition "${key}" is exported but never referenced in the schema`,
);
}
});
});
});

View File

@@ -5,8 +5,8 @@
*/
// --------------------------------------------------------------------------
// IMPORTANT: When adding a new setting, especially one with `showInDialog: true`,
// please ensure it is also documented in `docs/get-started/configuration.md`.
// IMPORTANT: After adding or updating settings, run `npm run docs:settings`
// to regenerate the settings reference in `docs/get-started/configuration.md`.
// --------------------------------------------------------------------------
import type {
@@ -57,6 +57,30 @@ export interface SettingEnumOption {
label: string;
}
function oneLine(strings: TemplateStringsArray, ...values: unknown[]): string {
let result = '';
for (let i = 0; i < strings.length; i++) {
result += strings[i];
if (i < values.length) {
result += String(values[i]);
}
}
return result.replace(/\s+/g, ' ').trim();
}
export interface SettingCollectionDefinition {
type: SettingsType;
description?: string;
properties?: SettingsSchema;
/** Enum type options */
options?: readonly SettingEnumOption[];
/**
* Optional reference identifier for generators that emit a `$ref`.
* For example, a JSON schema generator can use this to point to a shared definition.
*/
ref?: string;
}
export enum MergeStrategy {
// Replace the old value with the new value. This is the default.
REPLACE = 'replace',
@@ -83,6 +107,18 @@ export interface SettingDefinition {
mergeStrategy?: MergeStrategy;
/** Enum type options */
options?: readonly SettingEnumOption[];
/**
* For collection types (e.g. arrays), describes the shape of each item.
*/
items?: SettingCollectionDefinition;
/**
* For map-like objects without explicit `properties`, describes the shape of the values.
*/
additionalProperties?: SettingCollectionDefinition;
/**
* Optional reference identifier for generators that emit a `$ref`.
*/
ref?: string;
}
export interface SettingsSchema {
@@ -108,6 +144,10 @@ const SETTINGS_SCHEMA = {
description: 'Configuration for MCP servers.',
showInDialog: false,
mergeStrategy: MergeStrategy.SHALLOW_MERGE,
additionalProperties: {
type: 'object',
ref: 'MCPServerConfig',
},
},
general: {
@@ -294,7 +334,8 @@ const SETTINGS_SCHEMA = {
category: 'UI',
requiresRestart: false,
default: undefined as string | undefined,
description: 'The color theme for the UI.',
description:
'The color theme for the UI. See the CLI themes guide for available options.',
showInDialog: false,
},
customThemes: {
@@ -305,6 +346,10 @@ const SETTINGS_SCHEMA = {
default: {} as Record<string, CustomTheme>,
description: 'Custom theme definitions.',
showInDialog: false,
additionalProperties: {
type: 'object',
ref: 'CustomTheme',
},
},
hideWindowTitle: {
type: 'boolean',
@@ -452,8 +497,12 @@ const SETTINGS_SCHEMA = {
category: 'UI',
requiresRestart: false,
default: [] as string[],
description: 'Custom witty phrases to display during loading.',
description: oneLine`
Custom witty phrases to display during loading.
When provided, the CLI cycles through these instead of the defaults.
`,
showInDialog: false,
items: { type: 'string' },
},
accessibility: {
type: 'object',
@@ -547,6 +596,7 @@ const SETTINGS_SCHEMA = {
default: undefined as TelemetrySettings | undefined,
description: 'Telemetry configuration.',
showInDialog: false,
ref: 'TelemetrySettings',
},
model: {
@@ -585,8 +635,18 @@ const SETTINGS_SCHEMA = {
default: undefined as
| Record<string, { tokenBudget?: number }>
| undefined,
description: 'Settings for summarizing tool output.',
description: oneLine`
Enables or disables summarization of tool output.
Configure per-tool token budgets (for example {"run_shell_command": {"tokenBudget": 2000}}).
Currently only the run_shell_command tool supports summarization.
`,
showInDialog: false,
additionalProperties: {
type: 'object',
description:
'Per-tool summarization settings with an optional tokenBudget.',
ref: 'SummarizeToolOutputSettings',
},
},
compressionThreshold: {
type: 'number',
@@ -620,12 +680,14 @@ const SETTINGS_SCHEMA = {
showInDialog: false,
properties: {
fileName: {
type: 'object',
type: 'string',
label: 'Context File Name',
category: 'Context',
requiresRestart: false,
default: undefined as string | string[] | undefined,
description: 'The name of the context file.',
ref: 'StringOrStringArray',
description:
'The name of the context file or files to load into memory. Accepts either a single string or an array of strings.',
showInDialog: false,
},
importFormat: {
@@ -652,9 +714,12 @@ const SETTINGS_SCHEMA = {
category: 'Context',
requiresRestart: false,
default: [] as string[],
description:
'Additional directories to include in the workspace context. Missing directories will be skipped with a warning.',
description: oneLine`
Additional directories to include in the workspace context.
Missing directories will be skipped with a warning.
`,
showInDialog: false,
items: { type: 'string' },
mergeStrategy: MergeStrategy.CONCAT,
},
loadMemoryFromIncludeDirectories: {
@@ -663,7 +728,10 @@ const SETTINGS_SCHEMA = {
category: 'Context',
requiresRestart: false,
default: false,
description: 'Whether to load memory files from include directories.',
description: oneLine`
Controls how /memory refresh loads GEMINI.md files.
When true, include directories are scanned; when false, only the current directory is used.
`,
showInDialog: true,
},
fileFiltering: {
@@ -699,7 +767,9 @@ const SETTINGS_SCHEMA = {
category: 'Context',
requiresRestart: true,
default: true,
description: 'Enable recursive file search functionality',
description: oneLine`
Enable recursive file search functionality when completing @ references in the prompt.
`,
showInDialog: true,
},
disableFuzzySearch: {
@@ -726,13 +796,16 @@ const SETTINGS_SCHEMA = {
showInDialog: false,
properties: {
sandbox: {
type: 'object',
type: 'string',
label: 'Sandbox',
category: 'Tools',
requiresRestart: true,
default: undefined as boolean | string | undefined,
description:
'Sandbox execution environment (can be a boolean or a path string).',
ref: 'BooleanOrString',
description: oneLine`
Sandbox execution environment.
Set to a boolean to enable or disable the sandbox, or provide a string path to a sandbox profile.
`,
showInDialog: false,
},
shell: {
@@ -750,8 +823,10 @@ const SETTINGS_SCHEMA = {
category: 'Tools',
requiresRestart: true,
default: true,
description:
'Use node-pty for an interactive shell experience. Fallback to child_process still applies.',
description: oneLine`
Use node-pty for an interactive shell experience.
Fallback to child_process still applies.
`,
showInDialog: true,
},
pager: {
@@ -781,8 +856,9 @@ const SETTINGS_SCHEMA = {
category: 'Tools',
requiresRestart: false,
default: false,
description:
'Automatically accept and execute tool calls that are considered safe (e.g., read-only operations).',
description: oneLine`
Automatically accept and execute tool calls that are considered safe (e.g., read-only operations).
`,
showInDialog: true,
},
core: {
@@ -791,8 +867,12 @@ const SETTINGS_SCHEMA = {
category: 'Tools',
requiresRestart: true,
default: undefined as string[] | undefined,
description: 'Paths to core tool definitions.',
description: oneLine`
Restrict the set of built-in tools with an allowlist.
Match semantics mirror tools.allowed; see the built-in tools documentation for available names.
`,
showInDialog: false,
items: { type: 'string' },
},
allowed: {
type: 'array',
@@ -800,9 +880,13 @@ const SETTINGS_SCHEMA = {
category: 'Advanced',
requiresRestart: true,
default: undefined as string[] | undefined,
description:
'A list of tool names that will bypass the confirmation dialog.',
description: oneLine`
Tool names that bypass the confirmation dialog.
Useful for trusted commands (for example ["run_shell_command(git)", "run_shell_command(npm test)"]).
See shell tool command restrictions for matching details.
`,
showInDialog: false,
items: { type: 'string' },
},
exclude: {
type: 'array',
@@ -812,6 +896,7 @@ const SETTINGS_SCHEMA = {
default: undefined as string[] | undefined,
description: 'Tool names to exclude from discovery.',
showInDialog: false,
items: { type: 'string' },
mergeStrategy: MergeStrategy.UNION,
},
discoveryCommand: {
@@ -829,7 +914,10 @@ const SETTINGS_SCHEMA = {
category: 'Tools',
requiresRestart: true,
default: undefined as string | undefined,
description: 'Command to run for tool calls.',
description: oneLine`
Defines a custom shell command for invoking discovered tools.
The command must take the tool name as the first argument, read JSON arguments from stdin, and emit JSON results on stdout.
`,
showInDialog: false,
},
useRipgrep: {
@@ -876,8 +964,10 @@ const SETTINGS_SCHEMA = {
category: 'Tools',
requiresRestart: true,
default: false,
description:
'Enable policy-based tool confirmation via message bus integration. When enabled, tools will automatically respect policy engine decisions (ALLOW/DENY/ASK_USER) without requiring individual tool implementations.',
description: oneLine`
Enable policy-based tool confirmation via message bus integration.
When enabled, tools automatically respect policy engine decisions (ALLOW/DENY/ASK_USER) without requiring individual tool implementations.
`,
showInDialog: true,
},
enableHooks: {
@@ -919,6 +1009,7 @@ const SETTINGS_SCHEMA = {
default: undefined as string[] | undefined,
description: 'A list of MCP servers to allow.',
showInDialog: false,
items: { type: 'string' },
},
excluded: {
type: 'array',
@@ -928,6 +1019,7 @@ const SETTINGS_SCHEMA = {
default: undefined as string[] | undefined,
description: 'A list of MCP servers to exclude.',
showInDialog: false,
items: { type: 'string' },
},
},
},
@@ -1064,6 +1156,7 @@ const SETTINGS_SCHEMA = {
default: ['DEBUG', 'DEBUG_MODE'] as string[],
description: 'Environment variables to exclude from project context.',
showInDialog: false,
items: { type: 'string' },
mergeStrategy: MergeStrategy.UNION,
},
bugCommand: {
@@ -1074,6 +1167,7 @@ const SETTINGS_SCHEMA = {
default: undefined as BugCommandSettings | undefined,
description: 'Configuration for the bug report command.',
showInDialog: false,
ref: 'BugCommandSettings',
},
},
},
@@ -1196,6 +1290,7 @@ const SETTINGS_SCHEMA = {
default: [] as string[],
description: 'List of disabled extensions.',
showInDialog: false,
items: { type: 'string' },
mergeStrategy: MergeStrategy.UNION,
},
workspacesWithMigrationNudge: {
@@ -1207,6 +1302,7 @@ const SETTINGS_SCHEMA = {
description:
'List of workspaces for which the migration nudge has been shown.',
showInDialog: false,
items: { type: 'string' },
mergeStrategy: MergeStrategy.UNION,
},
},
@@ -1227,6 +1323,274 @@ const SETTINGS_SCHEMA = {
export type SettingsSchemaType = typeof SETTINGS_SCHEMA;
export type SettingsJsonSchemaDefinition = Record<string, unknown>;
export const SETTINGS_SCHEMA_DEFINITIONS: Record<
string,
SettingsJsonSchemaDefinition
> = {
MCPServerConfig: {
type: 'object',
description:
'Definition of a Model Context Protocol (MCP) server configuration.',
additionalProperties: false,
properties: {
command: {
type: 'string',
description: 'Executable invoked for stdio transport.',
},
args: {
type: 'array',
description: 'Command-line arguments for the stdio transport command.',
items: { type: 'string' },
},
env: {
type: 'object',
description: 'Environment variables to set for the server process.',
additionalProperties: { type: 'string' },
},
cwd: {
type: 'string',
description: 'Working directory for the server process.',
},
url: {
type: 'string',
description: 'SSE transport URL.',
},
httpUrl: {
type: 'string',
description: 'Streaming HTTP transport URL.',
},
headers: {
type: 'object',
description: 'Additional HTTP headers sent to the server.',
additionalProperties: { type: 'string' },
},
tcp: {
type: 'string',
description: 'TCP address for websocket transport.',
},
timeout: {
type: 'number',
description: 'Timeout in milliseconds for MCP requests.',
},
trust: {
type: 'boolean',
description:
'Marks the server as trusted. Trusted servers may gain additional capabilities.',
},
description: {
type: 'string',
description: 'Human-readable description of the server.',
},
includeTools: {
type: 'array',
description:
'Subset of tools that should be enabled for this server. When omitted all tools are enabled.',
items: { type: 'string' },
},
excludeTools: {
type: 'array',
description:
'Tools that should be disabled for this server even if exposed.',
items: { type: 'string' },
},
extension: {
type: 'object',
description:
'Metadata describing the Gemini CLI extension that owns this MCP server.',
additionalProperties: { type: ['string', 'boolean', 'number'] },
},
oauth: {
type: 'object',
description: 'OAuth configuration for authenticating with the server.',
additionalProperties: true,
},
authProviderType: {
type: 'string',
description:
'Authentication provider used for acquiring credentials (for example `dynamic_discovery`).',
enum: [
'dynamic_discovery',
'google_credentials',
'service_account_impersonation',
],
},
targetAudience: {
type: 'string',
description:
'OAuth target audience (CLIENT_ID.apps.googleusercontent.com).',
},
targetServiceAccount: {
type: 'string',
description:
'Service account email to impersonate (name@project.iam.gserviceaccount.com).',
},
},
},
TelemetrySettings: {
type: 'object',
description: 'Telemetry configuration for Gemini CLI.',
additionalProperties: false,
properties: {
enabled: {
type: 'boolean',
description: 'Enables telemetry emission.',
},
target: {
type: 'string',
description:
'Telemetry destination (for example `stderr`, `stdout`, or `otlp`).',
},
otlpEndpoint: {
type: 'string',
description: 'Endpoint for OTLP exporters.',
},
otlpProtocol: {
type: 'string',
description: 'Protocol for OTLP exporters.',
enum: ['grpc', 'http'],
},
logPrompts: {
type: 'boolean',
description: 'Whether prompts are logged in telemetry payloads.',
},
outfile: {
type: 'string',
description: 'File path for writing telemetry output.',
},
useCollector: {
type: 'boolean',
description: 'Whether to forward telemetry to an OTLP collector.',
},
},
},
BugCommandSettings: {
type: 'object',
description: 'Configuration for the bug report helper command.',
additionalProperties: false,
properties: {
urlTemplate: {
type: 'string',
description:
'Template used to open a bug report URL. Variables in the template are populated at runtime.',
},
},
required: ['urlTemplate'],
},
SummarizeToolOutputSettings: {
type: 'object',
description:
'Controls summarization behavior for individual tools. All properties are optional.',
additionalProperties: false,
properties: {
tokenBudget: {
type: 'number',
description:
'Maximum number of tokens used when summarizing tool output.',
},
},
},
CustomTheme: {
type: 'object',
description:
'Custom theme definition used for styling Gemini CLI output. Colors are provided as hex strings or named ANSI colors.',
additionalProperties: false,
properties: {
type: {
type: 'string',
enum: ['custom'],
default: 'custom',
},
name: {
type: 'string',
description: 'Theme display name.',
},
text: {
type: 'object',
additionalProperties: false,
properties: {
primary: { type: 'string' },
secondary: { type: 'string' },
link: { type: 'string' },
accent: { type: 'string' },
},
},
background: {
type: 'object',
additionalProperties: false,
properties: {
primary: { type: 'string' },
diff: {
type: 'object',
additionalProperties: false,
properties: {
added: { type: 'string' },
removed: { type: 'string' },
},
},
},
},
border: {
type: 'object',
additionalProperties: false,
properties: {
default: { type: 'string' },
focused: { type: 'string' },
},
},
ui: {
type: 'object',
additionalProperties: false,
properties: {
comment: { type: 'string' },
symbol: { type: 'string' },
gradient: {
type: 'array',
items: { type: 'string' },
},
},
},
status: {
type: 'object',
additionalProperties: false,
properties: {
error: { type: 'string' },
success: { type: 'string' },
warning: { type: 'string' },
},
},
Background: { type: 'string' },
Foreground: { type: 'string' },
LightBlue: { type: 'string' },
AccentBlue: { type: 'string' },
AccentPurple: { type: 'string' },
AccentCyan: { type: 'string' },
AccentGreen: { type: 'string' },
AccentYellow: { type: 'string' },
AccentRed: { type: 'string' },
DiffAdded: { type: 'string' },
DiffRemoved: { type: 'string' },
Comment: { type: 'string' },
Gray: { type: 'string' },
DarkGray: { type: 'string' },
GradientColors: {
type: 'array',
items: { type: 'string' },
},
},
required: ['type', 'name'],
},
StringOrStringArray: {
description: 'Accepts either a single string or an array of strings.',
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
},
BooleanOrString: {
description: 'Accepts either a boolean flag or a string command name.',
anyOf: [{ type: 'boolean' }, { type: 'string' }],
},
};
export function getSettingsSchema(): SettingsSchemaType {
return SETTINGS_SCHEMA;
}

1229
schemas/settings.schema.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,201 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { readFile, writeFile } from 'node:fs/promises';
import { generateSettingsSchema } from './generate-settings-schema.js';
import {
escapeBackticks,
formatDefaultValue,
formatWithPrettier,
normalizeForCompare,
} from './utils/autogen.js';
import type {
SettingDefinition,
SettingsSchema,
SettingsSchemaType,
} from '../packages/cli/src/config/settingsSchema.js';
const START_MARKER = '<!-- SETTINGS-AUTOGEN:START -->';
const END_MARKER = '<!-- SETTINGS-AUTOGEN:END -->';
const MANUAL_TOP_LEVEL = new Set(['mcpServers', 'telemetry', 'extensions']);
interface DocEntry {
path: string;
type: string;
description: string;
defaultValue: string;
requiresRestart: boolean;
enumValues?: string[];
}
export async function main(argv = process.argv.slice(2)) {
const checkOnly = argv.includes('--check');
await generateSettingsSchema({ checkOnly });
const repoRoot = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'..',
);
const docPath = path.join(repoRoot, 'docs/get-started/configuration.md');
const { getSettingsSchema } = await loadSettingsSchemaModule();
const schema = getSettingsSchema();
const sections = collectEntries(schema);
const generatedBlock = renderSections(sections);
const doc = await readFile(docPath, 'utf8');
const startIndex = doc.indexOf(START_MARKER);
const endIndex = doc.indexOf(END_MARKER);
if (startIndex === -1 || endIndex === -1 || startIndex >= endIndex) {
throw new Error(
`Could not locate documentation markers (${START_MARKER}, ${END_MARKER}).`,
);
}
const before = doc.slice(0, startIndex + START_MARKER.length);
const after = doc.slice(endIndex);
const formattedDoc = await formatWithPrettier(
`${before}\n${generatedBlock}\n${after}`,
docPath,
);
if (normalizeForCompare(doc) === normalizeForCompare(formattedDoc)) {
if (!checkOnly) {
console.log('Settings documentation already up to date.');
}
return;
}
if (checkOnly) {
console.error(
'Settings documentation is out of date. Run `npm run docs:settings` to regenerate.',
);
process.exitCode = 1;
return;
}
await writeFile(docPath, formattedDoc);
console.log('Settings documentation regenerated.');
}
async function loadSettingsSchemaModule() {
const modulePath = '../packages/cli/src/config/settingsSchema.ts';
return import(modulePath);
}
function collectEntries(schema: SettingsSchemaType) {
const sections = new Map<string, DocEntry[]>();
const visit = (
current: SettingsSchema,
pathSegments: string[],
topLevel?: string,
) => {
for (const [key, definition] of Object.entries(current)) {
if (pathSegments.length === 0 && MANUAL_TOP_LEVEL.has(key)) {
continue;
}
const newPathSegments = [...pathSegments, key];
const sectionKey = topLevel ?? key;
const hasChildren =
definition.type === 'object' &&
definition.properties &&
Object.keys(definition.properties).length > 0;
if (!hasChildren) {
if (!sections.has(sectionKey)) {
sections.set(sectionKey, []);
}
sections.get(sectionKey)!.push({
path: newPathSegments.join('.'),
type: formatType(definition),
description: formatDescription(definition),
defaultValue: formatDefaultValue(definition.default, {
quoteStrings: true,
}),
requiresRestart: Boolean(definition.requiresRestart),
enumValues: definition.options?.map((option) =>
formatDefaultValue(option.value, { quoteStrings: true }),
),
});
}
if (hasChildren && definition.properties) {
visit(definition.properties, newPathSegments, sectionKey);
}
}
};
visit(schema, []);
return sections;
}
function formatDescription(definition: SettingDefinition) {
if (definition.description?.trim()) {
return definition.description.trim();
}
return 'Description not provided.';
}
function formatType(definition: SettingDefinition): string {
switch (definition.ref) {
case 'StringOrStringArray':
return 'string | string[]';
case 'BooleanOrString':
return 'boolean | string';
default:
return definition.type;
}
}
function renderSections(sections: Map<string, DocEntry[]>) {
const lines: string[] = [];
for (const [section, entries] of sections) {
if (entries.length === 0) {
continue;
}
lines.push(`#### \`${section}\``);
lines.push('');
for (const entry of entries) {
lines.push(`- **\`${entry.path}\`** (${entry.type}):`);
lines.push(` - **Description:** ${entry.description}`);
lines.push(` - **Default:** \`${escapeBackticks(entry.defaultValue)}\``);
if (entry.enumValues && entry.enumValues.length > 0) {
const values = entry.enumValues
.map((value) => `\`${escapeBackticks(value)}\``)
.join(', ');
lines.push(` - **Values:** ${values}`);
}
if (entry.requiresRestart) {
lines.push(' - **Requires restart:** Yes');
}
lines.push('');
}
}
return lines.join('\n').trimEnd();
}
if (process.argv[1]) {
const entryUrl = pathToFileURL(path.resolve(process.argv[1])).href;
if (entryUrl === import.meta.url) {
await main();
}
}

View File

@@ -0,0 +1,354 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { mkdir, readFile, writeFile } from 'node:fs/promises';
import {
getSettingsSchema,
type SettingCollectionDefinition,
type SettingDefinition,
type SettingsSchema,
type SettingsSchemaType,
SETTINGS_SCHEMA_DEFINITIONS,
type SettingsJsonSchemaDefinition,
} from '../packages/cli/src/config/settingsSchema.js';
import {
formatDefaultValue,
formatWithPrettier,
normalizeForCompare,
} from './utils/autogen.js';
const OUTPUT_RELATIVE_PATH = ['schemas', 'settings.schema.json'];
const SCHEMA_ID =
'https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json';
type JsonPrimitive = string | number | boolean | null;
type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
interface JsonSchema {
[key: string]: JsonValue | JsonSchema | JsonSchema[] | undefined;
$schema?: string;
$id?: string;
title?: string;
description?: string;
markdownDescription?: string;
type?: string | string[];
enum?: JsonPrimitive[];
default?: JsonValue;
properties?: Record<string, JsonSchema>;
items?: JsonSchema;
additionalProperties?: boolean | JsonSchema;
required?: string[];
$ref?: string;
anyOf?: JsonSchema[];
}
interface GenerateOptions {
checkOnly: boolean;
}
export async function generateSettingsSchema(
options: GenerateOptions,
): Promise<void> {
const repoRoot = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'..',
);
const outputPath = path.join(repoRoot, ...OUTPUT_RELATIVE_PATH);
await mkdir(path.dirname(outputPath), { recursive: true });
const schemaObject = buildSchemaObject(getSettingsSchema());
const formatted = await formatWithPrettier(
JSON.stringify(schemaObject, null, 2),
outputPath,
);
let existing: string | undefined;
try {
existing = await readFile(outputPath, 'utf8');
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
throw error;
}
}
if (
existing &&
normalizeForCompare(existing) === normalizeForCompare(formatted)
) {
if (!options.checkOnly) {
console.log('Settings JSON schema already up to date.');
}
return;
}
if (options.checkOnly) {
console.error(
'Settings JSON schema is out of date. Run `npm run schema:settings` to regenerate.',
);
process.exitCode = 1;
return;
}
await writeFile(outputPath, formatted);
console.log('Settings JSON schema regenerated.');
}
export async function main(argv = process.argv.slice(2)): Promise<void> {
const checkOnly = argv.includes('--check');
await generateSettingsSchema({ checkOnly });
}
function buildSchemaObject(schema: SettingsSchemaType): JsonSchema {
const defs = new Map<string, JsonSchema>(
Object.entries(SETTINGS_SCHEMA_DEFINITIONS as Record<string, JsonSchema>),
);
const root: JsonSchema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
$id: SCHEMA_ID,
title: 'Gemini CLI Settings',
description:
'Configuration file schema for Gemini CLI settings. This schema enables IDE completion for `settings.json`.',
type: 'object',
additionalProperties: false,
properties: {},
};
for (const [key, definition] of Object.entries(schema)) {
root.properties![key] = buildSettingSchema(definition, [key], defs);
}
if (defs.size > 0) {
root.$defs = Object.fromEntries(defs.entries());
}
return root;
}
function buildSettingSchema(
definition: SettingDefinition,
pathSegments: string[],
defs: Map<string, JsonSchema>,
): JsonSchema {
const base: JsonSchema = {
title: definition.label,
description: definition.description,
markdownDescription: buildMarkdownDescription(definition),
};
if (definition.default !== undefined) {
base.default = definition.default as JsonValue;
}
const schemaShape = definition.ref
? buildRefSchema(definition.ref, defs)
: buildSchemaForType(definition, pathSegments, defs);
return { ...base, ...schemaShape };
}
function buildCollectionSchema(
collection: SettingCollectionDefinition,
pathSegments: string[],
defs: Map<string, JsonSchema>,
): JsonSchema {
if (collection.ref) {
return buildRefSchema(collection.ref, defs);
}
return buildSchemaForType(collection, pathSegments, defs);
}
function buildSchemaForType(
source: SettingDefinition | SettingCollectionDefinition,
pathSegments: string[],
defs: Map<string, JsonSchema>,
): JsonSchema {
switch (source.type) {
case 'boolean':
case 'string':
case 'number':
return { type: source.type };
case 'enum':
return buildEnumSchema(source.options);
case 'array': {
const itemPath = [...pathSegments, '<items>'];
const items = isSettingDefinition(source)
? source.items
? buildCollectionSchema(source.items, itemPath, defs)
: {}
: source.properties
? buildInlineObjectSchema(source.properties, itemPath, defs)
: {};
return { type: 'array', items };
}
case 'object':
return isSettingDefinition(source)
? buildObjectDefinitionSchema(source, pathSegments, defs)
: buildObjectCollectionSchema(source, pathSegments, defs);
default:
return {};
}
}
function buildEnumSchema(
options:
| SettingDefinition['options']
| SettingCollectionDefinition['options'],
): JsonSchema {
const values = options?.map((option) => option.value) ?? [];
const inferred = inferTypeFromValues(values);
return {
type: inferred ?? undefined,
enum: values,
};
}
function buildObjectDefinitionSchema(
definition: SettingDefinition,
pathSegments: string[],
defs: Map<string, JsonSchema>,
): JsonSchema {
const properties = definition.properties
? buildObjectProperties(definition.properties, pathSegments, defs)
: undefined;
const schema: JsonSchema = {
type: 'object',
};
if (properties && Object.keys(properties).length > 0) {
schema.properties = properties;
}
if (definition.additionalProperties) {
schema.additionalProperties = buildCollectionSchema(
definition.additionalProperties,
[...pathSegments, '<additionalProperties>'],
defs,
);
} else if (!definition.properties) {
schema.additionalProperties = true;
} else {
schema.additionalProperties = false;
}
return schema;
}
function buildObjectCollectionSchema(
collection: SettingCollectionDefinition,
pathSegments: string[],
defs: Map<string, JsonSchema>,
): JsonSchema {
if (collection.properties) {
return buildInlineObjectSchema(collection.properties, pathSegments, defs);
}
return { type: 'object', additionalProperties: true };
}
function buildObjectProperties(
properties: SettingsSchema,
pathSegments: string[],
defs: Map<string, JsonSchema>,
): Record<string, JsonSchema> {
const result: Record<string, JsonSchema> = {};
for (const [childKey, childDefinition] of Object.entries(properties)) {
result[childKey] = buildSettingSchema(
childDefinition,
[...pathSegments, childKey],
defs,
);
}
return result;
}
function buildInlineObjectSchema(
properties: SettingsSchema,
pathSegments: string[],
defs: Map<string, JsonSchema>,
): JsonSchema {
const childSchemas = buildObjectProperties(properties, pathSegments, defs);
return {
type: 'object',
properties: childSchemas,
additionalProperties: false,
};
}
function buildRefSchema(
ref: string,
defs: Map<string, JsonSchema>,
): JsonSchema {
ensureDefinition(ref, defs);
return { $ref: `#/$defs/${ref}` };
}
function isSettingDefinition(
source: SettingDefinition | SettingCollectionDefinition,
): source is SettingDefinition {
return 'label' in source;
}
function buildMarkdownDescription(definition: SettingDefinition): string {
const lines: string[] = [];
if (definition.description?.trim()) {
lines.push(definition.description.trim());
} else {
lines.push('Description not provided.');
}
lines.push('');
lines.push(`- Category: \`${definition.category}\``);
lines.push(
`- Requires restart: \`${definition.requiresRestart ? 'yes' : 'no'}\``,
);
if (definition.default !== undefined) {
lines.push(`- Default: \`${formatDefaultValue(definition.default)}\``);
}
return lines.join('\n');
}
function inferTypeFromValues(
values: Array<string | number>,
): string | undefined {
if (values.length === 0) {
return undefined;
}
if (values.every((value) => typeof value === 'string')) {
return 'string';
}
if (values.every((value) => typeof value === 'number')) {
return 'number';
}
return undefined;
}
function ensureDefinition(ref: string, defs: Map<string, JsonSchema>): void {
if (defs.has(ref)) {
return;
}
const predefined = SETTINGS_SCHEMA_DEFINITIONS[ref] as
| SettingsJsonSchemaDefinition
| undefined;
if (predefined) {
defs.set(ref, predefined as JsonSchema);
} else {
defs.set(ref, { description: `Definition for ${ref}` });
}
}
if (process.argv[1]) {
const entryUrl = pathToFileURL(path.resolve(process.argv[1])).href;
if (entryUrl === import.meta.url) {
await main();
}
}

View File

@@ -0,0 +1,16 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect } from 'vitest';
import { main as generateDocs } from '../generate-settings-doc.ts';
describe('generate-settings-doc', () => {
it('keeps documentation in sync in check mode', async () => {
const previousExitCode = process.exitCode;
await expect(generateDocs(['--check'])).resolves.toBeUndefined();
expect(process.exitCode).toBe(previousExitCode);
});
});

View File

@@ -0,0 +1,16 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect, it } from 'vitest';
import { main as generateSchema } from '../generate-settings-schema.ts';
describe('generate-settings-schema', () => {
it('keeps schema in sync in check mode', async () => {
const previousExitCode = process.exitCode;
await expect(generateSchema(['--check'])).resolves.toBeUndefined();
expect(process.exitCode).toBe(previousExitCode);
});
});

View File

@@ -10,7 +10,7 @@ export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['scripts/tests/**/*.test.js'],
include: ['scripts/tests/**/*.test.{js,ts}'],
setupFiles: ['scripts/tests/test-setup.ts'],
coverage: {
provider: 'v8',

83
scripts/utils/autogen.ts Normal file
View File

@@ -0,0 +1,83 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import prettier from 'prettier';
export async function formatWithPrettier(content: string, filePath: string) {
const options = await prettier.resolveConfig(filePath);
return prettier.format(content, {
...options,
filepath: filePath,
});
}
export function normalizeForCompare(content: string): string {
return content.replace(/\r\n/g, '\n').trimEnd();
}
export function escapeBackticks(value: string): string {
return value.replace(/\\/g, '\\\\').replace(/`/g, '\\`');
}
export interface FormatDefaultValueOptions {
/**
* When true, string values are JSON-stringified, including surrounding quotes.
* Defaults to false to return raw string content.
*/
quoteStrings?: boolean;
}
export function formatDefaultValue(
value: unknown,
options: FormatDefaultValueOptions = {},
): string {
const { quoteStrings = false } = options;
if (value === undefined) {
return 'undefined';
}
if (value === null) {
return 'null';
}
if (typeof value === 'string') {
return quoteStrings ? JSON.stringify(value) : value;
}
if (typeof value === 'number' || typeof value === 'boolean') {
return String(value);
}
if (Array.isArray(value)) {
if (value.length === 0) {
return '[]';
}
try {
return JSON.stringify(value);
} catch {
return String(value);
}
}
if (typeof value === 'object') {
try {
const json = JSON.stringify(value);
if (json === '{}') {
return '{}';
}
return json;
} catch {
return '[object Object]';
}
}
try {
return JSON.stringify(value);
} catch {
return String(value);
}
}