@gguf/claw
Version:
Multi-channel AI gateway with extensible messaging integrations
239 lines (237 loc) • 8.87 kB
JavaScript
import "./paths-B4BZAPZh.js";
import { B as theme, O as danger, S as shortenHomePath, k as info } from "./utils-CP9YLh6M.js";
import "./registry-B-j4DRfe.js";
import { f as defaultRuntime } from "./subsystem-BCQGGxdd.js";
import "./exec-DYqRzFbo.js";
import "./agent-scope-BnZW9Gh2.js";
import "./model-selection-CqaTAlhy.js";
import "./github-copilot-token-D2zp6kMZ.js";
import { t as formatCliCommand } from "./command-format-DEKzLnLg.js";
import "./boolean-BsqeuxE6.js";
import "./env-VriqyjXT.js";
import { l as writeConfigFile, o as readConfigFileSnapshot } from "./config-PQiujvsf.js";
import "./manifest-registry-4k4vkhPS.js";
import { t as formatDocsLink } from "./links-BVCMOGeE.js";
import JSON5 from "json5";
//#region src/cli/config-cli.ts
function isIndexSegment(raw) {
return /^[0-9]+$/.test(raw);
}
function parsePath(raw) {
const trimmed = raw.trim();
if (!trimmed) return [];
const parts = [];
let current = "";
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 === ".") {
if (current) parts.push(current);
current = "";
i += 1;
continue;
}
if (ch === "[") {
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(inside);
i = close + 1;
continue;
}
current += ch;
i += 1;
}
if (current) parts.push(current);
return parts.map((part) => part.trim()).filter(Boolean);
}
function parseValue(raw, opts) {
const trimmed = raw.trim();
if (opts.strictJson) try {
return JSON5.parse(trimmed);
} catch (err) {
throw new Error(`Failed to parse JSON5 value: ${String(err)}`, { cause: err });
}
try {
return JSON5.parse(trimmed);
} catch {
return raw;
}
}
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 = Number.parseInt(segment, 10);
if (!Number.isFinite(index) || index < 0 || index >= current.length) return { found: false };
current = current[index];
continue;
}
const record = current;
if (!(segment in record)) return { found: false };
current = record[segment];
}
return {
found: true,
value: current
};
}
function setAtPath(root, path, value) {
let current = root;
for (let i = 0; i < path.length - 1; i += 1) {
const segment = path[i];
const next = path[i + 1];
const nextIsIndex = Boolean(next && isIndexSegment(next));
if (Array.isArray(current)) {
if (!isIndexSegment(segment)) throw new Error(`Expected numeric index for array segment "${segment}"`);
const index = Number.parseInt(segment, 10);
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 = record[segment];
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 = Number.parseInt(last, 10);
current[index] = value;
return;
}
if (!current || typeof current !== "object") throw new Error(`Cannot set "${last}" (parent is not an object)`);
current[last] = value;
}
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 false;
if (Array.isArray(current)) {
if (!isIndexSegment(segment)) return false;
const index = Number.parseInt(segment, 10);
if (!Number.isFinite(index) || index < 0 || index >= current.length) return false;
current = current[index];
continue;
}
const record = current;
if (!(segment in record)) return false;
current = record[segment];
}
const last = path[path.length - 1];
if (Array.isArray(current)) {
if (!isIndexSegment(last)) return false;
const index = Number.parseInt(last, 10);
if (!Number.isFinite(index) || index < 0 || index >= current.length) return false;
current.splice(index, 1);
return true;
}
if (!current || typeof current !== "object") return false;
const record = current;
if (!(last in record)) return false;
delete record[last];
return true;
}
async function loadValidConfig(runtime = defaultRuntime) {
const snapshot = await readConfigFileSnapshot();
if (snapshot.valid) return snapshot;
runtime.error(`Config invalid at ${shortenHomePath(snapshot.path)}.`);
for (const issue of snapshot.issues) runtime.error(`- ${issue.path || "<root>"}: ${issue.message}`);
runtime.error(`Run \`${formatCliCommand("openclaw doctor")}\` 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.");
return parsedPath;
}
async function runConfigGet(opts) {
const runtime = opts.runtime ?? defaultRuntime;
try {
const parsedPath = parseRequiredPath(opts.path);
const res = getAtPath((await loadValidConfig(runtime)).config, parsedPath);
if (!res.found) {
runtime.error(danger(`Config path not found: ${opts.path}`));
runtime.exit(1);
return;
}
if (opts.json) {
runtime.log(JSON.stringify(res.value ?? null, null, 2));
return;
}
if (typeof res.value === "string" || typeof res.value === "number" || typeof res.value === "boolean") {
runtime.log(String(res.value));
return;
}
runtime.log(JSON.stringify(res.value ?? null, null, 2));
} catch (err) {
runtime.error(danger(String(err)));
runtime.exit(1);
}
}
async function runConfigUnset(opts) {
const runtime = opts.runtime ?? defaultRuntime;
try {
const parsedPath = parseRequiredPath(opts.path);
const snapshot = await loadValidConfig(runtime);
const next = structuredClone(snapshot.resolved);
if (!unsetAtPath(next, parsedPath)) {
runtime.error(danger(`Config path not found: ${opts.path}`));
runtime.exit(1);
return;
}
await writeConfigFile(next);
runtime.log(info(`Removed ${opts.path}. Restart the gateway to apply.`));
} catch (err) {
runtime.error(danger(String(err)));
runtime.exit(1);
}
}
function registerConfigCli(program) {
const cmd = program.command("config").description("Non-interactive config helpers (get/set/unset). Run without subcommand for the setup wizard.").addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/config", "docs.openclaw.ai/cli/config")}\n`).option("--section <section>", "Configure wizard sections (repeatable). Use with no subcommand.", (value, previous) => [...previous, value], []).action(async (opts) => {
const { configureCommandFromSectionsArg } = await import("./configure-CBZlcN2L.js").then((n) => n.t);
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)
});
});
cmd.command("set").description("Set a config value by dot path").argument("<path>", "Config path (dot or bracket notation)").argument("<value>", "Value (JSON5 or raw string)").option("--strict-json", "Strict JSON5 parsing (error instead of raw string fallback)", false).option("--json", "Legacy alias for --strict-json", false).action(async (path, value, opts) => {
try {
const parsedPath = parsePath(path);
if (parsedPath.length === 0) throw new Error("Path is empty.");
const parsedValue = parseValue(value, { strictJson: Boolean(opts.strictJson || opts.json) });
const snapshot = await loadValidConfig();
const next = structuredClone(snapshot.resolved);
setAtPath(next, parsedPath, parsedValue);
await writeConfigFile(next);
defaultRuntime.log(info(`Updated ${path}. Restart the gateway to apply.`));
} catch (err) {
defaultRuntime.error(danger(String(err)));
defaultRuntime.exit(1);
}
});
cmd.command("unset").description("Remove a config value by dot path").argument("<path>", "Config path (dot or bracket notation)").action(async (path) => {
await runConfigUnset({ path });
});
}
//#endregion
export { registerConfigCli };