openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,704 lines • 73.9 kB
JavaScript
import { c as normalizeOptionalString, f as normalizeStringifiedOptionalString } from "./string-coerce-mnp54Vah.js";
import { o as isRecord } from "./record-coerce-DHZ4bFlT.js";
import { b as parseStrictPositiveInteger } from "./number-coercion-CJQ8TR--.js";
import "./parse-finite-number-Z7n6tXLk.js";
import { t as CONFIG_PATH } from "./paths-mvMm5bYV.js";
import { t as formatCliCommand } from "./command-format-CKGmlpAQ.js";
import { t as formatDocsLink } from "./links-CsLBrRff.js";
import { r as theme } from "./theme-vjDs9tao.js";
import { l as normalizeStringEntries, v as uniqueValues } from "./string-normalization-WNUDCpXX.js";
import { g as shortenHomePath } from "./utils-CCC-BEJH.js";
import { l as isValidEnvSecretRefId, m as resolveSecretInputRef, o as coerceSecretRef } from "./types.secrets-_0JOMGE5.js";
import { n as defaultRuntime, r as writeRuntimeJson } from "./runtime-B4lgFmsS.js";
import { t as isBlockedObjectKey } from "./prototype-keys-D2nJOZIy.js";
import { a as isValidFileSecretRefId, c as secretRefKey, i as isValidExecSecretRefId, l as validateExecSecretRefId, o as isValidSecretProviderAlias, r as formatExecSecretRefIdValidationMessage } from "./ref-contract-BAa3qHwr.js";
import { a as resolveSecretRefValue, l as resolveSecretProviderIntegrationConfig, s as isPluginIntegrationSecretProviderConfig } from "./resolve-DPFnV1p0.js";
import { n as normalizeAgentModelRefForConfig, t as normalizeAgentModelMapForConfig } from "./model-input-B2zxl6MM.js";
import { t as parseConfigPathArrayIndex } from "./path-array-index-BDeCXbeb.js";
import { i as loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot-Ctk9Oarq.js";
import { n as discoverConfigSecretTargets, s as resolveConfigSecretTargetByPath } from "./target-registry-0k0oK6Ia.js";
import { n as info, o as success, t as danger } from "./globals-GTrXU4s9.js";
import { S as collectUnsupportedSecretRefPolicyIssues, T as validateConfigObjectRawWithPlugins, W as AUTO_MANAGED_CONFIG_META_PATHS, u as readConfigFileSnapshot } from "./io-Gi7-pyU-.js";
import { T as SecretProviderSchema } from "./zod-schema.core-1wQTyhOa.js";
import { n as formatConfigIssueLines, o as normalizeConfigIssues } from "./issue-format-CwCoGNbt.js";
import { n as isPluginPackagingRuntimeOutputInvalidConfigSnapshot } from "./recovery-policy-Dve6SNpn.js";
import { i as replaceConfigFile } from "./config-C9RxTsn1.js";
import { s as formatStrictJsonParseFailure } from "./error-format-COoNML-C.js";
import { n as formatPluginPackagingRuntimeOutputRecoveryHint } from "./config-recovery-hints-DCnttk3l.js";
import { r as normalizeConfiguredProviderCatalogModelId } from "./model-ref-shared-CqcindZs.js";
import { n as redactConfigObject } from "./redact-snapshot-CuoBedjD.js";
import { n as readBestEffortRuntimeConfigSchema } from "./runtime-schema-BjT8eJxD.js";
import { n as setCommandJsonMode } from "./json-mode-CJba5jsj.js";
import fs from "node:fs";
import JSON5 from "json5";
//#region src/cli/config-set-input.ts
function hasBatchMode(opts) {
return Boolean(normalizeOptionalString(opts.batchJson) || normalizeOptionalString(opts.batchFile));
}
function hasRefBuilderOptions(opts) {
return Boolean(opts.refProvider || opts.refSource || opts.refId);
}
function hasProviderBuilderOptions(opts) {
return Boolean(opts.providerSource || opts.providerAllowlist?.length || opts.providerPath || opts.providerMode || opts.providerTimeoutMs || opts.providerMaxBytes || opts.providerCommand || opts.providerArg?.length || opts.providerNoOutputTimeoutMs || opts.providerMaxOutputBytes || opts.providerJsonOnly || opts.providerEnv?.length || opts.providerPassEnv?.length || opts.providerTrustedDir?.length || opts.providerAllowInsecurePath || opts.providerAllowSymlinkCommand);
}
function parseJson5Raw(raw, label) {
try {
return JSON5.parse(raw);
} catch (err) {
throw new Error(`Failed to parse ${label}: ${String(err)}`, { cause: err });
}
}
function parseBatchEntries(raw, sourceLabel) {
const parsed = parseJson5Raw(raw, sourceLabel);
if (!Array.isArray(parsed)) throw new Error(`${sourceLabel} must be a JSON array.`);
const out = [];
for (const [index, entry] of parsed.entries()) {
if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw new Error(`${sourceLabel}[${index}] must be an object.`);
const typed = entry;
const path = normalizeOptionalString(typed.path) ?? "";
if (!path) throw new Error(`${sourceLabel}[${index}].path is required.`);
const hasValue = Object.hasOwn(typed, "value");
const hasRef = Object.hasOwn(typed, "ref");
const hasProvider = Object.hasOwn(typed, "provider");
if (Number(hasValue) + Number(hasRef) + Number(hasProvider) !== 1) throw new Error(`${sourceLabel}[${index}] must include exactly one of: value, ref, provider.`);
out.push({
path,
...hasValue ? { value: typed.value } : {},
...hasRef ? { ref: typed.ref } : {},
...hasProvider ? { provider: typed.provider } : {}
});
}
return out;
}
function parseBatchSource(opts) {
const batchJson = normalizeOptionalString(opts.batchJson);
const batchFile = normalizeOptionalString(opts.batchFile);
const hasInline = Boolean(batchJson);
const hasFile = Boolean(batchFile);
if (!hasInline && !hasFile) return null;
if (hasInline && hasFile) throw new Error("Use either --batch-json or --batch-file, not both.");
if (hasInline) return parseBatchEntries(batchJson, "--batch-json");
const pathname = normalizeStringifiedOptionalString(opts.batchFile) ?? "";
if (!pathname) throw new Error("--batch-file must not be empty.");
return parseBatchEntries(fs.readFileSync(pathname, "utf8"), "--batch-file");
}
//#endregion
//#region src/cli/config-set-parser.ts
/** Resolve the config-set input mode or return the exact flag-conflict error. */
function resolveConfigSetMode(params) {
if (params.hasBatchMode) {
if (params.hasRefBuilderOptions || params.hasProviderBuilderOptions) return {
ok: false,
error: "batch mode (--batch-json/--batch-file) cannot be combined with ref builder (--ref-*) or provider builder (--provider-*) flags."
};
return {
ok: true,
mode: "batch"
};
}
if (params.hasRefBuilderOptions && params.hasProviderBuilderOptions) return {
ok: false,
error: "choose exactly one mode: ref builder (--ref-provider/--ref-source/--ref-id) or provider builder (--provider-*), not both."
};
if (params.hasRefBuilderOptions) return {
ok: true,
mode: "ref_builder"
};
if (params.hasProviderBuilderOptions) return {
ok: true,
mode: "provider_builder"
};
return {
ok: true,
mode: params.strictJson ? "json" : "value"
};
}
//#endregion
//#region src/cli/config-cli.ts
function normalizeAgentDefaultModelValueForConfigMutation(value) {
if (typeof value === "string") return normalizeAgentModelRefForConfig(value);
if (!isRecord(value)) return value;
const next = { ...value };
if (typeof next.primary === "string") next.primary = normalizeAgentModelRefForConfig(next.primary);
if (Array.isArray(next.fallbacks)) next.fallbacks = next.fallbacks.map((fallback) => typeof fallback === "string" ? normalizeAgentModelRefForConfig(fallback) : fallback);
return next;
}
function normalizeAgentListModelRefsForConfigMutation(value) {
if (!Array.isArray(value)) return value;
let mutated = false;
const next = value.map((agent) => {
if (!isRecord(agent)) return agent;
let nextAgent = agent;
if (Object.hasOwn(agent, "model")) {
const model = normalizeAgentDefaultModelValueForConfigMutation(agent.model);
if (model !== agent.model) {
nextAgent = {
...nextAgent,
model
};
mutated = true;
}
}
if (isRecord(agent.models)) {
const models = normalizeAgentModelMapForConfig(agent.models);
if (models !== agent.models) {
nextAgent = {
...nextAgent,
models
};
mutated = true;
}
}
return nextAgent;
});
return mutated ? next : value;
}
function normalizeProviderCatalogModelsForConfigMutation(provider, models) {
if (!Array.isArray(models)) return models;
let mutated = false;
const next = models.map((model) => {
if (!isRecord(model) || typeof model.id !== "string") return model;
const trimmed = model.id.trim();
if (!trimmed) return model;
const id = normalizeConfiguredProviderCatalogModelId(provider, trimmed);
if (id === model.id) return model;
mutated = true;
return {
...model,
id
};
});
return mutated ? next : models;
}
function normalizeModelProviderRefsForConfigMutation(providers) {
if (!isRecord(providers)) return providers;
let mutated = false;
const nextProviders = { ...providers };
for (const [provider, providerConfig] of Object.entries(providers)) {
if (!isRecord(providerConfig)) continue;
const models = normalizeProviderCatalogModelsForConfigMutation(provider, providerConfig.models);
if (models === providerConfig.models) continue;
nextProviders[provider] = {
...providerConfig,
models
};
mutated = true;
}
return mutated ? nextProviders : providers;
}
function normalizeConfigMutationModelRefs(cfg) {
const defaults = cfg.agents?.defaults;
const agentList = cfg.agents?.list;
const providers = cfg.models?.providers;
const normalizedAgentList = normalizeAgentListModelRefsForConfigMutation(agentList);
const normalizedProviders = normalizeModelProviderRefsForConfigMutation(providers);
return {
...cfg,
...defaults || normalizedAgentList !== agentList ? { agents: {
...cfg.agents,
...defaults ? { defaults: {
...defaults,
...defaults.model !== void 0 ? { model: normalizeAgentDefaultModelValueForConfigMutation(defaults.model) } : void 0,
...defaults.models !== void 0 ? { models: normalizeAgentModelMapForConfig(defaults.models) } : void 0
} } : void 0,
...normalizedAgentList !== agentList ? { list: normalizedAgentList } : void 0
} } : void 0,
...normalizedProviders !== providers ? { models: {
...cfg.models,
providers: normalizedProviders
} } : void 0
};
}
function normalizeConfigMutationExplicitSetPath(path) {
if (path.length >= 4 && path[0] === "agents" && path[1] === "defaults" && path[2] === "models") {
const normalizedModelId = normalizeAgentModelRefForConfig(path[3]);
return normalizedModelId === path[3] ? path : [
...path.slice(0, 3),
normalizedModelId,
...path.slice(4)
];
}
return path;
}
const GATEWAY_AUTH_MODE_PATH = [
"gateway",
"auth",
"mode"
];
const SECRET_PROVIDER_PATH_PREFIX = ["secrets", "providers"];
const PLUGIN_INSTALL_RECORD_PATH_PREFIX = ["plugins", "installs"];
const CONFIG_SET_EXAMPLE_VALUE = formatCliCommand("openclaw config set gateway.port 19001 --strict-json");
const CONFIG_SET_EXAMPLE_REF = formatCliCommand("openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN");
const CONFIG_SET_EXAMPLE_PROVIDER = formatCliCommand("openclaw config set secrets.providers.vault --provider-source file --provider-path /etc/openclaw/secrets.json --provider-mode json");
const CONFIG_SET_EXAMPLE_BATCH = formatCliCommand("openclaw config set --batch-file ./config-set.batch.json --dry-run");
const CONFIG_PATCH_EXAMPLE_FILE = formatCliCommand("openclaw config patch --file ./openclaw.patch.json5 --dry-run");
const CONFIG_PATCH_EXAMPLE_STDIN = formatCliCommand("openclaw config patch --stdin");
const CONFIG_SET_DESCRIPTION = [
"Set config values by path (value mode, ref/provider builder mode, or batch JSON mode).",
"Examples:",
CONFIG_SET_EXAMPLE_VALUE,
CONFIG_SET_EXAMPLE_REF,
CONFIG_SET_EXAMPLE_PROVIDER,
CONFIG_SET_EXAMPLE_BATCH
].join("\n");
const CONFIG_PATCH_DESCRIPTION = [
"Patch config from a JSON5 object in one validated write.",
"Objects merge recursively, arrays/scalars replace, and null deletes a path.",
"Examples:",
CONFIG_PATCH_EXAMPLE_FILE,
CONFIG_PATCH_EXAMPLE_STDIN
].join("\n");
const CONFIG_SET_POLICY_ERROR_MAX_ISSUES = 5;
const CONFIG_PATCH_STDIN_MAX_BYTES = 1024 * 1024;
var ConfigSetDryRunValidationError = class extends Error {
constructor(result) {
super("config set dry-run validation failed");
this.result = result;
this.name = "ConfigSetDryRunValidationError";
}
};
function isIndexSegment(raw) {
return parseIndexSegment(raw) !== void 0;
}
function parseIndexSegment(raw) {
return parseConfigPathArrayIndex(raw);
}
function parseBracketPathSegment(raw, fullPath) {
const trimmed = raw.trim();
if (!trimmed) throw new Error(`Invalid path (empty "[]"): ${fullPath}`);
if (trimmed.startsWith("\"") || trimmed.startsWith("'")) {
try {
const parsed = JSON5.parse(trimmed);
if (typeof parsed === "string" && parsed.trim()) return parsed;
} catch (err) {
throw new Error(`Invalid path bracket string (${trimmed}): ${fullPath}`, { cause: err });
}
throw new Error(`Invalid path bracket string (${trimmed}): ${fullPath}`);
}
return trimmed;
}
function assertNotWhitespaceSegment(current, raw) {
if (current.length > 0 && !current.trim()) throw new Error(`Invalid path (empty segment): ${raw}`);
}
function parsePath(raw) {
const trimmed = raw.trim();
if (!trimmed) return [];
const parts = [];
let current = "";
let segmentEmitted = false;
let i = 0;
while (i < trimmed.length) {
const ch = trimmed[i];
if (ch === "\\") {
const next = trimmed[i + 1];
if (next) current += next;
i += 2;
continue;
}
if (ch === ".") {
assertNotWhitespaceSegment(current, raw);
if (!segmentEmitted && !current.trim()) throw new Error(`Invalid path (empty segment): ${raw}`);
if (current) parts.push(current);
current = "";
segmentEmitted = false;
i += 1;
continue;
}
if (ch === "[") {
assertNotWhitespaceSegment(current, raw);
if (!current.trim() && !segmentEmitted && parts.length > 0) throw new Error(`Invalid path (empty segment): ${raw}`);
if (current) parts.push(current);
current = "";
const close = trimmed.indexOf("]", i);
if (close === -1) throw new Error(`Invalid path (missing "]"): ${raw}`);
const inside = trimmed.slice(i + 1, close).trim();
if (!inside) throw new Error(`Invalid path (empty "[]"): ${raw}`);
parts.push(parseBracketPathSegment(inside, raw));
segmentEmitted = true;
i = close + 1;
continue;
}
current += ch;
i += 1;
}
if (!segmentEmitted && !current.trim()) throw new Error(`Invalid path (empty segment): ${raw}`);
if (current) parts.push(current);
return normalizeStringEntries(parts);
}
function parseValue(raw, opts) {
const trimmed = raw.trim();
if (opts.strictJson) try {
return JSON.parse(trimmed);
} catch (err) {
throw new Error(formatStrictJsonParseFailure({
value: raw,
cause: err
}), { cause: err });
}
try {
return JSON5.parse(trimmed);
} catch {
return raw;
}
}
function hasOwnPathKey(value, key) {
return Object.hasOwn(value, key);
}
function formatDoctorHint(message) {
return `Run \`${formatCliCommand("openclaw doctor --fix")}\` ${message}`;
}
function formatInvalidConfigRepairHint(snapshot, doctorMessage) {
return isPluginPackagingRuntimeOutputInvalidConfigSnapshot(snapshot) ? formatPluginPackagingRuntimeOutputRecoveryHint() : formatDoctorHint(doctorMessage);
}
function formatUnsupportedSecretRefPolicyFailureMessage(issues) {
const lines = ["Config policy validation failed: unsupported SecretRef usage was detected.", ...issues.slice(0, CONFIG_SET_POLICY_ERROR_MAX_ISSUES).map((issue) => `- ${issue}`)];
if (issues.length > CONFIG_SET_POLICY_ERROR_MAX_ISSUES) lines.push(`- ... ${issues.length - CONFIG_SET_POLICY_ERROR_MAX_ISSUES} more`);
return lines.join("\n");
}
function validatePathSegments(path) {
for (const segment of path) if (!isIndexSegment(segment) && isBlockedObjectKey(segment)) throw new Error(`Invalid path segment: ${segment}`);
}
function getAtPath(root, path) {
let current = root;
for (const segment of path) {
if (!current || typeof current !== "object") return { found: false };
if (Array.isArray(current)) {
if (!isIndexSegment(segment)) return { found: false };
const index = parseIndexSegment(segment);
if (index === void 0 || index >= current.length) return { found: false };
current = current[index];
continue;
}
const record = current;
if (!hasOwnPathKey(record, segment)) return { found: false };
current = record[segment];
}
return {
found: true,
value: current
};
}
function isSchemaRecord(value) {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
function schemaTypes(schema) {
if (typeof schema.type === "string") return new Set([schema.type]);
if (Array.isArray(schema.type)) return new Set(schema.type.filter((entry) => typeof entry === "string"));
return /* @__PURE__ */ new Set();
}
function schemaAlternatives(schema, seen = /* @__PURE__ */ new Set()) {
if (seen.has(schema)) return [];
seen.add(schema);
const alternatives = [schema];
for (const key of [
"anyOf",
"oneOf",
"allOf"
]) {
const entries = schema[key];
if (!Array.isArray(entries)) continue;
for (const entry of entries) if (isSchemaRecord(entry)) alternatives.push(...schemaAlternatives(entry, seen));
}
return alternatives;
}
function schemaLooksArray(schema) {
return schemaTypes(schema).has("array") || isSchemaRecord(schema.items) || Array.isArray(schema.items);
}
function schemaLooksObject(schema) {
return schemaTypes(schema).has("object") || isSchemaRecord(schema.properties) || schema.additionalProperties === true || isSchemaRecord(schema.additionalProperties);
}
function propertySchema(schema, segment) {
const schemas = [];
for (const alternative of schemaAlternatives(schema)) {
if (schemaLooksArray(alternative)) {
const index = parseIndexSegment(segment);
if (index !== void 0) {
const indexedItem = Array.isArray(alternative.items) ? alternative.items[index] : alternative.items;
if (isSchemaRecord(indexedItem)) schemas.push(indexedItem);
}
continue;
}
const explicit = (isSchemaRecord(alternative.properties) ? alternative.properties : void 0)?.[segment];
if (isSchemaRecord(explicit)) {
schemas.push(explicit);
continue;
}
if (isSchemaRecord(alternative.additionalProperties)) schemas.push(alternative.additionalProperties);
}
return schemas;
}
function schemasAtPath(schema, path) {
if (!schema) return [];
let schemas = [schema];
for (const segment of path) {
schemas = schemas.flatMap((candidate) => propertySchema(candidate, segment));
if (schemas.length === 0) return [];
}
return schemas;
}
function schemaPrefersArrayAtPath(schema, path) {
const candidates = schemasAtPath(schema, path).flatMap((candidate) => schemaAlternatives(candidate));
if (candidates.length === 0) return;
const hasArray = candidates.some((candidate) => schemaLooksArray(candidate));
const hasObject = candidates.some((candidate) => schemaLooksObject(candidate));
if (hasArray && !hasObject) return true;
if (hasObject && !hasArray) return false;
}
function shouldCreateArrayForMissingPathSegment(params) {
if (!params.next || params.options?.numericObjectKeys || !isIndexSegment(params.next)) return false;
const parentPath = params.path.slice(0, params.segmentIndex + 1);
const schemaPreference = schemaPrefersArrayAtPath(params.options?.schema, parentPath);
if (schemaPreference !== void 0) return schemaPreference;
return true;
}
function setAtPath(root, path, value, options) {
let current = root;
for (let i = 0; i < path.length - 1; i += 1) {
const segment = path[i];
const next = path[i + 1];
const nextIsIndex = shouldCreateArrayForMissingPathSegment({
path,
segmentIndex: i,
next,
options
});
if (Array.isArray(current)) {
if (!isIndexSegment(segment)) throw new Error(`Expected numeric index for array segment "${segment}"`);
const index = parseIndexSegment(segment);
if (index === void 0) throw new Error(`Expected numeric index for array segment "${segment}"`);
const existing = current[index];
if (!existing || typeof existing !== "object") current[index] = nextIsIndex ? [] : {};
current = current[index];
continue;
}
if (!current || typeof current !== "object") throw new Error(`Cannot traverse into "${segment}" (not an object)`);
const record = current;
const existing = hasOwnPathKey(record, segment) ? record[segment] : void 0;
if (!existing || typeof existing !== "object") record[segment] = nextIsIndex ? [] : {};
current = record[segment];
}
const last = path[path.length - 1];
if (Array.isArray(current)) {
if (!isIndexSegment(last)) throw new Error(`Expected numeric index for array segment "${last}"`);
const index = parseIndexSegment(last);
if (index === void 0) throw new Error(`Expected numeric index for array segment "${last}"`);
current[index] = value;
return;
}
if (!current || typeof current !== "object") throw new Error(`Cannot set "${last}" (parent is not an object)`);
current[last] = value;
}
function modelArrayIds(value) {
if (!Array.isArray(value)) return null;
const ids = /* @__PURE__ */ new Set();
for (const entry of value) {
if (!isRecord(entry) || typeof entry.id !== "string" || !entry.id.trim()) return null;
ids.add(entry.id.trim());
}
return ids;
}
function mergeModelArrays(existing, patch) {
const merged = [...existing];
const indexById = /* @__PURE__ */ new Map();
for (const [index, entry] of merged.entries()) if (isRecord(entry) && typeof entry.id === "string" && entry.id.trim()) indexById.set(entry.id.trim(), index);
for (const entry of patch) {
if (!isRecord(entry) || typeof entry.id !== "string" || !entry.id.trim()) {
merged.push(entry);
continue;
}
const id = entry.id.trim();
const existingIndex = indexById.get(id);
if (existingIndex === void 0) {
indexById.set(id, merged.length);
merged.push(entry);
continue;
}
const existingEntry = merged[existingIndex];
merged[existingIndex] = isRecord(existingEntry) ? {
...existingEntry,
...entry
} : entry;
}
return merged;
}
function mergeConfigValue(existing, patch, path) {
if (isProviderModelListPath(path) && Array.isArray(existing) && Array.isArray(patch)) return mergeModelArrays(existing, patch);
if (isRecord(existing) && isRecord(patch)) {
const next = { ...existing };
for (const [key, value] of Object.entries(patch)) next[key] = hasOwnPathKey(next, key) && isRecord(next[key]) && isRecord(value) ? mergeConfigValue(next[key], value, [...path, key]) : value;
return next;
}
throw new Error(`Cannot merge ${toDotPath(path)}; use --replace to replace intentionally.`);
}
function mergeAtPath(root, path, value, options) {
const existing = getAtPath(root, path);
if (!existing.found) {
setAtPath(root, path, value, options);
return;
}
setAtPath(root, path, mergeConfigValue(existing.value, value, path), options);
}
function isProviderModelListPath(path) {
return path.length === 4 && path[0] === "models" && path[1] === "providers" && path[3] === "models";
}
function isProtectedMapReplacementPath(path) {
if (path.join(".") === "agents.defaults.models") return true;
if (path.join(".") === "models.providers") return true;
if (path.length === 3 && path[0] === "models" && path[1] === "providers") return true;
if (path.join(".") === "plugins.entries") return true;
if (path.join(".") === "auth.profiles") return true;
return false;
}
function isProtectedArrayReplacementPath(path) {
return isProviderModelListPath(path) || path.join(".") === "agents.list";
}
function formatRemovedEntries(entries) {
const visible = entries.slice(0, 6);
const suffix = entries.length > visible.length ? `, ... ${entries.length - visible.length} more` : "";
return `${visible.join(", ")}${suffix}`;
}
function assertNonDestructiveReplacement(params) {
if (params.allowReplace) return;
const existing = getAtPath(params.root, params.path);
if (!existing.found) return;
const pathLabel = toDotPath(params.path);
if (isProtectedMapReplacementPath(params.path) && isRecord(existing.value)) {
if (!isRecord(params.value)) return;
const nextKeys = new Set(Object.keys(params.value));
const removed = Object.keys(existing.value).filter((key) => !nextKeys.has(key));
if (removed.length > 0) throw new Error(`Refusing to replace ${pathLabel}; it would remove existing entries: ${formatRemovedEntries(removed)}. Use --merge to merge object values or --replace to replace intentionally.`);
}
if (isProtectedArrayReplacementPath(params.path)) {
const existingIds = modelArrayIds(existing.value);
const nextIds = modelArrayIds(params.value);
if (!existingIds || !nextIds) return;
const removed = [...existingIds].filter((id) => !nextIds.has(id));
if (removed.length > 0) throw new Error(`Refusing to replace ${pathLabel}; it would remove existing entries: ${formatRemovedEntries(removed)}. Use --merge to merge by id or --replace to replace intentionally.`);
}
}
function unsetAtPath(root, path) {
let current = root;
for (let i = 0; i < path.length - 1; i += 1) {
const segment = path[i];
if (!current || typeof current !== "object") return { removed: false };
if (Array.isArray(current)) {
if (!isIndexSegment(segment)) return { removed: false };
const index = parseIndexSegment(segment);
if (index === void 0 || index >= current.length) return { removed: false };
current = current[index];
continue;
}
const record = current;
if (!hasOwnPathKey(record, segment)) return { removed: false };
current = record[segment];
}
const last = path[path.length - 1];
if (Array.isArray(current)) {
if (!isIndexSegment(last)) return { removed: false };
const index = parseIndexSegment(last);
if (index === void 0 || index >= current.length) return { removed: false };
current.splice(index, 1);
return {
removed: true,
leafContainer: "array"
};
}
if (!current || typeof current !== "object") return { removed: false };
const record = current;
if (!hasOwnPathKey(record, last)) return { removed: false };
delete record[last];
return {
removed: true,
leafContainer: "object"
};
}
async function loadValidConfig(runtime = defaultRuntime) {
const snapshot = await readConfigFileSnapshot();
if (snapshot.valid) return snapshot;
runtime.error(`OpenClaw config is invalid: ${shortenHomePath(snapshot.path)}`);
for (const line of formatConfigIssueLines(snapshot.issues, "-", { normalizeRoot: true })) runtime.error(line);
runtime.error(formatInvalidConfigRepairHint(snapshot, "to repair, then retry."));
runtime.exit(1);
return snapshot;
}
function parseRequiredPath(path) {
const parsedPath = parsePath(path);
if (parsedPath.length === 0) throw new Error("Path is empty.");
validatePathSegments(parsedPath);
return parsedPath;
}
function pathEquals(path, expected) {
return path.length === expected.length && path.every((segment, index) => segment === expected[index]);
}
function pruneInactiveGatewayAuthCredentials(params) {
if (!params.operations.some((operation) => pathEquals(operation.requestedPath, GATEWAY_AUTH_MODE_PATH))) return [];
const gatewayRaw = params.root.gateway;
if (!gatewayRaw || typeof gatewayRaw !== "object" || Array.isArray(gatewayRaw)) return [];
const authRaw = gatewayRaw.auth;
if (!authRaw || typeof authRaw !== "object" || Array.isArray(authRaw)) return [];
const auth = authRaw;
const mode = normalizeOptionalString(auth.mode) ?? "";
const removedPaths = [];
const remove = (key) => {
if (Object.hasOwn(auth, key)) {
delete auth[key];
removedPaths.push(`gateway.auth.${key}`);
}
};
if (mode === "token") remove("password");
else if (mode === "password") remove("token");
else if (mode === "trusted-proxy") {
remove("token");
remove("password");
}
return removedPaths;
}
function toDotPath(path) {
return path.join(".");
}
function parseSecretRefSource(raw, label) {
const source = raw.trim();
if (source === "env" || source === "file" || source === "exec") return source;
throw new Error(`${label} must be one of: env, file, exec.`);
}
function parseSecretRefBuilder(params) {
const provider = params.provider.trim();
if (!provider) throw new Error(`${params.fieldPrefix}.provider is required.`);
if (!isValidSecretProviderAlias(provider)) throw new Error(`${params.fieldPrefix}.provider must match /^[a-z][a-z0-9_-]{0,63}$/ (example: "default").`);
const source = parseSecretRefSource(params.source, `${params.fieldPrefix}.source`);
const id = params.id.trim();
if (!id) throw new Error(`${params.fieldPrefix}.id is required.`);
if (source === "env" && !isValidEnvSecretRefId(id)) throw new Error(`${params.fieldPrefix}.id must match /^[A-Z][A-Z0-9_]{0,127}$/ for env refs.`);
if (source === "file" && !isValidFileSecretRefId(id)) throw new Error(`${params.fieldPrefix}.id must be an absolute JSON pointer (or "value" for singleValue mode).`);
if (source === "exec") {
if (!validateExecSecretRefId(id).ok) throw new Error(formatExecSecretRefIdValidationMessage());
}
return {
source,
provider,
id
};
}
function parseOptionalPositiveInteger(raw, flag) {
if (raw === void 0) return;
const trimmed = raw.trim();
if (!trimmed) throw new Error(`${flag} must not be empty.`);
const parsed = parseStrictPositiveInteger(trimmed);
if (parsed === void 0) throw new Error(`${flag} must be a positive integer.`);
return parsed;
}
function parseProviderEnvEntries(entries) {
if (!entries || entries.length === 0) return;
const env = {};
for (const entry of entries) {
const separator = entry.indexOf("=");
if (separator <= 0) throw new Error(`--provider-env expects KEY=VALUE entries (received: "${entry}").`);
const key = entry.slice(0, separator).trim();
if (!key) throw new Error(`--provider-env key must not be empty (received: "${entry}").`);
env[key] = entry.slice(separator + 1);
}
return Object.keys(env).length > 0 ? env : void 0;
}
function parseProviderAliasPath(path) {
if (!(path.length === 3 && path[0] === SECRET_PROVIDER_PATH_PREFIX[0] && path[1] === SECRET_PROVIDER_PATH_PREFIX[1])) throw new Error("Provider builder mode requires path \"secrets.providers.<alias>\" (example: secrets.providers.vault).");
const alias = path[2] ?? "";
if (!isValidSecretProviderAlias(alias)) throw new Error(`Provider alias "${alias}" must match /^[a-z][a-z0-9_-]{0,63}$/ (example: "default").`);
return alias;
}
function buildProviderFromBuilder(opts) {
const sourceRaw = opts.providerSource?.trim();
if (!sourceRaw) throw new Error("--provider-source is required in provider builder mode.");
const source = parseSecretRefSource(sourceRaw, "--provider-source");
const timeoutMs = parseOptionalPositiveInteger(opts.providerTimeoutMs, "--provider-timeout-ms");
const maxBytes = parseOptionalPositiveInteger(opts.providerMaxBytes, "--provider-max-bytes");
const noOutputTimeoutMs = parseOptionalPositiveInteger(opts.providerNoOutputTimeoutMs, "--provider-no-output-timeout-ms");
const maxOutputBytes = parseOptionalPositiveInteger(opts.providerMaxOutputBytes, "--provider-max-output-bytes");
const providerEnv = parseProviderEnvEntries(opts.providerEnv);
let provider;
if (source === "env") {
const allowlist = normalizeStringEntries(opts.providerAllowlist);
for (const envName of allowlist) if (!isValidEnvSecretRefId(envName)) throw new Error(`--provider-allowlist entry "${envName}" must match /^[A-Z][A-Z0-9_]{0,127}$/.`);
provider = {
source: "env",
...allowlist.length > 0 ? { allowlist } : {}
};
} else if (source === "file") {
const filePath = opts.providerPath?.trim();
if (!filePath) throw new Error("--provider-path is required when --provider-source file is used.");
const modeRaw = opts.providerMode?.trim();
if (modeRaw && modeRaw !== "singleValue" && modeRaw !== "json") throw new Error("--provider-mode must be one of: singleValue, json.");
const mode = modeRaw === "singleValue" || modeRaw === "json" ? modeRaw : void 0;
provider = {
source: "file",
path: filePath,
...mode ? { mode } : {},
...timeoutMs !== void 0 ? { timeoutMs } : {},
...maxBytes !== void 0 ? { maxBytes } : {},
...opts.providerAllowInsecurePath ? { allowInsecurePath: true } : {}
};
} else {
const command = opts.providerCommand?.trim();
if (!command) throw new Error("--provider-command is required when --provider-source exec is used.");
provider = {
source: "exec",
command,
...opts.providerArg && opts.providerArg.length > 0 ? { args: opts.providerArg.map((entry) => entry.trim()) } : {},
...timeoutMs !== void 0 ? { timeoutMs } : {},
...noOutputTimeoutMs !== void 0 ? { noOutputTimeoutMs } : {},
...maxOutputBytes !== void 0 ? { maxOutputBytes } : {},
...opts.providerJsonOnly ? { jsonOnly: true } : {},
...providerEnv ? { env: providerEnv } : {},
...opts.providerPassEnv && opts.providerPassEnv.length > 0 ? { passEnv: normalizeStringEntries(opts.providerPassEnv) } : {},
...opts.providerTrustedDir && opts.providerTrustedDir.length > 0 ? { trustedDirs: normalizeStringEntries(opts.providerTrustedDir) } : {},
...opts.providerAllowInsecurePath ? { allowInsecurePath: true } : {},
...opts.providerAllowSymlinkCommand ? { allowSymlinkCommand: true } : {}
};
}
const validated = SecretProviderSchema.safeParse(provider);
if (!validated.success) {
const issue = validated.error.issues[0];
const issuePath = issue?.path?.join(".") ?? "<provider>";
const issueMessage = issue?.message ?? "Invalid provider config.";
throw new Error(`Provider builder config invalid at ${issuePath}: ${issueMessage}`);
}
return validated.data;
}
function parseSecretRefFromUnknown(value, label) {
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object with source/provider/id.`);
const candidate = value;
if (typeof candidate.provider !== "string" || typeof candidate.source !== "string" || typeof candidate.id !== "string") throw new Error(`${label} must include string fields: source, provider, id.`);
return parseSecretRefBuilder({
provider: candidate.provider,
source: candidate.source,
id: candidate.id,
fieldPrefix: label
});
}
function buildRefAssignmentOperation(params) {
const resolved = resolveConfigSecretTargetByPath(params.requestedPath);
if (resolved?.entry.secretShape === "sibling_ref" && resolved.refPathSegments) return {
inputMode: params.inputMode,
requestedPath: params.requestedPath,
setPath: resolved.refPathSegments,
value: params.ref,
schemaValidated: true,
touchedSecretTargetPath: toDotPath(resolved.pathSegments),
assignedRef: params.ref,
...resolved.providerId ? { touchedProviderAlias: resolved.providerId } : {}
};
return {
inputMode: params.inputMode,
requestedPath: params.requestedPath,
setPath: params.requestedPath,
value: params.ref,
...resolved ? { schemaValidated: true } : {},
touchedSecretTargetPath: resolved ? toDotPath(resolved.pathSegments) : toDotPath(params.requestedPath),
assignedRef: params.ref,
...resolved?.providerId ? { touchedProviderAlias: resolved.providerId } : {}
};
}
function parseProviderAliasFromTargetPath(path) {
if (path.length >= 3 && path[0] === SECRET_PROVIDER_PATH_PREFIX[0] && path[1] === SECRET_PROVIDER_PATH_PREFIX[1]) return path[2] ?? null;
return null;
}
function touchesSecretProviderCollection(path) {
return path.length === 1 && path[0] === "secrets" || path.length === 2 && path[0] === "secrets" && path[1] === "providers";
}
function touchesSecretDefaults(path) {
return path.length === 1 && path[0] === "secrets" || path.length === 2 && path[0] === "secrets" && path[1] === "defaults";
}
function buildValueAssignmentOperation(params) {
const resolved = resolveConfigSecretTargetByPath(params.requestedPath);
const providerAlias = parseProviderAliasFromTargetPath(params.requestedPath);
const coercedRef = coerceSecretRef(params.value);
return {
inputMode: params.inputMode,
requestedPath: params.requestedPath,
setPath: params.requestedPath,
value: params.value,
...resolved ? { touchedSecretTargetPath: toDotPath(resolved.pathSegments) } : {},
...providerAlias ? { touchedProviderAlias: providerAlias } : {},
...coercedRef ? { assignedRef: coercedRef } : {}
};
}
function parseBatchOperations(entries) {
const operations = [];
for (const [index, entry] of entries.entries()) {
const path = parseRequiredPath(entry.path);
if (entry.ref !== void 0) {
const ref = parseSecretRefFromUnknown(entry.ref, `batch[${index}].ref`);
operations.push(buildRefAssignmentOperation({
requestedPath: path,
ref,
inputMode: "json"
}));
continue;
}
if (entry.provider !== void 0) {
const alias = parseProviderAliasPath(path);
const validated = SecretProviderSchema.safeParse(entry.provider);
if (!validated.success) {
const issue = validated.error.issues[0];
const issuePath = issue?.path?.join(".") ?? "<provider>";
throw new Error(`batch[${index}].provider invalid at ${issuePath}: ${issue?.message ?? ""}`);
}
operations.push({
inputMode: "json",
requestedPath: path,
setPath: path,
value: validated.data,
schemaValidated: true,
touchedProviderAlias: alias
});
continue;
}
operations.push(buildValueAssignmentOperation({
requestedPath: path,
value: entry.value,
inputMode: "json"
}));
}
return operations;
}
function configPatchModeError(message) {
return /* @__PURE__ */ new Error(`config patch mode error: ${message}`);
}
async function readStdinText() {
const chunks = [];
let bytes = 0;
if (process.stdin.isTTY) throw configPatchModeError("--stdin refuses to read from an interactive terminal; pipe input or use --file <path>.");
process.stdin.setEncoding("utf8");
for await (const chunk of process.stdin) {
const text = String(chunk);
bytes += Buffer.byteLength(text, "utf8");
if (bytes > CONFIG_PATCH_STDIN_MAX_BYTES) throw configPatchModeError(`--stdin input exceeds ${CONFIG_PATCH_STDIN_MAX_BYTES} bytes; use --file <path> for larger patches.`);
chunks.push(text);
}
return chunks.join("");
}
async function readConfigPatchInput(opts) {
const file = normalizeOptionalString(opts.file);
const stdin = Boolean(opts.stdin);
if (Boolean(file) === stdin) throw configPatchModeError("provide exactly one of --file <path> or --stdin.");
const sourceLabel = stdin ? "--stdin" : "--file";
const raw = stdin ? await readStdinText() : fs.readFileSync(file, "utf8");
try {
return JSON5.parse(raw);
} catch (err) {
throw new Error(`Failed to parse ${sourceLabel} as JSON5: ${String(err)}`, { cause: err });
}
}
function parseReplacePaths(paths) {
return (paths ?? []).map((path) => parseRequiredPath(path));
}
function pathKey(path) {
return JSON.stringify(path);
}
function buildDeleteOperation(path) {
return {
inputMode: "json",
requestedPath: path,
setPath: path,
value: void 0,
mutation: "delete"
};
}
function buildUnsetOperation(path) {
const resolved = resolveConfigSecretTargetByPath(path);
const providerAlias = parseProviderAliasFromTargetPath(path);
return {
inputMode: "unset",
requestedPath: path,
setPath: path,
value: void 0,
mutation: "delete",
...touchesSecretProviderCollection(path) || touchesSecretDefaults(path) ? { touchesAllSecretRefs: true } : {},
...resolved ? { touchedSecretTargetPath: toDotPath(resolved.pathSegments) } : {},
...providerAlias ? { touchedProviderAlias: providerAlias } : {}
};
}
function buildApplyValueOperation(params) {
if (isRecord(params.value) ? coerceSecretRef(params.value) : null) return {
...buildRefAssignmentOperation({
requestedPath: params.path,
ref: parseSecretRefFromUnknown(params.value, `patch.${toDotPath(params.path)}`),
inputMode: "json"
}),
...params.mutation ? { mutation: params.mutation } : {}
};
return {
...buildValueAssignmentOperation({
requestedPath: params.path,
value: params.value,
inputMode: "json"
}),
...params.mutation ? { mutation: params.mutation } : {}
};
}
function buildConfigPatchOperations(params) {
if (!isRecord(params.patch)) throw configPatchModeError("input must be a JSON5 object patch.");
const operations = [];
const replacePathKeys = new Set(params.replacePaths.map(pathKey));
const matchedReplacePathKeys = /* @__PURE__ */ new Set();
const visit = (value, path) => {
validatePathSegments(path);
const replacementKey = pathKey(path);
if (path.length > 0 && replacePathKeys.has(replacementKey)) {
matchedReplacePathKeys.add(replacementKey);
operations.push(value === null ? buildDeleteOperation(path) : buildApplyValueOperation({
path,
value,
mutation: "replace"
}));
return;
}
if (path.length > 0 && value === null) {
operations.push(buildDeleteOperation(path));
return;
}
if (path.length > 0 && isRecord(value) && coerceSecretRef(value)) {
operations.push(buildApplyValueOperation({
path,
value
}));
return;
}
if (isRecord(value)) {
if (path.length > 0 && Object.keys(value).length === 0) {
operations.push(buildApplyValueOperation({
path,
value,
mutation: "merge"
}));
return;
}
for (const [key, child] of Object.entries(value)) visit(child, [...path, key]);
return;
}
if (path.length === 0) throw configPatchModeError("input must contain at least one config key.");
operations.push(buildApplyValueOperation({
path,
value
}));
};
visit(params.patch, []);
const unusedReplacePath = params.replacePaths.find((path) => !matchedReplacePathKeys.has(pathKey(path)));
if (unusedReplacePath) throw configPatchModeError(`--replace-path ${toDotPath(unusedReplacePath)} did not match any value in the input patch.`);
if (operations.length === 0) throw configPatchModeError("input patch did not contain any config updates.");
return operations;
}
function collectSecretRefsFromUnknown(value) {
const refs = [];
const visit = (candidate) => {
const ref = coerceSecretRef(candidate);
if (ref) {
refs.push(ref);
return;
}
if (Array.isArray(candidate)) {
for (const entry of candidate) visit(entry);
return;
}
if (isRecord(candidate)) for (const entry of Object.values(candidate)) visit(entry);
};
visit(value);
return refs;
}
function modeError(message) {
return /* @__PURE__ */ new Error(`config set mode error: ${message}`);
}
function buildSingleSetOperations(params) {
const pathProvided = typeof params.path === "string" && params.path.trim().length > 0;
const parsedPath = pathProvided ? parseRequiredPath(params.path) : null;
const strictJson = Boolean(params.opts.strictJson || params.opts.json);
const modeResolution = resolveConfigSetMode({
hasBatchMode: false,
hasRefBuilderOptions: hasRefBuilderOptions(params.opts),
hasProviderBuilderOptions: hasProviderBuilderOptions(params.opts),
strictJson
});
if (!modeResolution.ok) throw modeError(modeResolution.error);
if (modeResolution.mode === "ref_builder") {
if (!pathProvided || !parsedPath) throw modeError("ref builder mode requires <path>.");
if (params.value !== void 0) throw modeError("ref builder mode does not accept <value>.");
if (!params.opts.refProvider || !params.opts.refSource || !params.opts.refId) throw modeError("ref builder mode requires --ref-provider <alias>, --ref-source <env|file|exec>, and --ref-id <id>.");
return [buildRefAssignmentOperation({
requestedPath: parsedPath,
ref: parseSecretRefBuilder({
provider: params.opts.refProvider,
source: params.opts.refSource,
id: params.opts.refId,
fieldPrefix: "ref"
}),
inputMode: "builder"
})];
}
if (modeResolution.mode === "provider_builder") {
if (!pathProvided || !parsedPath) throw modeError("provider builder mode requires <path>.");
if (params.value !== void 0) throw modeError("provider builder mode does not accept <value>.");
const alias = parseProviderAliasPath(parsedPath);
return [{
inputMode: "builder",
requestedPath: parsedPath,
setPath: parsedPath,
value: buildProviderFromBuilder(params.opts),
schemaValidated: true,
touchedProviderAlias: alias
}];
}
if (!pathProvided || !parsedPath) throw modeError("value/json mode requires <path> when batch mode is not used.");
if (params.value === void 0) throw modeError("value/json mode requires <value>.");
return [buildValueAssignmentOperation({
requestedPath: parsedPath,
value: parseValue(params.value, { strictJson }),
inputMode: modeResolution.mode === "json" ? "json" : "value"
})];
}
function collectDryRunRefs(params) {
const refsByKey = /* @__PURE__ */ new Map();
const targetPaths = /* @__PURE__ */ new Set();
const providerAliases = /* @__PURE__ */ new Set();
let includeAllDiscoveredRefs = false;
for (const operation of params.operations) {
if (operation.assignedRef) refsByKey.set(secretRefKey(operation.assignedRef), operation.assignedRef);
for (const ref of collectSecretRefsFromUnknown(operation.value)) refsByKey.set(secretRefKey(ref), ref);
if (operation.touchedSecretTargetPath) targetPaths.add(operation.touchedSecretTargetPath);
if (operation.touchedProviderAlias) providerAliases.add(operation.touchedProviderAlias);
includeAllDiscoveredRefs ||= operation.touchesAllSecretRefs === true;
}
if (!includeAllDiscoveredRefs && targetPaths.size === 0 && providerAliases.size === 0) return [...refsByKey.values()];
const defaults = params.config.secrets?.defaults;
for (const target of discoverConfigSecretTargets(params.config)) {
const { ref } = resolveSecretInputRef({
value: target.value,
refValue: target.refValue,
defaults
});
if (!ref) continue;
if (includeAllDiscoveredRefs || targetPaths.has(target.path) || providerAliases.has(ref.provider)) refsByKey.set(secretRefKey(ref), ref);
}
return [...refsByKey.values()];
}
async function collectDryRunResolvabilityErrors(params) {
const failures = [];
for (const ref of params.refs) try {
await resolveSecretRefValue(ref, {
config: params.config,
env: process.env
});
} catch (err) {
failures.push({
kind: "resolvability",
message: String(err),
ref: `${ref.source}:${ref.provider}:${ref.id}`
});
}
return failures;
}
function collectDryRunStaticErrorsForSkippedExecRefs(params) {
const failures = [];
for (const ref of params.refs) {
const id = ref.id.trim();
const refLabel = `${ref.source}:${ref.provider}:${id}`;
if (!id) {
failures.push({
kind: "resolvability",
message: "Error: Secret reference id is empty.",
ref: refLabel
});
continue;
}
if (!isValidExecSecretRefId(id)) {
failures.push({
kind: "resolvability",
message: `Error: ${formatExecSecretRefIdValidationMessage()} (ref: ${refLabel}).`,
ref: refLabel
});
continue;
}
const providerConfig = params.config.secrets?.providers?.[ref.provider];
if (!providerConfig) {
failures.push({
kind: "resolvability",
message: `Error: Secret provider "${ref.provider}" is not configured (ref: ${refLabel}).`,
ref: refLabel
});
continue;
}
if (providerConfig.source !== ref.source) failures.push({
kind: "resolvability",
message: `Error: Secret provider "${ref.provider}" has source "${providerConfig.source}" but ref requests "${ref.source}".`,
ref: refLabel
});
}
return failures;
}
function selectDryRunRefsForResolution(params) {
const refsToResolve = [];
const skippedExecRefs = [];
for (const ref of params.refs) {
if (ref.source === "exec" && !params.allowExecInDryRun) {
skippedExecRefs.push(ref);
continue;
}
refsToResolve.push(ref);
}
return {
refsToResolve,
skippedExecRefs
};
}
function pathStartsWith(path, prefix) {
return prefix.every((segment, index) => path[index] === segment);
}
function formatPluginInstallConfigSetError() {
return [
"plugins.installs is managed by the plugin index and cannot be edited with config set.",
"",
"Use plugin commands instead:",
` ${formatCliCommand("openclaw plugins install <spec>")}`,
` ${formatCliCommand("openclaw plugins update <plugin-id>")}`,
` ${formatCliCommand("openclaw plugins uninstall <plugin-id>")}`
].join("\n");
}
function isAutoManagedMetaPath(path) {
return AUTO_MANAGED_CONFIG_META_PATHS.some((managedPath) => pathStartsWith(path, managedPath));
}
function valueHasAutoManagedChild(value, childPath) {
let cursor = value;
for (const segment of childPath) {
if (cursor === null || typeof cursor !== "object" || Array.isArray(cursor)) return false;
if (typeof segment !== "string") return false;
const record = cursor;
if (!Object.hasOwn(record, segment)) return false;
cursor = record[segment];
}
return cursor !== void 0;
}
function operationClobbersAncestorChild(operation, managedPath, options) {
if (operation.mutation === "delete") return true;
const childPath = managedPath.slice(operation.requestedPath.length);
if (operation.mutation === "merge" || Boolean(options.merge) && operation.mutation !== "replace") return valueHasAutoManagedChild(operation.value, childPath);
return true;
}
function findAutoManagedMetaTargets(operations, options = {}) {
const matches = [];
const seen = /* @__PURE__ */ new Set();
const record = (path) => {
const segments = [...path];
const key = toDotPath(segments);
if (seen.has(key)) return;
seen.add(key);
matches.push(segments);
};
for (const operation of operations) {
if (isAutoManagedMetaPath(operation.requestedPath)) {
record(operation.requestedPath);
continue;
}
for (const managedPath of AUTO_MANAGED_CONFIG_META_PATHS) {
if (operation.requestedPath.length >= managedPath.length) continue;
if (!pathStartsWith(managedPath, operation.requestedPath)) continue;
if (operationClobbersAncestorChild(operation, managedPath, options)) record(managedPath);
}
}
return matches;
}
function findAutoManagedMetaUnsetTargets(path) {
return findAutoManagedMetaTargets([{
inputMode: "json",
requestedPath: [...path],
setPath: [...path],
value: void 0,
mutation: "delete"
}]);
}
function formatAutoManagedMetaError(paths) {
const targets = paths.map((path) => toDotPath(path));
return [
`${targets.length === 1 ? targets[0] : targets.join(", ")} is auto-managed by OpenClaw and cannot be edited; the value would be overwritten on the next config write.`,
"",
"These fields are stamped on every config write to record the OpenClaw version and timestamp that produced the file."
].join("\n");
}
async function loadConfigMutationSchema() {
try {
return structuredClone((await readBestEffortRuntimeConfigSchema()).schema);
} catch {
return;
}
}
function collectDryRunSchemaErrors(params) {
const validated = validateConfigObjectRawWithPlugins(params.config);
if (validated.ok) return [];
return formatConfigIssueLines(validated.issues, "-", { normalizeRoot: true }).map((message) => ({
kind: "schema",
message
}));
}
function collectPluginIntegrationProviderErrors(params) {
const providers = params.config.secrets?.providers ?? {};
let validateAllProviders = false;
const touchedProviderAliases = /* @__PURE__ */ new Set();
for (const operation of params.operations) {
if (operation.touchedProviderAlias) touchedProviderAliases.add(operation.touchedProviderAlias);
if (operation.assignedRef) touchedProviderAliases.add(operation.assignedRef.provider);
for (const ref of collectSecretRefsFromUnknown(operation.value)) touchedProviderAliases.add(ref.provider);
if (touchesSecretProviderCollection(operation.setPath)) validateAllProviders = true;
}
if (!validateAllProviders && touchedProviderAliases.size === 0) return [];
const integrationProviders = [];
for (const [alias, provider] of Object.entries(providers)) {
if (!validateAllProviders && !touchedProviderAliases.has(alias)) continue;
if (isPluginIntegrationSecretProviderConfig(provider)) integrationProviders.push({
alias,
provider
});
}
if (integrationProviders.length === 0) return [];
const manifestRegistry = loadPluginMetadataSnapshot({
config: params.config,
env: process.env
}).manifestRegistry;
const errors = [];
for (const { alias, provider } of integrationProviders) {
const resolved = resolveSecretProviderIntegrationConfig({
manifestRegistry,
providerAlias: alias,
providerConfig: provider,
config: params.config,
env: process.env
});
if (!resolved.ok) errors.push({
kind: "schema",
message: `secrets.providers.${alias}: ${resolved.reason}`
});
}
return errors;
}
function dedupeDryRunErrors(errors) {
const deduped = [];
const seen = /* @__PURE__ */ new Set();
for (const error of errors) {
const key = error.kind === "resolvability" ? `${error.kind}\u0000${error.ref ?? ""}\u0000${error.message}` : `${error.kind}\u0000${error.message}`;
if (seen.has(key)) continue;
seen.add(key);
deduped.push(error);
}
return deduped;
}
function formatDryRunFailureMessage(params) {
const { errors, skippedExecRefs } = params;
const missingPathErrors = errors.filter((error) => error.kind === "missing-path");
const schemaErrors = errors.filter((error) => error.kind === "schema");
const resolveErrors = errors.filter((error) => error.kind === "resolvability");
const lines = [];
if (missingPathErrors.length > 0) lines.push(...missingPathErrors.map((error) => error.message));
if (schemaErrors.length > 0) {
lines.push("Dry run failed: config schema validation failed.");
lines.push(...schemaErrors.map((error) => `- ${error.message}`));
}
if (resolveErrors.length > 0) {
lines.push(`Dry run failed: ${resolveErrors.length} SecretRef assignment(s) could not be resolved.`);
lines.push(...resolveErrors.slice(0, 5).map((error) => `- ${error.ref ?? "<unknown-ref>"} -> ${error.message}`));
if (resolveErrors.length > 5) lines.push(`- ... ${resolveErrors.length - 5} more`);
}
if (skippedExecRefs > 0) lines.push(`Dry run note: skipped ${skippedExecRefs} exec SecretRef resolvability check(s). Re-run with --allow-exec to execute exec providers during dry-run.`);
return lines.join("\n");
}
async function runConfigOperations(params) {
const { runtime, operations, options } = params;
if (operations.some((operation) => pathStartsWith(operation.requestedPath, PLUGIN_INSTALL_RECORD_PATH_PREFIX))) throw new Error(formatPluginInstallConfigSetError());
const autoManagedMetaTargets = findAutoManagedMetaTargets(operations, { merge: options.merge });
if (autoManagedMetaTargets.length > 0) throw new Error(formatAutoManagedMetaError(autoManagedMetaTargets));
const snapshot = await loadValidConfig(runtime);
const next = structuredClone(snapshot.resolved);
const mutationSchema = await loadConfigMutationSchema();
const unsetPaths = [];
const explicitSetPaths = [];
for (const operation of operations) {
if (operation.mutation === "delete") {
unsetAtPath(next, operation.setPath);
unsetPaths.push(operation.setPath);
continue;
}
explicitSetPaths.push(operation.setPath);
if (operation.mutation === "merge" || options.merge && operation.mutation !== "replace") mergeAtPath(next, operation.setPath, operation.value, {
numericObjectKeys: params.successMode === "patch",
schema: mutationSchema
});
else {
assertNonDestructiveReplacement({
root: next,
path: operation.setPath,
value: operation.value,
allowReplace: options.replace || operation.mutation === "replace"
});
setAtPath(next, operation.setPath, operation.value, {
numericObjectKeys: params.successMode === "patch",
schema: mutationSchema
});
}
}
const removedGatewayAuthPaths = pruneInactiveGatewayAuthCredentials({
root: next,
operations
});
const nextConfig = normalizeConfigMutationModelRefs(next);
const normalizedExplicitSetPaths = explicitSetPaths.map(normalizeConfigMutationExplicitSetPath);
const policyIssueLines = formatConfigIssueLines(collectUnsupportedSecretRefPolicyIssues(nextConfig), "", { normalizeRoot: true }).map((line) => line.trim());
const pluginIntegrationProviderErrors = collectPluginIntegrationProviderErrors({
config: nextConfig,
operations
});
if (options.dryRun) {
const hasJsonMode = operations.some((operation) => operation.inputMode === "json");
const hasBuilderMode = operations.some((operation) => operation.inputMode === "builder");
const hasUnsetMode = operations.some((operation) => operation.inputMode === "unset");
const requiresFullSchemaValidation = operations.some((operation) => operation.inputMode === "unset" || operation.inputMode === "json" && operation.schemaValidated !== true);
const selectedDryRunRefs = selectDryRunRefsForResolution({
refs: hasJsonMode || hasBuilderMode || hasUnsetMode ? collectDryRunRefs({
config: nextConfig,
operations
}) : [],
allowExecInDryRun: Boolean(options.allowExec)
});
const errors = [];
if ((!hasJsonMode || !requiresFullSchemaValidation) && policyIssueLines.length > 0) errors.push(...policyIssueLines.map((message) => ({
kind: "schema",
message
})));
errors.push(...pluginIntegrationProviderErrors);
if (requiresFullSchemaValidation) errors.push(...collectDryRunSchemaErrors({ config: nextConfig }));
if (hasJsonMode || hasBuilderMode || hasUnsetMode) {
errors.push(...collectDryRunStaticErrorsForSkippedExecRefs({
refs: selectedDryRunRefs.skippedExecRefs,
config: nextConfig
}));
errors.push(...await collectDryRunResolvabilityErrors({
refs: selectedDryRunRefs.refsToResolve,
config: nextConfig
}));
}
const dedupedErrors = dedupeDryRunErrors(errors);
const dryRunResult = {
ok: dedupedErrors.length === 0,
operations: operations.length,
configPath: shortenHomePath(snapshot.path),
inputModes: uniqueValues(operations.map((operation) => operation.inputMode)),
checks: {
schema: requiresFullSchemaValidation || policyIssueLines.length > 0 || pluginIntegrationProviderErrors.length > 0,
resolvability: hasJsonMode || hasBuilderMode || hasUnsetMode,
resolvabilityComplete: (hasJsonMode || hasBuilderMode || hasUnsetMode) && selectedDryRunRefs.skippedExecRefs.length === 0
},
refsChecked: selectedDryRunRefs.refsToResolve.length,
skippedExecRefs: selectedDryRunRefs.skippedExecRefs.length,
...dedupedErrors.length > 0 ? { errors: dedupedErrors } : {}
};
if (dedupedErrors.length > 0) {
if (options.json) throw new ConfigSetDryRunValidationError(dryRunResult);
throw new Error(formatDryRunFailureMessage({
errors: dedupedErrors,
skippedExecRefs: selectedDryRunRefs.skippedExecRefs.length
}));
}
if (options.json) writeRuntimeJson(runtime, dryRunResult);
else {
if (!dryRunResult.checks.schema && !dryRunResult.checks.resolvability) runtime.log(info("Dry run note: value mode does not run schema/resolvability checks. Use --strict-json, builder flags, or batch mode to enable validation checks."));
if (dryRunResult.skippedExecRefs > 0) runtime.log(info(`Dry run note: skipped ${dryRunResult.skippedExecRefs} exec SecretRef resolvability check(s). Re-run with --allow-exec to execute exec providers during dry-run.`));
runtime.log(info(`Dry run successful: ${operations.length} update(s) validated against ${shortenHomePath(snapshot.path)}.`));
}
return;
}
if (policyIssueLines.length > 0) throw new Error(formatUnsupportedSecretRefPolicyFailureMessage(policyIssueLines));
if (pluginIntegrationProviderErrors.length > 0) throw new Error(["Config validation failed: plugin-managed SecretRef provider integration is invalid.", ...pluginIntegrationProviderErrors.map((error) => `- ${error.message}`)].join("\n"));
await replaceConfigFile({
nextConfig,
...snapshot.hash !== void 0 ? { baseHash: snapshot.hash } : {},
...unsetPaths.length > 0 || explicitSetPaths.length > 0 ? { writeOptions: {
...unsetPaths.length > 0 ? { unsetPaths } : {},
...normalizedExplicitSetPaths.length > 0 ? { explicitSetPaths: normalizedExplicitSetPaths } : {}
} } : {}
});
if (removedGatewayAuthPaths.length > 0) runtime.log(info(`Removed inactive ${removedGatewayAuthPaths.join(", ")} for gateway.auth.mode=${nextConfig.gateway?.auth?.mode ?? "<unset>"}.`));
if (params.successMode === "set" && operations.length === 1) {
const operation = operations[0];
const action = operation?.mutation === "delete" ? "Removed" : "Updated";
runtime.log(info(`${action} ${toDotPath(operation?.requestedPath ?? [])}. Restart the gateway to apply.`));
return;
}
if (params.successMode === "set") {
runtime.log(info(`Updated ${operations.length} config paths. Restart the gateway to apply.`));
return;
}
runtime.log(info(`Applied ${operations.length} config update(s). Restart the gateway to apply.`));
}
function handleConfigMutationError(params) {
if (params.options.dryRun && params.options.json && params.err instanceof ConfigSetDryRunValidationError) {
writeRuntimeJson(params.runtime, params.err.result);
params.runtime.exit(1);
return;
}
params.runtime.error(danger(String(params.err)));
params.runtime.exit(1);
}
async function runConfigSet(opts) {
const runtime = opts.runtime ?? defaultRuntime;
try {
const modeResolution = resolveConfigSetMode({
hasBatchMode: hasBatchMode(opts.cliOptions),
hasRefBuilderOptions: hasRefBuilderOptions(opts.cliOptions),
hasProviderBuilderOptions: hasProviderBuilderOptions(opts.cliOptions),
strictJson: Boolean(opts.cliOptions.strictJson || opts.cliOptions.json)
});
if (!modeResolution.ok) throw modeError(modeResolution.error);
if (opts.cliOptions.allowExec && !opts.cliOptions.dryRun) throw modeError("--allow-exec requires --dry-run.");
if (opts.cliOptions.merge && opts.cliOptions.replace) throw modeError("choose either --merge or --replace, not both.");
const batchEntries = parseBatchSource(opts.cliOptions);
if (batchEntries) {
if (opts.path !== void 0 || opts.value !== void 0) throw modeError("batch mode does not accept <path> or <value> arguments.");
}
await runConfigOperations({
runtime,
operations: batchEntries ? parseBatchOperations(batchEntries) : buildSingleSetOperations({
path: opts.path,
value: opts.value,
opts: opts.cliOptions
}),
options: opts.cliOptions,
successMode: "set"
});
} catch (err) {
handleConfigMutationError({
err,
runtime,
options: opts.cliOptions
});
}
}
async function runConfigPatch(opts) {
const runtime = opts.runtime ?? defaultRuntime;
try {
if (opts.cliOptions.allowExec && !opts.cliOptions.dryRun) throw configPatchModeError("--allow-exec requires --dry-run.");
if (opts.cliOptions.json && !opts.cliOptions.dryRun) throw configPatchModeError("--json requires --dry-run.");
await runConfigOperations({
runtime,
operations: buildConfigPatchOperations({
patch: await readConfigPatchInput(opts.cliOptions),
replacePaths: parseReplacePaths(opts.cliOptions.replacePath)
}),
options: {
dryRun: opts.cliOptions.dryRun,
allowExec: opts.cliOptions.allowExec,
json: opts.cliOptions.json
},
successMode: "patch"
});
} catch (err) {
handleConfigMutationError({
err,
runtime,
options: opts.cliOptions
});
}
}
async function runConfigGet(opts) {
const runtime = opts.runtime ?? defaultRuntime;
try {
const parsedPath = parseRequiredPath(opts.path);
const res = getAtPath(redactConfigObject((await loadValidConfig(runtime)).config), parsedPath);
if (!res.found) {
runtime.error(danger(`Config path not found: ${opts.path}. Run ${formatCliCommand("openclaw config validate")} to inspect config shape.`));
runtime.exit(1);
return;
}
if (opts.json) {
writeRuntimeJson(runtime, res.value ?? null);
return;
}
if (typeof res.value === "string" || typeof res.value === "number" || typeof res.value === "boolean") {
runtime.log(String(res.value));
return;
}
writeRuntimeJson(runtime, res.value ?? null);
} catch (err) {
runtime.error(danger(String(err)));
runtime.exit(1);
}
}
async function runConfigUnset(opts) {
const runtime = opts.runtime ?? defaultRuntime;
const cliOptions = opts.cliOptions ?? {};
try {
if (cliOptions.allowExec && !cliOptions.dryRun) throw new Error("--allow-exec can only be used with --dry-run.");
if (cliOptions.json && !cliOptions.dryRun) throw new Error("--json can only be used with --dry-run.");
const parsedPath = parseRequiredPath(opts.path);
const autoManagedUnsetTargets = findAutoManagedMetaUnsetTargets(parsedPath);
if (autoManagedUnsetTargets.length > 0) throw new Error(formatAutoManagedMetaError(autoManagedUnsetTargets));
const snapshot = await loadValidConfig(runtime);
const next = structuredClone(snapshot.resolved);
const unsetResult = unsetAtPath(next, parsedPath);
if (!unsetResult.removed) {
if (cliOptions.dryRun && cliOptions.json) throw new ConfigSetDryRunValidationError({
ok: false,
operations: 1,
configPath: shortenHomePath(snapshot.path),
inputModes: ["unset"],
checks: {
schema: false,
resolvability: false,
resolvabilityComplete: false
},
refsChecked: 0,
skippedExecRefs: 0,
errors: [{
kind: "missing-path",
message: `Config path not found: ${opts.path}. Nothing was changed.`
}]
});
runtime.error(danger(`Config path not found: ${opts.path}. Nothing was changed. Run ${formatCliCommand("openclaw config get <path>")} first if you are unsure of the path.`));
runtime.exit(1);
return;
}
if (cliOptions.dryRun) {
await runConfigOperations({
runtime,
operations: [buildUnsetOperation(parsedPath)],
options: cliOptions,
successMode: "set"
});
return;
}
await replaceConfigFile({
nextConfig: next,
...snapshot.hash !== void 0 ? { baseHash: snapshot.hash } : {},
...unsetResult.leafContainer === "array" ? {} : { writeOptions: { unsetPaths: [parsedPath] } }
});
runtime.log(info(`Removed ${opts.path}. Restart the gateway to apply.`));
} catch (err) {
handleConfigMutationError({
err,
runtime,
options: cliOptions
});
}
}
async function runConfigFile(opts) {
const runtime = opts.runtime ?? defaultRuntime;
try {
const snapshot = await readConfigFileSnapshot();
runtime.log(shortenHomePath(snapshot.path));
} catch (err) {
runtime.error(danger(String(err)));
runtime.exit(1);
}
}
async function buildCliConfigSchema() {
const schema = structuredClone((await readBestEffortRuntimeConfigSchema()).schema);
schema.properties = {
$schema: { type: "string" },
...schema.properties
};
return schema;
}
async function runConfigSchema(opts = {}) {
const runtime = opts.runtime ?? defaultRuntime;
try {
writeRuntimeJson(runtime, await buildCliConfigSchema());
} catch (err) {
runtime.error(danger(`Config schema error: ${String(err)}`));
runtime.exit(1);
}
}
async function runConfigValidate(opts = {}) {
const runtime = opts.runtime ?? defaultRuntime;
let outputPath = CONFIG_PATH ?? "openclaw.json";
try {
const snapshot = await readConfigFileSnapshot();
outputPath = snapshot.path;
const shortPath = shortenHomePath(outputPath);
if (!snapshot.exists) {
if (opts.json) writeRuntimeJson(runtime, {
valid: false,
path: outputPath,
error: "file not found"
}, 0);
else {
runtime.error(danger(`Config file not found: ${shortPath}`));
runtime.error(`Create one with ${formatCliCommand("openclaw onboard")} or run ${formatCliCommand("openclaw doctor --fix")}.`);
}
runtime.exit(1);
return;
}
if (!snapshot.valid) {
const issues = normalizeConfigIssues(snapshot.issues);
if (opts.json) writeRuntimeJson(runtime, {
valid: false,
path: outputPath,
issues
});
else {
runtime.error(danger(`OpenClaw config is invalid: ${shortPath}`));
for (const line of formatConfigIssueLines(issues, danger("×"), { normalizeRoot: true })) runtime.error(` ${line}`);
runtime.error("");
runtime.error(formatInvalidConfigRepairHint(snapshot, "to repair, or fix the keys above manually."));
runtime.error(`Inspect with ${formatCliCommand("openclaw config validate")}.`);
}
runtime.exit(1);
return;
}
if (opts.json) writeRuntimeJson(runtime, {
valid: true,
path: outputPath
}, 0);
else runtime.log(success(`Config valid: ${shortPath}`));
} catch (err) {
if (opts.json) writeRuntimeJson(runtime, {
valid: false,
path: outputPath,
error: String(err)
}, 0);
else runtime.error(danger(`Config validation error: ${String(err)}`));
runtime.exit(1);
}
}
function registerConfigCli(program) {
const cmd = program.command("config").description("Non-interactive config helpers (get/set/patch/unset/file/schema/validate). Run without subcommand for guided setup.").addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/config", "docs.openclaw.ai/cli/config")}\n`).option("--section <section>", "Configuration sections for guided setup (repeatable). Use with no subcommand.", (value, previous) => [...previous, value], []).action(async (opts) => {
const { configureCommandFromSectionsArg } = await import("./configure-yakXLLdq.js");
await configureCommandFromSectionsArg(opts.section, defaultRuntime);
});
cmd.command("get").description("Get a config value by dot path").argument("<path>", "Config path (dot or bracket notation)").option("--json", "Output JSON", false).action(async (path, opts) => {
await runConfigGet({
path,
json: Boolean(opts.json)
});
});
setCommandJsonMode(cmd.command("set"), "parse-only").description(CONFIG_SET_DESCRIPTION).argument("[path]", "Config path (dot or bracket notation)").argument("[value]", "Value (JSON/JSON5 or raw string)").option("--strict-json", "Strict JSON parsing (error instead of raw string fallback)", false).option("--json", "Legacy alias for --strict-json", false).option("--dry-run", "Validate changes without writing openclaw.json (checks run in builder/json/batch modes; exec SecretRefs are skipped unless --allow-exec is set)", false).option("--allow-exec", "Dry-run only: allow exec SecretRef resolvability checks (may execute provider commands)", false).option("--merge", "Merge object/map values instead of replacing the target path", false).option("--replace", "Allow full replacement of protected map/list paths such as agents.defaults.models", false).option("--ref-provider <alias>", "SecretRef builder: provider alias").option("--ref-source <source>", "SecretRef builder: source (env|file|exec)").option("--ref-id <id>", "SecretRef builder: ref id").option("--provider-source <source>", "Provider builder: source (env|file|exec)").option("--provider-allowlist <envVar>", "Provider builder (env): allowlist entry (repeatable)", (value, previous) => [...previous, value], []).option("--provider-path <path>", "Provider builder (file): path").option("--provider-mode <mode>", "Provider builder (file): mode (singleValue|json)").option("--provider-timeout-ms <ms>", "Provider builder (file|exec): timeout ms").option("--provider-max-bytes <bytes>", "Provider builder (file): max bytes").option("--provider-command <path>", "Provider builder (exec): absolute command path").option("--provider-arg <arg>", "Provider builder (exec): command arg (repeatable)", (value, previous) => [...previous, value], []).option("--provider-no-output-timeout-ms <ms>", "Provider builder (exec): no-output timeout ms").option("--provider-max-output-bytes <bytes>", "Provider builder (exec): max output bytes").option("--provider-json-only", "Provider builder (exec): require JSON output", false).option("--provider-env <key=value>", "Provider builder (exec): env assignment (repeatable)", (value, previous) => [...previous, value], []).option("--provider-pass-env <envVar>", "Provider builder (exec): pass host env var (repeatable)", (value, previous) => [...previous, value], []).option("--provider-trusted-dir <path>", "Provider builder (exec): trusted directory (repeatable)", (value, previous) => [...previous, value], []).option("--provider-allow-insecure-path", "Provider builder (file|exec): bypass strict path permission checks", false).option("--provider-allow-symlink-command", "Provider builder (exec): allow command symlink path", false).option("--batch-json <json>", "Batch mode: JSON array of set operations").option("--batch-file <path>", "Batch mode: read JSON array of set operations from file").action(async (path, value, opts) => {
await runConfigSet({
path,
value,
cliOptions: opts
});
});
cmd.command("patch").description(CONFIG_PATCH_DESCRIPTION).option("--file <path>", "Read a JSON5 config patch object from file").option("--stdin", "Read a JSON5 config patch object from stdin", false).option("--dry-run", "Validate changes without writing openclaw.json (checks schema and SecretRef resolvability; exec SecretRefs are skipped unless --allow-exec is set)", false).option("--allow-exec", "Dry-run only: allow exec SecretRef resolvability checks (may execute provider commands)", false).option("--json", "Output dry-run result as JSON", false).option("--replace-path <path>", "Replace the object or array at this dot/bracket path instead of recursively applying it (repeatable)", (value, previous) => [...previous, value], []).action(async (opts) => {
await runConfigPatch({ cliOptions: opts });
});
cmd.command("unset").description("Remove a config value by dot path").argument("<path>", "Config path (dot or bracket notation)").option("--dry-run", "validate the removal without writing the config file").option("--allow-exec", "allow exec SecretRef providers during --dry-run").option("--json", "print dry-run result as JSON").action(async (path, options) => {
await runConfigUnset({
path,
cliOptions: options
});
});
cmd.command("file").description("Print the active config file path").action(async () => {
await runConfigFile({});
});
cmd.command("schema").description("Print the JSON schema for openclaw.json").action(async () => {
await runConfigSchema({});
});
cmd.command("validate").description("Validate the current config against the schema without starting the gateway").option("--json", "Output validation result as JSON", false).action(async (opts) => {
await runConfigValidate({ json: Boolean(opts.json) });
});
}
//#endregion
export { registerConfigCli, runConfigFile, runConfigGet, runConfigPatch, runConfigSchema, runConfigSet, runConfigUnset, runConfigValidate };