From 797ea1f6073e97d02a4e91c9030e4fb846ff54fa Mon Sep 17 00:00:00 2001 From: Bee <68532117+abeatrix@users.noreply.github.com> Date: Mon, 8 Jun 2026 14:22:41 -0700 Subject: [PATCH] feat: global auto-update setting for CLI startup updates (#11326) * feat: global auto-update setting for CLI startup updates * patches --------- Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> --- apps/cli/src/commands/update.test.ts | 83 ++++++++++++++++++- apps/cli/src/commands/update.ts | 2 + apps/cli/src/tui/views/config-view.tsx | 15 ++++ apps/cline-hub/src/server/desktop-commands.ts | 8 ++ .../views/settings/settings-view.tsx | 55 ++++++++++++ sdk/packages/core/src/index.ts | 2 + .../core/src/services/global-settings.test.ts | 73 ++++++++++++++-- .../core/src/services/global-settings.ts | 20 +++++ .../src/services/plugin-uninstall.test.ts | 2 + .../src/settings/settings-service.test.ts | 3 +- sdk/packages/core/src/types.ts | 2 + 11 files changed, 257 insertions(+), 8 deletions(-) diff --git a/apps/cli/src/commands/update.test.ts b/apps/cli/src/commands/update.test.ts index c57de0d596..bbfcc25ebe 100644 --- a/apps/cli/src/commands/update.test.ts +++ b/apps/cli/src/commands/update.test.ts @@ -1,8 +1,10 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { + autoUpdateOnStartup, + checkForUpdates, getInstallationInfo, PackageManager, withMinimumReleaseAgeBypass, @@ -10,6 +12,9 @@ import { const originalArgv = [...process.argv]; const originalWrapperPath = process.env.CLINE_WRAPPER_PATH; +const originalGlobalSettingsPath = process.env.CLINE_GLOBAL_SETTINGS_PATH; +const originalIsDev = process.env.IS_DEV; +const originalNoAutoUpdate = process.env.CLINE_NO_AUTO_UPDATE; const tempDirs: string[] = []; function createFile(path: string): string { @@ -32,6 +37,22 @@ describe("getInstallationInfo", () => { } else { process.env.CLINE_WRAPPER_PATH = originalWrapperPath; } + if (originalGlobalSettingsPath === undefined) { + delete process.env.CLINE_GLOBAL_SETTINGS_PATH; + } else { + process.env.CLINE_GLOBAL_SETTINGS_PATH = originalGlobalSettingsPath; + } + if (originalIsDev === undefined) { + delete process.env.IS_DEV; + } else { + process.env.IS_DEV = originalIsDev; + } + if (originalNoAutoUpdate === undefined) { + delete process.env.CLINE_NO_AUTO_UPDATE; + } else { + process.env.CLINE_NO_AUTO_UPDATE = originalNoAutoUpdate; + } + vi.restoreAllMocks(); for (const dir of tempDirs.splice(0)) { rmSync(dir, { recursive: true, force: true }); } @@ -72,6 +93,66 @@ describe("getInstallationInfo", () => { }); }); +describe("auto update settings", () => { + afterEach(() => { + process.argv = [...originalArgv]; + if (originalWrapperPath === undefined) { + delete process.env.CLINE_WRAPPER_PATH; + } else { + process.env.CLINE_WRAPPER_PATH = originalWrapperPath; + } + if (originalGlobalSettingsPath === undefined) { + delete process.env.CLINE_GLOBAL_SETTINGS_PATH; + } else { + process.env.CLINE_GLOBAL_SETTINGS_PATH = originalGlobalSettingsPath; + } + if (originalIsDev === undefined) { + delete process.env.IS_DEV; + } else { + process.env.IS_DEV = originalIsDev; + } + if (originalNoAutoUpdate === undefined) { + delete process.env.CLINE_NO_AUTO_UPDATE; + } else { + process.env.CLINE_NO_AUTO_UPDATE = originalNoAutoUpdate; + } + vi.restoreAllMocks(); + for (const dir of tempDirs.splice(0)) { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("skips startup auto update when disabled globally", () => { + const settingsPath = createTempFile("data/global-settings.json"); + writeFileSync(settingsPath, JSON.stringify({ autoUpdateEnabled: false })); + process.env.CLINE_GLOBAL_SETTINGS_PATH = settingsPath; + delete process.env.IS_DEV; + delete process.env.CLINE_NO_AUTO_UPDATE; + const fetchSpy = vi + .spyOn(globalThis, "fetch") + .mockRejectedValue(new Error("should not fetch")); + + autoUpdateOnStartup(); + + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + it("still lets manual update checks run when startup auto update is disabled", async () => { + const settingsPath = createTempFile("data/global-settings.json"); + writeFileSync(settingsPath, JSON.stringify({ autoUpdateEnabled: false })); + process.env.CLINE_GLOBAL_SETTINGS_PATH = settingsPath; + delete process.env.CLINE_NO_AUTO_UPDATE; + const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue({ + ok: true, + json: async () => ({ version: "0.0.0" }), + } as Response); + + await checkForUpdates({ includeKanban: false }); + + expect(fetchSpy).toHaveBeenCalled(); + }); +}); + describe("withMinimumReleaseAgeBypass", () => { it("adds the package-manager-specific cooldown bypass", () => { expect( diff --git a/apps/cli/src/commands/update.ts b/apps/cli/src/commands/update.ts index b784e1f811..9d2615d117 100644 --- a/apps/cli/src/commands/update.ts +++ b/apps/cli/src/commands/update.ts @@ -2,6 +2,7 @@ import { type ChildProcess, spawn } from "node:child_process"; import { realpathSync } from "node:fs"; import { clearHubDiscovery, + isAutoUpdateEnabledGlobally, probeHubServer, readHubDiscovery, resolveSharedHubOwnerContext, @@ -340,6 +341,7 @@ async function restartHubServerIfRunning(): Promise { export function autoUpdateOnStartup(): void { if (process.env.IS_DEV === "true") return; if (process.env.CLINE_NO_AUTO_UPDATE === "1") return; + if (!isAutoUpdateEnabledGlobally()) return; const { packageName, packageManager, updateCommand } = getInstallationInfo(version); diff --git a/apps/cli/src/tui/views/config-view.tsx b/apps/cli/src/tui/views/config-view.tsx index f78ee181fd..55411d8fc0 100644 --- a/apps/cli/src/tui/views/config-view.tsx +++ b/apps/cli/src/tui/views/config-view.tsx @@ -1,3 +1,4 @@ +import { readGlobalSettings, setAutoUpdateEnabledGlobally } from "@cline/core"; import { useTerminalDimensions } from "@opentui/react"; import type { ChoiceContext } from "@opentui-ui/dialog"; import { useDialogKeyboard } from "@opentui-ui/dialog/react"; @@ -368,6 +369,9 @@ export function ConfigPanelContent(props: ConfigPanelProps) { const [autoApprove, setAutoApprove] = useState( config.toolPolicies["*"]?.autoApprove !== false, ); + const [autoUpdateEnabled, setAutoUpdateEnabled] = useState( + () => readGlobalSettings().autoUpdateEnabled, + ); const [verbose, setVerbose] = useState(config.verbose); const [compactionMode, setCompactionMode] = useState( props.currentCompactionMode, @@ -445,6 +449,7 @@ export function ConfigPanelContent(props: ConfigPanelProps) { id: "auto-approve", label: "Auto-approve all", }); + r.push({ kind: "toggle", id: "auto-update", label: "Auto update" }); r.push({ kind: "toggle", id: "verbose", label: "Verbose" }); } else { const activeItems = resolveActiveConfigItems(configData, activeTab); @@ -584,6 +589,13 @@ export function ConfigPanelContent(props: ConfigPanelProps) { setAutoApprove(!autoApprove); props.onToggleAutoApprove(); break; + case "auto-update": + setAutoUpdateEnabled((previous) => { + const next = !previous; + setAutoUpdateEnabledGlobally(next); + return next; + }); + break; case "compaction": { const nextMode = getNextCliCompactionMode(compactionMode); setCompactionMode(nextMode); @@ -789,6 +801,9 @@ export function ConfigPanelContent(props: ConfigPanelProps) { } else if (row.id === "auto-approve") { value = autoApprove ? "● on" : "○ off"; valueColor = autoApprove ? palette.success : "gray"; + } else if (row.id === "auto-update") { + value = autoUpdateEnabled ? "● on" : "○ off"; + valueColor = autoUpdateEnabled ? palette.success : "gray"; } else if (row.id === "compaction") { value = formatCliCompactionMode(compactionMode); valueColor = COMPACTION_MODE_COLORS[compactionMode]; diff --git a/apps/cline-hub/src/server/desktop-commands.ts b/apps/cline-hub/src/server/desktop-commands.ts index 4065af2c08..a5200c846d 100644 --- a/apps/cline-hub/src/server/desktop-commands.ts +++ b/apps/cline-hub/src/server/desktop-commands.ts @@ -15,6 +15,7 @@ import { resolveLocalClineAuthToken, saveLocalProviderOAuthCredentials, saveLocalProviderSettings, + setAutoUpdateEnabledGlobally, setDisabledPlugin, setDisabledTools, setTelemetryOptOutGlobally, @@ -155,6 +156,13 @@ export async function handleDesktopCommand( setTelemetryOptOutGlobally(args.telemetry_opt_out); return readGlobalSettings(); } + if (command === "set_auto_update_enabled") { + if (typeof args?.auto_update_enabled !== "boolean") { + throw new Error("auto_update_enabled must be a boolean"); + } + setAutoUpdateEnabledGlobally(args.auto_update_enabled); + return readGlobalSettings(); + } if (command === "list_connector_channels") { return connectorChannelsPayload(); } diff --git a/apps/cline-hub/src/webview/src/components/views/settings/settings-view.tsx b/apps/cline-hub/src/webview/src/components/views/settings/settings-view.tsx index 3db19a40d4..c209b9662a 100644 --- a/apps/cline-hub/src/webview/src/components/views/settings/settings-view.tsx +++ b/apps/cline-hub/src/webview/src/components/views/settings/settings-view.tsx @@ -43,6 +43,7 @@ export type SettingsSection = (typeof navCategories)[number]; type Theme = "dark" | "light"; type GlobalSettingsResponse = { telemetryOptOut: boolean; + autoUpdateEnabled: boolean; }; const PROVIDER_CATALOG_CACHE_TTL_MS = 60_000; @@ -525,20 +526,29 @@ function GeneralSettingsContent({ const [telemetryLoading, setTelemetryLoading] = useState(true); const [telemetrySaving, setTelemetrySaving] = useState(false); const [telemetryError, setTelemetryError] = useState(null); + const [autoUpdateEnabled, setAutoUpdateEnabled] = useState(true); + const [autoUpdateLoading, setAutoUpdateLoading] = useState(true); + const [autoUpdateSaving, setAutoUpdateSaving] = useState(false); + const [autoUpdateError, setAutoUpdateError] = useState(null); const loadGlobalSettings = useCallback(async () => { setTelemetryLoading(true); setTelemetryError(null); + setAutoUpdateLoading(true); + setAutoUpdateError(null); try { const settings = await desktopClient.invoke( "get_global_settings", ); setTelemetryOptOut(settings.telemetryOptOut); + setAutoUpdateEnabled(settings.autoUpdateEnabled); } catch (error) { const message = error instanceof Error ? error.message : String(error); setTelemetryError(message); + setAutoUpdateError(message); } finally { setTelemetryLoading(false); + setAutoUpdateLoading(false); } }, []); @@ -571,6 +581,28 @@ function GeneralSettingsContent({ } }; + const updateAutoUpdateEnabled = async (nextValue: boolean) => { + const previousValue = autoUpdateEnabled; + setAutoUpdateEnabled(nextValue); + setAutoUpdateSaving(true); + setAutoUpdateError(null); + try { + const settings = await desktopClient.invoke( + "set_auto_update_enabled", + { + auto_update_enabled: nextValue, + }, + ); + setAutoUpdateEnabled(settings.autoUpdateEnabled); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + setAutoUpdateEnabled(previousValue); + setAutoUpdateError(message); + } finally { + setAutoUpdateSaving(false); + } + }; + return (
@@ -605,6 +637,29 @@ function GeneralSettingsContent({
+
+
+
+

Auto update

+

+ Automatically install CLI updates on startup. +

+ {autoUpdateError ? ( +

+ Failed to update auto update setting: {autoUpdateError} +

+ ) : null} +
+ + void updateAutoUpdateEnabled(checked) + } + /> +
+
diff --git a/sdk/packages/core/src/index.ts b/sdk/packages/core/src/index.ts index 7973843933..1fed5b1ca5 100644 --- a/sdk/packages/core/src/index.ts +++ b/sdk/packages/core/src/index.ts @@ -421,12 +421,14 @@ export { filterDisabledTools, filterExtensionToolRegistrations, GlobalSettingsSchema, + isAutoUpdateEnabledGlobally, isPluginDisabledGlobally, isTelemetryOptedOutGlobally, isToolDisabledGlobally, readGlobalSettings, resolveDisabledPluginPaths, resolveDisabledToolNames, + setAutoUpdateEnabledGlobally, setDisabledPlugin, setDisabledTools, setTelemetryOptOutGlobally, diff --git a/sdk/packages/core/src/services/global-settings.test.ts b/sdk/packages/core/src/services/global-settings.test.ts index 756213078f..74bdebfd7b 100644 --- a/sdk/packages/core/src/services/global-settings.test.ts +++ b/sdk/packages/core/src/services/global-settings.test.ts @@ -6,6 +6,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { GlobalSettingsSchema, readGlobalSettings, + setAutoUpdateEnabledGlobally, setDisabledPlugin, setDisabledTools, setTelemetryOptOutGlobally, @@ -26,6 +27,7 @@ describe("global-settings", () => { disabledPlugins: ["/plugins/example.js", "/plugins/example.js"], }), ).toEqual({ + autoUpdateEnabled: true, disabledPlugins: ["/plugins/example.js"], disabledTools: ["editor", "read_files"], telemetryOptOut: false, @@ -35,8 +37,9 @@ describe("global-settings", () => { disabledTools: [], telemetryOptOut: true, }), - ).toEqual({ telemetryOptOut: true }); + ).toEqual({ autoUpdateEnabled: true, telemetryOptOut: true }); expect(GlobalSettingsSchema.parse({ disabledTools: [] })).toEqual({ + autoUpdateEnabled: true, telemetryOptOut: false, }); expect( @@ -45,6 +48,7 @@ describe("global-settings", () => { extra: true, }), ).toEqual({ + autoUpdateEnabled: true, disabledTools: ["read_files"], telemetryOptOut: false, }); @@ -55,8 +59,17 @@ describe("global-settings", () => { telemetryOptOut: true, }), ).toEqual({ + autoUpdateEnabled: true, telemetryOptOut: true, }); + expect( + GlobalSettingsSchema.parse({ + autoUpdateEnabled: false, + }), + ).toEqual({ + autoUpdateEnabled: false, + telemetryOptOut: false, + }); }); it("uses the schema when reading and writing settings", async () => { @@ -71,10 +84,12 @@ describe("global-settings", () => { }); expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, disabledTools: ["editor", "read_files"], telemetryOptOut: false, }); expect(JSON.parse(await readFile(settingsPath, "utf8"))).toEqual({ + autoUpdateEnabled: true, disabledTools: ["editor", "read_files"], telemetryOptOut: false, }); @@ -88,6 +103,7 @@ describe("global-settings", () => { }), ); expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, disabledTools: ["read_files"], telemetryOptOut: true, }); @@ -100,7 +116,10 @@ describe("global-settings", () => { telemetryOptOut: true, }), ); - expect(readGlobalSettings()).toEqual({ telemetryOptOut: true }); + expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, + telemetryOptOut: true, + }); } finally { await rm(root, { recursive: true, force: true }); } @@ -117,11 +136,13 @@ describe("global-settings", () => { setDisabledTools(["editor"], false); expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, disabledPlugins: ["/plugins/example.js"], disabledTools: ["read_files"], telemetryOptOut: false, }); expect(JSON.parse(await readFile(settingsPath, "utf8"))).toEqual({ + autoUpdateEnabled: true, disabledPlugins: ["/plugins/example.js"], disabledTools: ["read_files"], telemetryOptOut: false, @@ -147,7 +168,32 @@ describe("global-settings", () => { expect(captureRequired).toHaveBeenCalledTimes(1); expect(captureRequired).toHaveBeenCalledWith("user.opt_out", undefined); - expect(readGlobalSettings()).toEqual({ telemetryOptOut: false }); + expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, + telemetryOptOut: false, + }); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); + + it("preserves other settings when auto update is changed", async () => { + const root = await mkdtemp(join(tmpdir(), "core-global-settings-")); + try { + const settingsPath = join(root, "global-settings.json"); + process.env.CLINE_GLOBAL_SETTINGS_PATH = settingsPath; + + writeGlobalSettings({ + disabledTools: ["editor"], + telemetryOptOut: true, + }); + setAutoUpdateEnabledGlobally(false); + + expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: false, + disabledTools: ["editor"], + telemetryOptOut: true, + }); } finally { await rm(root, { recursive: true, force: true }); } @@ -165,6 +211,7 @@ describe("global-settings", () => { writeGlobalSettings({ disabledTools: ["read_files"] }); expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, disabledTools: ["read_files"], telemetryOptOut: false, }); @@ -187,6 +234,7 @@ describe("global-settings", () => { ); expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, disabledTools: ["read_files"], telemetryOptOut: false, }); @@ -205,6 +253,7 @@ describe("global-settings", () => { process.env.CLINE_GLOBAL_SETTINGS_PATH = pathA; writeGlobalSettings({ disabledTools: ["editor"] }); expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, disabledTools: ["editor"], telemetryOptOut: false, }); @@ -212,12 +261,14 @@ describe("global-settings", () => { process.env.CLINE_GLOBAL_SETTINGS_PATH = pathB; writeGlobalSettings({ disabledTools: ["read_files"] }); expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, disabledTools: ["read_files"], telemetryOptOut: false, }); process.env.CLINE_GLOBAL_SETTINGS_PATH = pathA; expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, disabledTools: ["editor"], telemetryOptOut: false, }); @@ -233,8 +284,14 @@ describe("global-settings", () => { const settingsPath = join(root, "missing-global-settings.json"); process.env.CLINE_GLOBAL_SETTINGS_PATH = settingsPath; - expect(readGlobalSettings()).toEqual({ telemetryOptOut: false }); - expect(readGlobalSettings()).toEqual({ telemetryOptOut: false }); + expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, + telemetryOptOut: false, + }); + expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, + telemetryOptOut: false, + }); } finally { await rm(root, { recursive: true, force: true }); } @@ -272,7 +329,10 @@ describe("global-settings", () => { const settingsPath = join(root, "global-settings.json"); process.env.CLINE_GLOBAL_SETTINGS_PATH = settingsPath; - expect(readGlobalSettings()).toEqual({ telemetryOptOut: false }); + expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, + telemetryOptOut: false, + }); await writeFile( settingsPath, @@ -280,6 +340,7 @@ describe("global-settings", () => { ); expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, disabledTools: ["editor"], telemetryOptOut: false, }); diff --git a/sdk/packages/core/src/services/global-settings.ts b/sdk/packages/core/src/services/global-settings.ts index ecb77b7c23..d1934b7ba6 100644 --- a/sdk/packages/core/src/services/global-settings.ts +++ b/sdk/packages/core/src/services/global-settings.ts @@ -32,6 +32,7 @@ const GlobalSettingsStringListSchema = z export const GlobalSettingsSchema = z .object({ telemetryOptOut: z.boolean().default(false).catch(false), + autoUpdateEnabled: z.boolean().default(true).catch(true), disabledTools: GlobalSettingsStringListSchema.optional(), disabledPlugins: GlobalSettingsStringListSchema.optional(), }) @@ -39,9 +40,11 @@ export const GlobalSettingsSchema = z .transform((settings) => { const normalized: { telemetryOptOut: boolean; + autoUpdateEnabled: boolean; disabledTools?: string[]; disabledPlugins?: string[]; } = { + autoUpdateEnabled: settings.autoUpdateEnabled, telemetryOptOut: settings.telemetryOptOut, }; if (settings.disabledTools?.length) { @@ -160,6 +163,23 @@ export function setTelemetryOptOutGlobally( ); } +export function isAutoUpdateEnabledGlobally(): boolean { + return readGlobalSettings().autoUpdateEnabled; +} + +export function setAutoUpdateEnabledGlobally( + autoUpdateEnabled: boolean, + options: WriteGlobalSettingsOptions = {}, +): void { + writeGlobalSettings( + { + ...readGlobalSettings(), + autoUpdateEnabled, + }, + options, + ); +} + export function resolveDisabledToolNames( disabledToolNames?: ReadonlyArray, ): Set { diff --git a/sdk/packages/core/src/services/plugin-uninstall.test.ts b/sdk/packages/core/src/services/plugin-uninstall.test.ts index 8d278176f2..94aad5a4e4 100644 --- a/sdk/packages/core/src/services/plugin-uninstall.test.ts +++ b/sdk/packages/core/src/services/plugin-uninstall.test.ts @@ -106,6 +106,7 @@ describe("plugin uninstall service", () => { expect(result.installPath).toBe(installPath); expect(existsSync(installPath)).toBe(false); expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, disabledPlugins: ["/tmp/other-plugin.ts"], telemetryOptOut: false, }); @@ -146,6 +147,7 @@ describe("plugin uninstall service", () => { await expect(uninstallPlugin({ path: pluginPath })).rejects.toThrow(); expect(existsSync(pluginPath)).toBe(true); expect(readGlobalSettings()).toEqual({ + autoUpdateEnabled: true, disabledPlugins: [pluginPath], telemetryOptOut: false, }); diff --git a/sdk/packages/core/src/settings/settings-service.test.ts b/sdk/packages/core/src/settings/settings-service.test.ts index 73e0d370e8..7806cd15e9 100644 --- a/sdk/packages/core/src/settings/settings-service.test.ts +++ b/sdk/packages/core/src/settings/settings-service.test.ts @@ -287,6 +287,7 @@ Use this skill.`, await readFile(process.env.CLINE_GLOBAL_SETTINGS_PATH, "utf8"), ), ).toEqual({ + autoUpdateEnabled: true, disabledTools: ["plugin-tool"], telemetryOptOut: false, }); @@ -297,6 +298,6 @@ Use this skill.`, JSON.parse( await readFile(process.env.CLINE_GLOBAL_SETTINGS_PATH, "utf8"), ), - ).toEqual({ telemetryOptOut: false }); + ).toEqual({ autoUpdateEnabled: true, telemetryOptOut: false }); }); }); diff --git a/sdk/packages/core/src/types.ts b/sdk/packages/core/src/types.ts index 4a95c18140..5f15c5822f 100644 --- a/sdk/packages/core/src/types.ts +++ b/sdk/packages/core/src/types.ts @@ -109,12 +109,14 @@ export { filterDisabledTools, filterExtensionToolRegistrations, GlobalSettingsSchema, + isAutoUpdateEnabledGlobally, isPluginDisabledGlobally, isTelemetryOptedOutGlobally, isToolDisabledGlobally, readGlobalSettings, resolveDisabledPluginPaths, resolveDisabledToolNames, + setAutoUpdateEnabledGlobally, setDisabledPlugin, setDisabledTools, setTelemetryOptOutGlobally,