From 5062fadf8767de5531a0a1577946d0e8227117a6 Mon Sep 17 00:00:00 2001 From: cornmander Date: Sun, 2 Nov 2025 20:42:49 -0500 Subject: [PATCH] chore: autogenerate settings documentation (#12451) --- .github/workflows/ci.yml | 6 + docs/get-started/configuration.md | 295 ++-- package.json | 3 + .../cli/src/config/settingsSchema.test.ts | 48 + packages/cli/src/config/settingsSchema.ts | 412 +++++- schemas/settings.schema.json | 1229 +++++++++++++++++ scripts/generate-settings-doc.ts | 201 +++ scripts/generate-settings-schema.ts | 354 +++++ scripts/tests/generate-settings-doc.test.ts | 16 + .../tests/generate-settings-schema.test.ts | 16 + scripts/tests/vitest.config.ts | 2 +- scripts/utils/autogen.ts | 83 ++ 12 files changed, 2546 insertions(+), 119 deletions(-) create mode 100644 schemas/settings.schema.json create mode 100644 scripts/generate-settings-doc.ts create mode 100644 scripts/generate-settings-schema.ts create mode 100644 scripts/tests/generate-settings-doc.test.ts create mode 100644 scripts/tests/generate-settings-schema.test.ts create mode 100644 scripts/utils/autogen.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 493232c586..486cafbb49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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' diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md index cc853b6039..e2e2621b78 100644 --- a/docs/get-started/configuration.md +++ b/docs/get-started/configuration.md @@ -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. + + #### `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:** `{}` + #### `mcpServers` diff --git a/package.json b/package.json index a9f1eaa357..b8e724ee50 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/cli/src/config/settingsSchema.test.ts b/packages/cli/src/config/settingsSchema.test.ts index 657c4c928d..421d51a7f8 100644 --- a/packages/cli/src/config/settingsSchema.test.ts +++ b/packages/cli/src/config/settingsSchema.test.ts @@ -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(); + + 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`, + ); + } + }); + }); }); diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 82ccdddd1f..ad71e1728d 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -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, 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 | 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; + +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; } diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json new file mode 100644 index 0000000000..c3b8844796 --- /dev/null +++ b/schemas/settings.schema.json @@ -0,0 +1,1229 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json", + "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": { + "mcpServers": { + "title": "MCP Servers", + "description": "Configuration for MCP servers.", + "markdownDescription": "Configuration for MCP servers.\n\n- Category: `Advanced`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/MCPServerConfig" + } + }, + "general": { + "title": "General", + "description": "General application settings.", + "markdownDescription": "General application settings.\n\n- Category: `General`\n- Requires restart: `no`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "preferredEditor": { + "title": "Preferred Editor", + "description": "The preferred editor to open files in.", + "markdownDescription": "The preferred editor to open files in.\n\n- Category: `General`\n- Requires restart: `no`", + "type": "string" + }, + "vimMode": { + "title": "Vim Mode", + "description": "Enable Vim keybindings", + "markdownDescription": "Enable Vim keybindings\n\n- Category: `General`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "disableAutoUpdate": { + "title": "Disable Auto Update", + "description": "Disable automatic updates", + "markdownDescription": "Disable automatic updates\n\n- Category: `General`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "disableUpdateNag": { + "title": "Disable Update Nag", + "description": "Disable update notification prompts.", + "markdownDescription": "Disable update notification prompts.\n\n- Category: `General`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "checkpointing": { + "title": "Checkpointing", + "description": "Session checkpointing settings.", + "markdownDescription": "Session checkpointing settings.\n\n- Category: `General`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "enabled": { + "title": "Enable Checkpointing", + "description": "Enable session checkpointing for recovery", + "markdownDescription": "Enable session checkpointing for recovery\n\n- Category: `General`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "enablePromptCompletion": { + "title": "Enable Prompt Completion", + "description": "Enable AI-powered prompt completion suggestions while typing.", + "markdownDescription": "Enable AI-powered prompt completion suggestions while typing.\n\n- Category: `General`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "retryFetchErrors": { + "title": "Retry Fetch Errors", + "description": "Retry on \"exception TypeError: fetch failed sending request\" errors.", + "markdownDescription": "Retry on \"exception TypeError: fetch failed sending request\" errors.\n\n- Category: `General`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "debugKeystrokeLogging": { + "title": "Debug Keystroke Logging", + "description": "Enable debug logging of keystrokes to the console.", + "markdownDescription": "Enable debug logging of keystrokes to the console.\n\n- Category: `General`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "sessionRetention": { + "title": "Session Retention", + "description": "Settings for automatic session cleanup.", + "markdownDescription": "Settings for automatic session cleanup.\n\n- Category: `General`\n- Requires restart: `no`", + "type": "object", + "properties": { + "enabled": { + "title": "Enable Session Cleanup", + "description": "Enable automatic session cleanup", + "markdownDescription": "Enable automatic session cleanup\n\n- Category: `General`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "maxAge": { + "title": "Max Session Age", + "description": "Maximum age of sessions to keep (e.g., \"30d\", \"7d\", \"24h\", \"1w\")", + "markdownDescription": "Maximum age of sessions to keep (e.g., \"30d\", \"7d\", \"24h\", \"1w\")\n\n- Category: `General`\n- Requires restart: `no`", + "type": "string" + }, + "maxCount": { + "title": "Max Session Count", + "description": "Alternative: Maximum number of sessions to keep (most recent)", + "markdownDescription": "Alternative: Maximum number of sessions to keep (most recent)\n\n- Category: `General`\n- Requires restart: `no`", + "type": "number" + }, + "minRetention": { + "title": "Min Retention Period", + "description": "Minimum retention period (safety limit, defaults to \"1d\")", + "markdownDescription": "Minimum retention period (safety limit, defaults to \"1d\")\n\n- Category: `General`\n- Requires restart: `no`\n- Default: `1d`", + "default": "1d", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "output": { + "title": "Output", + "description": "Settings for the CLI output.", + "markdownDescription": "Settings for the CLI output.\n\n- Category: `General`\n- Requires restart: `no`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "format": { + "title": "Output Format", + "description": "The format of the CLI output.", + "markdownDescription": "The format of the CLI output.\n\n- Category: `General`\n- Requires restart: `no`\n- Default: `text`", + "default": "text", + "type": "string", + "enum": ["text", "json"] + } + }, + "additionalProperties": false + }, + "ui": { + "title": "UI", + "description": "User interface settings.", + "markdownDescription": "User interface settings.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "theme": { + "title": "Theme", + "description": "The color theme for the UI. See the CLI themes guide for available options.", + "markdownDescription": "The color theme for the UI. See the CLI themes guide for available options.\n\n- Category: `UI`\n- Requires restart: `no`", + "type": "string" + }, + "customThemes": { + "title": "Custom Themes", + "description": "Custom theme definitions.", + "markdownDescription": "Custom theme definitions.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `{}`", + "default": {}, + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/CustomTheme" + } + }, + "hideWindowTitle": { + "title": "Hide Window Title", + "description": "Hide the window title bar", + "markdownDescription": "Hide the window title bar\n\n- Category: `UI`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "showStatusInTitle": { + "title": "Show Status in Title", + "description": "Show Gemini CLI status and thoughts in the terminal window title", + "markdownDescription": "Show Gemini CLI status and thoughts in the terminal window title\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "hideTips": { + "title": "Hide Tips", + "description": "Hide helpful tips in the UI", + "markdownDescription": "Hide helpful tips in the UI\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "hideBanner": { + "title": "Hide Banner", + "description": "Hide the application banner", + "markdownDescription": "Hide the application banner\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "hideContextSummary": { + "title": "Hide Context Summary", + "description": "Hide the context summary (GEMINI.md, MCP servers) above the input.", + "markdownDescription": "Hide the context summary (GEMINI.md, MCP servers) above the input.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "footer": { + "title": "Footer", + "description": "Settings for the footer.", + "markdownDescription": "Settings for the footer.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "hideCWD": { + "title": "Hide CWD", + "description": "Hide the current working directory path in the footer.", + "markdownDescription": "Hide the current working directory path in the footer.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "hideSandboxStatus": { + "title": "Hide Sandbox Status", + "description": "Hide the sandbox status indicator in the footer.", + "markdownDescription": "Hide the sandbox status indicator in the footer.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "hideModelInfo": { + "title": "Hide Model Info", + "description": "Hide the model name and context usage in the footer.", + "markdownDescription": "Hide the model name and context usage in the footer.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "hideContextPercentage": { + "title": "Hide Context Window Percentage", + "description": "Hides the context window remaining percentage.", + "markdownDescription": "Hides the context window remaining percentage.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `true`", + "default": true, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "hideFooter": { + "title": "Hide Footer", + "description": "Hide the footer from the UI", + "markdownDescription": "Hide the footer from the UI\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "showMemoryUsage": { + "title": "Show Memory Usage", + "description": "Display memory usage information in the UI", + "markdownDescription": "Display memory usage information in the UI\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "showLineNumbers": { + "title": "Show Line Numbers", + "description": "Show line numbers in the chat.", + "markdownDescription": "Show line numbers in the chat.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "showCitations": { + "title": "Show Citations", + "description": "Show citations for generated text in the chat.", + "markdownDescription": "Show citations for generated text in the chat.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "useFullWidth": { + "title": "Use Full Width", + "description": "Use the entire width of the terminal for output.", + "markdownDescription": "Use the entire width of the terminal for output.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "customWittyPhrases": { + "title": "Custom Witty Phrases", + "description": "Custom witty phrases to display during loading. When provided, the CLI cycles through these instead of the defaults.", + "markdownDescription": "Custom witty phrases to display during loading. When provided, the CLI cycles through these instead of the defaults.\n\n- Category: `UI`\n- Requires restart: `no`\n- Default: `[]`", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "accessibility": { + "title": "Accessibility", + "description": "Accessibility settings.", + "markdownDescription": "Accessibility settings.\n\n- Category: `UI`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "disableLoadingPhrases": { + "title": "Disable Loading Phrases", + "description": "Disable loading phrases for accessibility", + "markdownDescription": "Disable loading phrases for accessibility\n\n- Category: `UI`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "screenReader": { + "title": "Screen Reader Mode", + "description": "Render output in plain-text to be more screen reader accessible", + "markdownDescription": "Render output in plain-text to be more screen reader accessible\n\n- Category: `UI`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "ide": { + "title": "IDE", + "description": "IDE integration settings.", + "markdownDescription": "IDE integration settings.\n\n- Category: `IDE`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "enabled": { + "title": "IDE Mode", + "description": "Enable IDE integration mode", + "markdownDescription": "Enable IDE integration mode\n\n- Category: `IDE`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "hasSeenNudge": { + "title": "Has Seen IDE Integration Nudge", + "description": "Whether the user has seen the IDE integration nudge.", + "markdownDescription": "Whether the user has seen the IDE integration nudge.\n\n- Category: `IDE`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "privacy": { + "title": "Privacy", + "description": "Privacy-related settings.", + "markdownDescription": "Privacy-related settings.\n\n- Category: `Privacy`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "usageStatisticsEnabled": { + "title": "Enable Usage Statistics", + "description": "Enable collection of usage statistics", + "markdownDescription": "Enable collection of usage statistics\n\n- Category: `Privacy`\n- Requires restart: `yes`\n- Default: `true`", + "default": true, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "telemetry": { + "title": "Telemetry", + "description": "Telemetry configuration.", + "markdownDescription": "Telemetry configuration.\n\n- Category: `Advanced`\n- Requires restart: `yes`", + "$ref": "#/$defs/TelemetrySettings" + }, + "model": { + "title": "Model", + "description": "Settings related to the generative model.", + "markdownDescription": "Settings related to the generative model.\n\n- Category: `Model`\n- Requires restart: `no`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "name": { + "title": "Model", + "description": "The Gemini model to use for conversations.", + "markdownDescription": "The Gemini model to use for conversations.\n\n- Category: `Model`\n- Requires restart: `no`", + "type": "string" + }, + "maxSessionTurns": { + "title": "Max Session Turns", + "description": "Maximum number of user/model/tool turns to keep in a session. -1 means unlimited.", + "markdownDescription": "Maximum number of user/model/tool turns to keep in a session. -1 means unlimited.\n\n- Category: `Model`\n- Requires restart: `no`\n- Default: `-1`", + "default": -1, + "type": "number" + }, + "summarizeToolOutput": { + "title": "Summarize Tool Output", + "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.", + "markdownDescription": "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.\n\n- Category: `Model`\n- Requires restart: `no`", + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/SummarizeToolOutputSettings" + } + }, + "compressionThreshold": { + "title": "Compression Threshold", + "description": "The fraction of context usage at which to trigger context compression (e.g. 0.2, 0.3).", + "markdownDescription": "The fraction of context usage at which to trigger context compression (e.g. 0.2, 0.3).\n\n- Category: `Model`\n- Requires restart: `yes`\n- Default: `0.2`", + "default": 0.2, + "type": "number" + }, + "skipNextSpeakerCheck": { + "title": "Skip Next Speaker Check", + "description": "Skip the next speaker check.", + "markdownDescription": "Skip the next speaker check.\n\n- Category: `Model`\n- Requires restart: `no`\n- Default: `true`", + "default": true, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "context": { + "title": "Context", + "description": "Settings for managing context provided to the model.", + "markdownDescription": "Settings for managing context provided to the model.\n\n- Category: `Context`\n- Requires restart: `no`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "fileName": { + "title": "Context File Name", + "description": "The name of the context file or files to load into memory. Accepts either a single string or an array of strings.", + "markdownDescription": "The name of the context file or files to load into memory. Accepts either a single string or an array of strings.\n\n- Category: `Context`\n- Requires restart: `no`", + "$ref": "#/$defs/StringOrStringArray" + }, + "importFormat": { + "title": "Memory Import Format", + "description": "The format to use when importing memory.", + "markdownDescription": "The format to use when importing memory.\n\n- Category: `Context`\n- Requires restart: `no`", + "type": "string" + }, + "discoveryMaxDirs": { + "title": "Memory Discovery Max Dirs", + "description": "Maximum number of directories to search for memory.", + "markdownDescription": "Maximum number of directories to search for memory.\n\n- Category: `Context`\n- Requires restart: `no`\n- Default: `200`", + "default": 200, + "type": "number" + }, + "includeDirectories": { + "title": "Include Directories", + "description": "Additional directories to include in the workspace context. Missing directories will be skipped with a warning.", + "markdownDescription": "Additional directories to include in the workspace context. Missing directories will be skipped with a warning.\n\n- Category: `Context`\n- Requires restart: `no`\n- Default: `[]`", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "loadMemoryFromIncludeDirectories": { + "title": "Load Memory From Include Directories", + "description": "Controls how /memory refresh loads GEMINI.md files. When true, include directories are scanned; when false, only the current directory is used.", + "markdownDescription": "Controls how /memory refresh loads GEMINI.md files. When true, include directories are scanned; when false, only the current directory is used.\n\n- Category: `Context`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "fileFiltering": { + "title": "File Filtering", + "description": "Settings for git-aware file filtering.", + "markdownDescription": "Settings for git-aware file filtering.\n\n- Category: `Context`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "respectGitIgnore": { + "title": "Respect .gitignore", + "description": "Respect .gitignore files when searching", + "markdownDescription": "Respect .gitignore files when searching\n\n- Category: `Context`\n- Requires restart: `yes`\n- Default: `true`", + "default": true, + "type": "boolean" + }, + "respectGeminiIgnore": { + "title": "Respect .geminiignore", + "description": "Respect .geminiignore files when searching", + "markdownDescription": "Respect .geminiignore files when searching\n\n- Category: `Context`\n- Requires restart: `yes`\n- Default: `true`", + "default": true, + "type": "boolean" + }, + "enableRecursiveFileSearch": { + "title": "Enable Recursive File Search", + "description": "Enable recursive file search functionality when completing @ references in the prompt.", + "markdownDescription": "Enable recursive file search functionality when completing @ references in the prompt.\n\n- Category: `Context`\n- Requires restart: `yes`\n- Default: `true`", + "default": true, + "type": "boolean" + }, + "disableFuzzySearch": { + "title": "Disable Fuzzy Search", + "description": "Disable fuzzy search when searching for files.", + "markdownDescription": "Disable fuzzy search when searching for files.\n\n- Category: `Context`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "tools": { + "title": "Tools", + "description": "Settings for built-in and custom tools.", + "markdownDescription": "Settings for built-in and custom tools.\n\n- Category: `Tools`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "sandbox": { + "title": "Sandbox", + "description": "Sandbox execution environment. Set to a boolean to enable or disable the sandbox, or provide a string path to a sandbox profile.", + "markdownDescription": "Sandbox execution environment. Set to a boolean to enable or disable the sandbox, or provide a string path to a sandbox profile.\n\n- Category: `Tools`\n- Requires restart: `yes`", + "$ref": "#/$defs/BooleanOrString" + }, + "shell": { + "title": "Shell", + "description": "Settings for shell execution.", + "markdownDescription": "Settings for shell execution.\n\n- Category: `Tools`\n- Requires restart: `no`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "enableInteractiveShell": { + "title": "Enable Interactive Shell", + "description": "Use node-pty for an interactive shell experience. Fallback to child_process still applies.", + "markdownDescription": "Use node-pty for an interactive shell experience. Fallback to child_process still applies.\n\n- Category: `Tools`\n- Requires restart: `yes`\n- Default: `true`", + "default": true, + "type": "boolean" + }, + "pager": { + "title": "Pager", + "description": "The pager command to use for shell output. Defaults to `cat`.", + "markdownDescription": "The pager command to use for shell output. Defaults to `cat`.\n\n- Category: `Tools`\n- Requires restart: `no`\n- Default: `cat`", + "default": "cat", + "type": "string" + }, + "showColor": { + "title": "Show Color", + "description": "Show color in shell output.", + "markdownDescription": "Show color in shell output.\n\n- Category: `Tools`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "autoAccept": { + "title": "Auto Accept", + "description": "Automatically accept and execute tool calls that are considered safe (e.g., read-only operations).", + "markdownDescription": "Automatically accept and execute tool calls that are considered safe (e.g., read-only operations).\n\n- Category: `Tools`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "core": { + "title": "Core Tools", + "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.", + "markdownDescription": "Restrict the set of built-in tools with an allowlist. Match semantics mirror tools.allowed; see the built-in tools documentation for available names.\n\n- Category: `Tools`\n- Requires restart: `yes`", + "type": "array", + "items": { + "type": "string" + } + }, + "allowed": { + "title": "Allowed Tools", + "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.", + "markdownDescription": "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.\n\n- Category: `Advanced`\n- Requires restart: `yes`", + "type": "array", + "items": { + "type": "string" + } + }, + "exclude": { + "title": "Exclude Tools", + "description": "Tool names to exclude from discovery.", + "markdownDescription": "Tool names to exclude from discovery.\n\n- Category: `Tools`\n- Requires restart: `yes`", + "type": "array", + "items": { + "type": "string" + } + }, + "discoveryCommand": { + "title": "Tool Discovery Command", + "description": "Command to run for tool discovery.", + "markdownDescription": "Command to run for tool discovery.\n\n- Category: `Tools`\n- Requires restart: `yes`", + "type": "string" + }, + "callCommand": { + "title": "Tool Call Command", + "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.", + "markdownDescription": "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.\n\n- Category: `Tools`\n- Requires restart: `yes`", + "type": "string" + }, + "useRipgrep": { + "title": "Use Ripgrep", + "description": "Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance.", + "markdownDescription": "Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance.\n\n- Category: `Tools`\n- Requires restart: `no`\n- Default: `true`", + "default": true, + "type": "boolean" + }, + "enableToolOutputTruncation": { + "title": "Enable Tool Output Truncation", + "description": "Enable truncation of large tool outputs.", + "markdownDescription": "Enable truncation of large tool outputs.\n\n- Category: `General`\n- Requires restart: `yes`\n- Default: `true`", + "default": true, + "type": "boolean" + }, + "truncateToolOutputThreshold": { + "title": "Tool Output Truncation Threshold", + "description": "Truncate tool output if it is larger than this many characters. Set to -1 to disable.", + "markdownDescription": "Truncate tool output if it is larger than this many characters. Set to -1 to disable.\n\n- Category: `General`\n- Requires restart: `yes`\n- Default: `4000000`", + "default": 4000000, + "type": "number" + }, + "truncateToolOutputLines": { + "title": "Tool Output Truncation Lines", + "description": "The number of lines to keep when truncating tool output.", + "markdownDescription": "The number of lines to keep when truncating tool output.\n\n- Category: `General`\n- Requires restart: `yes`\n- Default: `1000`", + "default": 1000, + "type": "number" + }, + "enableMessageBusIntegration": { + "title": "Enable Message Bus Integration", + "description": "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.", + "markdownDescription": "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.\n\n- Category: `Tools`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "enableHooks": { + "title": "Enable Hooks System", + "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.", + "markdownDescription": "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.\n\n- Category: `Advanced`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "mcp": { + "title": "MCP", + "description": "Settings for Model Context Protocol (MCP) servers.", + "markdownDescription": "Settings for Model Context Protocol (MCP) servers.\n\n- Category: `MCP`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "serverCommand": { + "title": "MCP Server Command", + "description": "Command to start an MCP server.", + "markdownDescription": "Command to start an MCP server.\n\n- Category: `MCP`\n- Requires restart: `yes`", + "type": "string" + }, + "allowed": { + "title": "Allow MCP Servers", + "description": "A list of MCP servers to allow.", + "markdownDescription": "A list of MCP servers to allow.\n\n- Category: `MCP`\n- Requires restart: `yes`", + "type": "array", + "items": { + "type": "string" + } + }, + "excluded": { + "title": "Exclude MCP Servers", + "description": "A list of MCP servers to exclude.", + "markdownDescription": "A list of MCP servers to exclude.\n\n- Category: `MCP`\n- Requires restart: `yes`", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "useSmartEdit": { + "title": "Use Smart Edit", + "description": "Enable the smart-edit tool instead of the replace tool.", + "markdownDescription": "Enable the smart-edit tool instead of the replace tool.\n\n- Category: `Advanced`\n- Requires restart: `no`\n- Default: `true`", + "default": true, + "type": "boolean" + }, + "useWriteTodos": { + "title": "Use Write Todos", + "description": "Enable the write_todos_list tool.", + "markdownDescription": "Enable the write_todos_list tool.\n\n- Category: `Advanced`\n- Requires restart: `no`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "security": { + "title": "Security", + "description": "Security-related settings.", + "markdownDescription": "Security-related settings.\n\n- Category: `Security`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "disableYoloMode": { + "title": "Disable YOLO Mode", + "description": "Disable YOLO mode, even if enabled by a flag.", + "markdownDescription": "Disable YOLO mode, even if enabled by a flag.\n\n- Category: `Security`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "folderTrust": { + "title": "Folder Trust", + "description": "Settings for folder trust.", + "markdownDescription": "Settings for folder trust.\n\n- Category: `Security`\n- Requires restart: `no`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "enabled": { + "title": "Folder Trust", + "description": "Setting to track whether Folder trust is enabled.", + "markdownDescription": "Setting to track whether Folder trust is enabled.\n\n- Category: `Security`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "auth": { + "title": "Authentication", + "description": "Authentication settings.", + "markdownDescription": "Authentication settings.\n\n- Category: `Security`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "selectedType": { + "title": "Selected Auth Type", + "description": "The currently selected authentication type.", + "markdownDescription": "The currently selected authentication type.\n\n- Category: `Security`\n- Requires restart: `yes`", + "type": "string" + }, + "enforcedType": { + "title": "Enforced Auth Type", + "description": "The required auth type. If this does not match the selected auth type, the user will be prompted to re-authenticate.", + "markdownDescription": "The required auth type. If this does not match the selected auth type, the user will be prompted to re-authenticate.\n\n- Category: `Advanced`\n- Requires restart: `yes`", + "type": "string" + }, + "useExternal": { + "title": "Use External Auth", + "description": "Whether to use an external authentication flow.", + "markdownDescription": "Whether to use an external authentication flow.\n\n- Category: `Security`\n- Requires restart: `yes`", + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "advanced": { + "title": "Advanced", + "description": "Advanced settings for power users.", + "markdownDescription": "Advanced settings for power users.\n\n- Category: `Advanced`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "autoConfigureMemory": { + "title": "Auto Configure Max Old Space Size", + "description": "Automatically configure Node.js memory limits", + "markdownDescription": "Automatically configure Node.js memory limits\n\n- Category: `Advanced`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "dnsResolutionOrder": { + "title": "DNS Resolution Order", + "description": "The DNS resolution order.", + "markdownDescription": "The DNS resolution order.\n\n- Category: `Advanced`\n- Requires restart: `yes`", + "type": "string" + }, + "excludedEnvVars": { + "title": "Excluded Project Environment Variables", + "description": "Environment variables to exclude from project context.", + "markdownDescription": "Environment variables to exclude from project context.\n\n- Category: `Advanced`\n- Requires restart: `no`\n- Default: `[\"DEBUG\",\"DEBUG_MODE\"]`", + "default": ["DEBUG", "DEBUG_MODE"], + "type": "array", + "items": { + "type": "string" + } + }, + "bugCommand": { + "title": "Bug Command", + "description": "Configuration for the bug report command.", + "markdownDescription": "Configuration for the bug report command.\n\n- Category: `Advanced`\n- Requires restart: `no`", + "$ref": "#/$defs/BugCommandSettings" + } + }, + "additionalProperties": false + }, + "experimental": { + "title": "Experimental", + "description": "Setting to enable experimental features", + "markdownDescription": "Setting to enable experimental features\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "extensionManagement": { + "title": "Extension Management", + "description": "Enable extension management features.", + "markdownDescription": "Enable extension management features.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `true`", + "default": true, + "type": "boolean" + }, + "extensionReloading": { + "title": "Extension Reloading", + "description": "Enables extension loading/unloading within the CLI session.", + "markdownDescription": "Enables extension loading/unloading within the CLI session.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "useModelRouter": { + "title": "Use Model Router", + "description": "Enable model routing to route requests to the best model based on complexity.", + "markdownDescription": "Enable model routing to route requests to the best model based on complexity.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `true`", + "default": true, + "type": "boolean" + }, + "codebaseInvestigatorSettings": { + "title": "Codebase Investigator Settings", + "description": "Configuration for Codebase Investigator.", + "markdownDescription": "Configuration for Codebase Investigator.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "enabled": { + "title": "Enable Codebase Investigator", + "description": "Enable the Codebase Investigator agent.", + "markdownDescription": "Enable the Codebase Investigator agent.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + }, + "maxNumTurns": { + "title": "Codebase Investigator Max Num Turns", + "description": "Maximum number of turns for the Codebase Investigator agent.", + "markdownDescription": "Maximum number of turns for the Codebase Investigator agent.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `15`", + "default": 15, + "type": "number" + }, + "maxTimeMinutes": { + "title": "Max Time (Minutes)", + "description": "Maximum time for the Codebase Investigator agent (in minutes).", + "markdownDescription": "Maximum time for the Codebase Investigator agent (in minutes).\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `5`", + "default": 5, + "type": "number" + }, + "thinkingBudget": { + "title": "Thinking Budget", + "description": "The thinking budget for the Codebase Investigator agent.", + "markdownDescription": "The thinking budget for the Codebase Investigator agent.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `-1`", + "default": -1, + "type": "number" + }, + "model": { + "title": "Model", + "description": "The model to use for the Codebase Investigator agent.", + "markdownDescription": "The model to use for the Codebase Investigator agent.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `gemini-2.5-pro`", + "default": "gemini-2.5-pro", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "extensions": { + "title": "Extensions", + "description": "Settings for extensions.", + "markdownDescription": "Settings for extensions.\n\n- Category: `Extensions`\n- Requires restart: `yes`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "disabled": { + "title": "Disabled Extensions", + "description": "List of disabled extensions.", + "markdownDescription": "List of disabled extensions.\n\n- Category: `Extensions`\n- Requires restart: `yes`\n- Default: `[]`", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "workspacesWithMigrationNudge": { + "title": "Workspaces with Migration Nudge", + "description": "List of workspaces for which the migration nudge has been shown.", + "markdownDescription": "List of workspaces for which the migration nudge has been shown.\n\n- Category: `Extensions`\n- Requires restart: `no`\n- Default: `[]`", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "hooks": { + "title": "Hooks", + "description": "Hook configurations for intercepting and customizing agent behavior.", + "markdownDescription": "Hook configurations for intercepting and customizing agent behavior.\n\n- Category: `Advanced`\n- Requires restart: `no`\n- Default: `{}`", + "default": {}, + "type": "object", + "additionalProperties": true + } + }, + "$defs": { + "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" + } + ] + } + } +} diff --git a/scripts/generate-settings-doc.ts b/scripts/generate-settings-doc.ts new file mode 100644 index 0000000000..3a9b6b7e03 --- /dev/null +++ b/scripts/generate-settings-doc.ts @@ -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 = ''; +const END_MARKER = ''; + +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(); + + 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) { + 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(); + } +} diff --git a/scripts/generate-settings-schema.ts b/scripts/generate-settings-schema.ts new file mode 100644 index 0000000000..392194910b --- /dev/null +++ b/scripts/generate-settings-schema.ts @@ -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; + items?: JsonSchema; + additionalProperties?: boolean | JsonSchema; + required?: string[]; + $ref?: string; + anyOf?: JsonSchema[]; +} + +interface GenerateOptions { + checkOnly: boolean; +} + +export async function generateSettingsSchema( + options: GenerateOptions, +): Promise { + 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 { + const checkOnly = argv.includes('--check'); + await generateSettingsSchema({ checkOnly }); +} + +function buildSchemaObject(schema: SettingsSchemaType): JsonSchema { + const defs = new Map( + Object.entries(SETTINGS_SCHEMA_DEFINITIONS as Record), + ); + + 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, +): 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, +): JsonSchema { + if (collection.ref) { + return buildRefSchema(collection.ref, defs); + } + return buildSchemaForType(collection, pathSegments, defs); +} + +function buildSchemaForType( + source: SettingDefinition | SettingCollectionDefinition, + pathSegments: string[], + defs: Map, +): 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, '']; + 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, +): 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, ''], + defs, + ); + } else if (!definition.properties) { + schema.additionalProperties = true; + } else { + schema.additionalProperties = false; + } + + return schema; +} + +function buildObjectCollectionSchema( + collection: SettingCollectionDefinition, + pathSegments: string[], + defs: Map, +): JsonSchema { + if (collection.properties) { + return buildInlineObjectSchema(collection.properties, pathSegments, defs); + } + return { type: 'object', additionalProperties: true }; +} + +function buildObjectProperties( + properties: SettingsSchema, + pathSegments: string[], + defs: Map, +): Record { + const result: Record = {}; + 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, +): JsonSchema { + const childSchemas = buildObjectProperties(properties, pathSegments, defs); + return { + type: 'object', + properties: childSchemas, + additionalProperties: false, + }; +} + +function buildRefSchema( + ref: string, + defs: Map, +): 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 | 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): 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(); + } +} diff --git a/scripts/tests/generate-settings-doc.test.ts b/scripts/tests/generate-settings-doc.test.ts new file mode 100644 index 0000000000..173c6ec566 --- /dev/null +++ b/scripts/tests/generate-settings-doc.test.ts @@ -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); + }); +}); diff --git a/scripts/tests/generate-settings-schema.test.ts b/scripts/tests/generate-settings-schema.test.ts new file mode 100644 index 0000000000..ed7919bba2 --- /dev/null +++ b/scripts/tests/generate-settings-schema.test.ts @@ -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); + }); +}); diff --git a/scripts/tests/vitest.config.ts b/scripts/tests/vitest.config.ts index 7d3ac66252..9eb42595cf 100644 --- a/scripts/tests/vitest.config.ts +++ b/scripts/tests/vitest.config.ts @@ -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', diff --git a/scripts/utils/autogen.ts b/scripts/utils/autogen.ts new file mode 100644 index 0000000000..a4448f2040 --- /dev/null +++ b/scripts/utils/autogen.ts @@ -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); + } +}