Files
openclaw/extensions/buzz/src/setup-core.ts
2026-07-26 14:09:23 +01:00

117 lines
3.5 KiB
TypeScript

import {
defineChannelSetupContract,
type ChannelSetupAdapter,
type ChannelSetupInput,
} from "openclaw/plugin-sdk/channel-setup";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import { applyAccountNameToChannelSection } from "openclaw/plugin-sdk/setup";
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup-runtime";
import { decodeBuzzPrivateKey, resolveBuzzPublicKey } from "./types.js";
type BuzzSetupInput = ChannelSetupInput & {
relayUrl?: string;
privateKey?: string;
};
function validRelayUrl(value: string | undefined): boolean {
try {
const url = new URL(value ?? "");
return url.protocol === "ws:" || url.protocol === "wss:";
} catch {
return false;
}
}
function resolveComparableCurrentKey(cfg: OpenClawConfig): string | undefined {
const configured = cfg.channels?.buzz?.privateKey;
if (configured !== undefined) {
return typeof configured === "string" ? configured.trim() || undefined : undefined;
}
return process.env.BUZZ_PRIVATE_KEY?.trim() || undefined;
}
export function isSameBuzzIdentity(currentKey?: string, nextKey?: string): boolean {
if (!currentKey || !nextKey) {
return false;
}
try {
return resolveBuzzPublicKey(currentKey) === resolveBuzzPublicKey(nextKey);
} catch {
return false;
}
}
export const buzzSetupAdapter: ChannelSetupAdapter<BuzzSetupInput> = {
resolveAccountId: () => DEFAULT_ACCOUNT_ID,
applyAccountName: ({ cfg, accountId, name }) =>
applyAccountNameToChannelSection({
cfg,
channelKey: "buzz",
accountId,
name,
}),
validateInput: ({ accountId, input }) => {
if (accountId !== DEFAULT_ACCOUNT_ID) {
return "Buzz currently supports only the default account.";
}
if (!validRelayUrl(input.relayUrl)) {
return "Buzz requires --relay-url with a ws:// or wss:// URL.";
}
if (input.useEnv) {
return process.env.BUZZ_PRIVATE_KEY?.trim() ? null : "BUZZ_PRIVATE_KEY is not set.";
}
if (!input.privateKey?.trim()) {
return "Buzz requires --private-key or --use-env.";
}
try {
decodeBuzzPrivateKey(input.privateKey);
return null;
} catch (error) {
return error instanceof Error ? error.message : "Invalid Buzz private key.";
}
},
applyAccountConfig: ({ cfg, input }) => {
const currentPrivateKey = resolveComparableCurrentKey(cfg);
const nextPrivateKey = input.useEnv
? process.env.BUZZ_PRIVATE_KEY?.trim()
: input.privateKey?.trim();
const keepAuthTag = isSameBuzzIdentity(currentPrivateKey, nextPrivateKey);
const { privateKey: _privateKey, authTag, ...existing } = cfg.channels?.buzz ?? {};
return {
...cfg,
channels: {
...cfg.channels,
buzz: {
...existing,
enabled: true,
relayUrl: input.relayUrl?.trim(),
...(keepAuthTag && authTag !== undefined ? { authTag } : {}),
...(input.useEnv ? {} : { privateKey: input.privateKey?.trim() }),
},
},
} as OpenClawConfig;
},
};
export const buzzSetupContract = defineChannelSetupContract({
fields: {
relayUrl: {
kind: "string",
cli: { flags: "--relay-url <url>", description: "Buzz relay WebSocket URL" },
},
privateKey: {
kind: "string",
sensitive: true,
cli: { flags: "--private-key <key>", description: "Buzz bot Nostr private key" },
},
useEnv: {
kind: "boolean",
cli: {
flags: "--use-env",
description: "Use BUZZ_PRIVATE_KEY with the supplied relay URL",
},
},
},
adapter: buzzSetupAdapter,
});