kitchen-sink/scripts/openclaw-surface.mjs
2026-04-28 16:15:07 -07:00

153 lines
5.6 KiB
JavaScript

import { existsSync, readFileSync } from "node:fs";
import path from "node:path";
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
// These bundled-plugin convenience barrels existed in published OpenClaw builds
// but were retired from the public package export contract on current main.
const retiredPluginSdkExports = new Set([
"openclaw/plugin-sdk/bluebubbles",
"openclaw/plugin-sdk/bluebubbles-policy",
"openclaw/plugin-sdk/browser-cdp",
"openclaw/plugin-sdk/browser-config-runtime",
"openclaw/plugin-sdk/browser-config-support",
"openclaw/plugin-sdk/browser-control-auth",
"openclaw/plugin-sdk/browser-node-runtime",
"openclaw/plugin-sdk/browser-profiles",
"openclaw/plugin-sdk/browser-security-runtime",
"openclaw/plugin-sdk/browser-setup-tools",
"openclaw/plugin-sdk/browser-support",
"openclaw/plugin-sdk/diagnostics-otel",
"openclaw/plugin-sdk/diagnostics-prometheus",
"openclaw/plugin-sdk/diffs",
"openclaw/plugin-sdk/feishu",
"openclaw/plugin-sdk/feishu-conversation",
"openclaw/plugin-sdk/feishu-setup",
"openclaw/plugin-sdk/github-copilot-login",
"openclaw/plugin-sdk/github-copilot-token",
"openclaw/plugin-sdk/googlechat",
"openclaw/plugin-sdk/googlechat-runtime-shared",
"openclaw/plugin-sdk/irc",
"openclaw/plugin-sdk/irc-surface",
"openclaw/plugin-sdk/line",
"openclaw/plugin-sdk/line-core",
"openclaw/plugin-sdk/line-runtime",
"openclaw/plugin-sdk/line-surface",
"openclaw/plugin-sdk/llm-task",
"openclaw/plugin-sdk/matrix",
"openclaw/plugin-sdk/matrix-helper",
"openclaw/plugin-sdk/matrix-runtime-heavy",
"openclaw/plugin-sdk/matrix-runtime-shared",
"openclaw/plugin-sdk/matrix-runtime-surface",
"openclaw/plugin-sdk/matrix-surface",
"openclaw/plugin-sdk/matrix-thread-bindings",
"openclaw/plugin-sdk/mattermost",
"openclaw/plugin-sdk/mattermost-policy",
"openclaw/plugin-sdk/memory-core",
"openclaw/plugin-sdk/memory-lancedb",
"openclaw/plugin-sdk/msteams",
"openclaw/plugin-sdk/nextcloud-talk",
"openclaw/plugin-sdk/nostr",
"openclaw/plugin-sdk/opencode",
"openclaw/plugin-sdk/telegram-command-ui",
"openclaw/plugin-sdk/thread-ownership",
"openclaw/plugin-sdk/tlon",
"openclaw/plugin-sdk/twitch",
"openclaw/plugin-sdk/voice-call",
"openclaw/plugin-sdk/volc-model-catalog-shared",
"openclaw/plugin-sdk/zalo",
"openclaw/plugin-sdk/zalo-setup",
"openclaw/plugin-sdk/zalouser",
]);
export function readOpenClawSurface() {
const packageEntryPath = resolveOpenClawPackageEntry();
const packageRoot = findPackageRoot(packageEntryPath);
const packageJsonPath = path.join(packageRoot, "package.json");
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
const pluginSdkExports = Object.keys(packageJson.exports ?? {})
.filter((specifier) => specifier === "./plugin-sdk" || specifier.startsWith("./plugin-sdk/"))
.map((specifier) => `openclaw/${specifier.slice(2)}`)
.filter((specifier) => !retiredPluginSdkExports.has(specifier))
.sort();
const apiBuilderPath = firstExistingPath([
path.join(packageRoot, "dist/plugin-sdk/src/plugins/api-builder.d.ts"),
path.join(packageRoot, "src/plugins/api-builder.ts"),
]);
const hookTypesPath = firstExistingPath([
path.join(packageRoot, "dist/plugin-sdk/src/plugins/hook-types.d.ts"),
path.join(packageRoot, "src/plugins/hook-types.ts"),
]);
const manifestPath = firstExistingPath([
path.join(packageRoot, "dist/plugin-sdk/src/plugins/manifest.d.ts"),
path.join(packageRoot, "src/plugins/manifest.ts"),
]);
const apiBuilderSource = readOptional(apiBuilderPath);
const hookTypesSource = readOptional(hookTypesPath);
const manifestSource = readOptional(manifestPath);
return {
packageJsonPath,
packageVersion: packageJson.version,
pluginSdkExports,
registrars: unique([...apiBuilderSource.matchAll(/\b(register[A-Za-z0-9]+)\b/g)].map((match) => match[1])).sort(),
hooks: parseHookNames(hookTypesSource),
manifestContracts: parseTypeFields(manifestSource, "PluginManifestContracts"),
};
}
function resolveOpenClawPackageEntry() {
const packageRoot = process.env.OPENCLAW_PACKAGE_ROOT;
if (packageRoot) {
return path.join(path.resolve(packageRoot), "package.json");
}
return require.resolve("openclaw");
}
function findPackageRoot(entryPath) {
let current = path.dirname(entryPath);
while (current !== path.dirname(current)) {
const candidate = path.join(current, "package.json");
if (existsSync(candidate)) {
return current;
}
current = path.dirname(current);
}
throw new Error(`Could not find openclaw package root from ${entryPath}`);
}
function firstExistingPath(paths) {
return paths.find((item) => existsSync(item)) ?? null;
}
function readOptional(filePath) {
return filePath ? readFileSync(filePath, "utf8") : "";
}
function parseHookNames(source) {
const arrayMatch = source.match(/PLUGIN_HOOK_NAMES[^=]*=\s*\[([\s\S]*?)\]/);
if (arrayMatch) {
return unique([...arrayMatch[1].matchAll(/["'`]([a-z0-9_:-]+)["'`]/g)].map((match) => match[1])).sort();
}
const unionMatch = source.match(/type\s+PluginHookName\s*=\s*([\s\S]*?);/);
if (unionMatch) {
return unique([...unionMatch[1].matchAll(/["'`]([a-z0-9_:-]+)["'`]/g)].map((match) => match[1])).sort();
}
return [];
}
function parseTypeFields(source, typeName) {
const match = source.match(new RegExp(`(?:export\\s+)?(?:declare\\s+)?type\\s+${typeName}\\s*=\\s*\\{([\\s\\S]*?)\\n\\};`));
if (!match) {
return [];
}
return unique([...match[1].matchAll(/^\s*([A-Za-z][A-Za-z0-9]*)\??\s*:/gm)].map((field) => field[1])).sort();
}
function unique(values) {
return [...new Set(values)];
}