Files
clawdbot/src/auto-reply/reply/commands-config.ts
2026-01-15 01:41:15 +00:00

266 lines
8.0 KiB
TypeScript

import {
readConfigFileSnapshot,
validateConfigObject,
writeConfigFile,
} from "../../config/config.js";
import {
getConfigValueAtPath,
parseConfigPath,
setConfigValueAtPath,
unsetConfigValueAtPath,
} from "../../config/config-paths.js";
import {
getConfigOverrides,
resetConfigOverrides,
setConfigOverride,
unsetConfigOverride,
} from "../../config/runtime-overrides.js";
import { resolveChannelConfigWrites } from "../../channels/plugins/config-writes.js";
import { normalizeChannelId } from "../../channels/registry.js";
import { logVerbose } from "../../globals.js";
import type { CommandHandler } from "./commands-types.js";
import { parseConfigCommand } from "./config-commands.js";
import { parseDebugCommand } from "./debug-commands.js";
export const handleConfigCommand: CommandHandler = async (params, allowTextCommands) => {
if (!allowTextCommands) return null;
const configCommand = parseConfigCommand(params.command.commandBodyNormalized);
if (!configCommand) return null;
if (!params.command.isAuthorizedSender) {
logVerbose(
`Ignoring /config from unauthorized sender: ${params.command.senderId || "<unknown>"}`,
);
return { shouldContinue: false };
}
if (params.cfg.commands?.config !== true) {
return {
shouldContinue: false,
reply: {
text: "⚠️ /config is disabled. Set commands.config=true to enable.",
},
};
}
if (configCommand.action === "error") {
return {
shouldContinue: false,
reply: { text: `⚠️ ${configCommand.message}` },
};
}
if (configCommand.action === "set" || configCommand.action === "unset") {
const channelId = params.command.channelId ?? normalizeChannelId(params.command.channel);
const allowWrites = resolveChannelConfigWrites({
cfg: params.cfg,
channelId,
accountId: params.ctx.AccountId,
});
if (!allowWrites) {
const channelLabel = channelId ?? "this channel";
const hint = channelId
? `channels.${channelId}.configWrites=true`
: "channels.<channel>.configWrites=true";
return {
shouldContinue: false,
reply: {
text: `⚠️ Config writes are disabled for ${channelLabel}. Set ${hint} to enable.`,
},
};
}
}
const snapshot = await readConfigFileSnapshot();
if (!snapshot.valid || !snapshot.parsed || typeof snapshot.parsed !== "object") {
return {
shouldContinue: false,
reply: {
text: "⚠️ Config file is invalid; fix it before using /config.",
},
};
}
const parsedBase = structuredClone(snapshot.parsed as Record<string, unknown>);
if (configCommand.action === "show") {
const pathRaw = configCommand.path?.trim();
if (pathRaw) {
const parsedPath = parseConfigPath(pathRaw);
if (!parsedPath.ok || !parsedPath.path) {
return {
shouldContinue: false,
reply: { text: `⚠️ ${parsedPath.error ?? "Invalid path."}` },
};
}
const value = getConfigValueAtPath(parsedBase, parsedPath.path);
const rendered = JSON.stringify(value ?? null, null, 2);
return {
shouldContinue: false,
reply: {
text: `⚙️ Config ${pathRaw}:\n\`\`\`json\n${rendered}\n\`\`\``,
},
};
}
const json = JSON.stringify(parsedBase, null, 2);
return {
shouldContinue: false,
reply: { text: `⚙️ Config (raw):\n\`\`\`json\n${json}\n\`\`\`` },
};
}
if (configCommand.action === "unset") {
const parsedPath = parseConfigPath(configCommand.path);
if (!parsedPath.ok || !parsedPath.path) {
return {
shouldContinue: false,
reply: { text: `⚠️ ${parsedPath.error ?? "Invalid path."}` },
};
}
const removed = unsetConfigValueAtPath(parsedBase, parsedPath.path);
if (!removed) {
return {
shouldContinue: false,
reply: { text: `⚙️ No config value found for ${configCommand.path}.` },
};
}
const validated = validateConfigObject(parsedBase);
if (!validated.ok) {
const issue = validated.issues[0];
return {
shouldContinue: false,
reply: {
text: `⚠️ Config invalid after unset (${issue.path}: ${issue.message}).`,
},
};
}
await writeConfigFile(validated.config);
return {
shouldContinue: false,
reply: { text: `⚙️ Config updated: ${configCommand.path} removed.` },
};
}
if (configCommand.action === "set") {
const parsedPath = parseConfigPath(configCommand.path);
if (!parsedPath.ok || !parsedPath.path) {
return {
shouldContinue: false,
reply: { text: `⚠️ ${parsedPath.error ?? "Invalid path."}` },
};
}
setConfigValueAtPath(parsedBase, parsedPath.path, configCommand.value);
const validated = validateConfigObject(parsedBase);
if (!validated.ok) {
const issue = validated.issues[0];
return {
shouldContinue: false,
reply: {
text: `⚠️ Config invalid after set (${issue.path}: ${issue.message}).`,
},
};
}
await writeConfigFile(validated.config);
const valueLabel =
typeof configCommand.value === "string"
? `"${configCommand.value}"`
: JSON.stringify(configCommand.value);
return {
shouldContinue: false,
reply: {
text: `⚙️ Config updated: ${configCommand.path}=${valueLabel ?? "null"}`,
},
};
}
return null;
};
export const handleDebugCommand: CommandHandler = async (params, allowTextCommands) => {
if (!allowTextCommands) return null;
const debugCommand = parseDebugCommand(params.command.commandBodyNormalized);
if (!debugCommand) return null;
if (!params.command.isAuthorizedSender) {
logVerbose(
`Ignoring /debug from unauthorized sender: ${params.command.senderId || "<unknown>"}`,
);
return { shouldContinue: false };
}
if (params.cfg.commands?.debug !== true) {
return {
shouldContinue: false,
reply: {
text: "⚠️ /debug is disabled. Set commands.debug=true to enable.",
},
};
}
if (debugCommand.action === "error") {
return {
shouldContinue: false,
reply: { text: `⚠️ ${debugCommand.message}` },
};
}
if (debugCommand.action === "show") {
const overrides = getConfigOverrides();
const hasOverrides = Object.keys(overrides).length > 0;
if (!hasOverrides) {
return {
shouldContinue: false,
reply: { text: "⚙️ Debug overrides: (none)" },
};
}
const json = JSON.stringify(overrides, null, 2);
return {
shouldContinue: false,
reply: {
text: `⚙️ Debug overrides (memory-only):\n\`\`\`json\n${json}\n\`\`\``,
},
};
}
if (debugCommand.action === "reset") {
resetConfigOverrides();
return {
shouldContinue: false,
reply: { text: "⚙️ Debug overrides cleared; using config on disk." },
};
}
if (debugCommand.action === "unset") {
const result = unsetConfigOverride(debugCommand.path);
if (!result.ok) {
return {
shouldContinue: false,
reply: { text: `⚠️ ${result.error ?? "Invalid path."}` },
};
}
if (!result.removed) {
return {
shouldContinue: false,
reply: {
text: `⚙️ No debug override found for ${debugCommand.path}.`,
},
};
}
return {
shouldContinue: false,
reply: { text: `⚙️ Debug override removed for ${debugCommand.path}.` },
};
}
if (debugCommand.action === "set") {
const result = setConfigOverride(debugCommand.path, debugCommand.value);
if (!result.ok) {
return {
shouldContinue: false,
reply: { text: `⚠️ ${result.error ?? "Invalid override."}` },
};
}
const valueLabel =
typeof debugCommand.value === "string"
? `"${debugCommand.value}"`
: JSON.stringify(debugCommand.value);
return {
shouldContinue: false,
reply: {
text: `⚙️ Debug override set: ${debugCommand.path}=${valueLabel ?? "null"}`,
},
};
}
return null;
};