UNPKG

@gguf/claw

Version:

WhatsApp gateway CLI (Baileys web) with Pi RPC agent

443 lines (437 loc) 16.1 kB
import { h as resolveUserPath, t as CONFIG_DIR } from "./utils-CKSrBNwq.js"; import { t as parseBooleanValue } from "./boolean-BgXe2hyu.js"; import { a as MANIFEST_KEY, i as LEGACY_MANIFEST_KEYS } from "./manifest-registry-DHaa1SJb.js"; import { v as parseFrontmatterBlock } from "./skills-D5JDj3TR.js"; import { fileURLToPath } from "node:url"; import path from "node:path"; import fs from "node:fs"; import JSON5 from "json5"; //#region src/hooks/frontmatter.ts function parseFrontmatter(content) { return parseFrontmatterBlock(content); } function normalizeStringList(input) { if (!input) return []; if (Array.isArray(input)) return input.map((value) => String(value).trim()).filter(Boolean); if (typeof input === "string") return input.split(",").map((value) => value.trim()).filter(Boolean); return []; } function parseInstallSpec(input) { if (!input || typeof input !== "object") return; const raw = input; const kind = (typeof raw.kind === "string" ? raw.kind : typeof raw.type === "string" ? raw.type : "").trim().toLowerCase(); if (kind !== "bundled" && kind !== "npm" && kind !== "git") return; const spec = { kind }; if (typeof raw.id === "string") spec.id = raw.id; if (typeof raw.label === "string") spec.label = raw.label; const bins = normalizeStringList(raw.bins); if (bins.length > 0) spec.bins = bins; if (typeof raw.package === "string") spec.package = raw.package; if (typeof raw.repository === "string") spec.repository = raw.repository; return spec; } function getFrontmatterValue(frontmatter, key) { const raw = frontmatter[key]; return typeof raw === "string" ? raw : void 0; } function parseFrontmatterBool(value, fallback) { const parsed = parseBooleanValue(value); return parsed === void 0 ? fallback : parsed; } function resolveOpenClawMetadata(frontmatter) { const raw = getFrontmatterValue(frontmatter, "metadata"); if (!raw) return; try { const parsed = JSON5.parse(raw); if (!parsed || typeof parsed !== "object") return; const metadataRawCandidates = [MANIFEST_KEY, ...LEGACY_MANIFEST_KEYS]; let metadataRaw; for (const key of metadataRawCandidates) { const candidate = parsed[key]; if (candidate && typeof candidate === "object") { metadataRaw = candidate; break; } } if (!metadataRaw || typeof metadataRaw !== "object") return; const metadataObj = metadataRaw; const requiresRaw = typeof metadataObj.requires === "object" && metadataObj.requires !== null ? metadataObj.requires : void 0; const install = (Array.isArray(metadataObj.install) ? metadataObj.install : []).map((entry) => parseInstallSpec(entry)).filter((entry) => Boolean(entry)); const osRaw = normalizeStringList(metadataObj.os); const eventsRaw = normalizeStringList(metadataObj.events); return { always: typeof metadataObj.always === "boolean" ? metadataObj.always : void 0, emoji: typeof metadataObj.emoji === "string" ? metadataObj.emoji : void 0, homepage: typeof metadataObj.homepage === "string" ? metadataObj.homepage : void 0, hookKey: typeof metadataObj.hookKey === "string" ? metadataObj.hookKey : void 0, export: typeof metadataObj.export === "string" ? metadataObj.export : void 0, os: osRaw.length > 0 ? osRaw : void 0, events: eventsRaw.length > 0 ? eventsRaw : [], requires: requiresRaw ? { bins: normalizeStringList(requiresRaw.bins), anyBins: normalizeStringList(requiresRaw.anyBins), env: normalizeStringList(requiresRaw.env), config: normalizeStringList(requiresRaw.config) } : void 0, install: install.length > 0 ? install : void 0 }; } catch { return; } } function resolveHookInvocationPolicy(frontmatter) { return { enabled: parseFrontmatterBool(getFrontmatterValue(frontmatter, "enabled"), true) }; } function resolveHookKey$1(hookName, entry) { return entry?.metadata?.hookKey ?? hookName; } //#endregion //#region src/hooks/config.ts const DEFAULT_CONFIG_VALUES = { "browser.enabled": true, "browser.evaluateEnabled": true, "workspace.dir": true }; function isTruthy(value) { if (value === void 0 || value === null) return false; if (typeof value === "boolean") return value; if (typeof value === "number") return value !== 0; if (typeof value === "string") return value.trim().length > 0; return true; } function resolveConfigPath(config, pathStr) { const parts = pathStr.split(".").filter(Boolean); let current = config; for (const part of parts) { if (typeof current !== "object" || current === null) return; current = current[part]; } return current; } function isConfigPathTruthy(config, pathStr) { const value = resolveConfigPath(config, pathStr); if (value === void 0 && pathStr in DEFAULT_CONFIG_VALUES) return DEFAULT_CONFIG_VALUES[pathStr]; return isTruthy(value); } function resolveHookConfig(config, hookKey) { const hooks = config?.hooks?.internal?.entries; if (!hooks || typeof hooks !== "object") return; const entry = hooks[hookKey]; if (!entry || typeof entry !== "object") return; return entry; } function resolveRuntimePlatform() { return process.platform; } function hasBinary(bin) { const parts = (process.env.PATH ?? "").split(path.delimiter).filter(Boolean); for (const part of parts) { const candidate = path.join(part, bin); try { fs.accessSync(candidate, fs.constants.X_OK); return true; } catch {} } return false; } function shouldIncludeHook(params) { const { entry, config, eligibility } = params; const hookConfig = resolveHookConfig(config, resolveHookKey$1(entry.hook.name, entry)); const pluginManaged = entry.hook.source === "openclaw-plugin"; const osList = entry.metadata?.os ?? []; const remotePlatforms = eligibility?.remote?.platforms ?? []; if (!pluginManaged && hookConfig?.enabled === false) return false; if (osList.length > 0 && !osList.includes(resolveRuntimePlatform()) && !remotePlatforms.some((platform) => osList.includes(platform))) return false; if (entry.metadata?.always === true) return true; const requiredBins = entry.metadata?.requires?.bins ?? []; if (requiredBins.length > 0) for (const bin of requiredBins) { if (hasBinary(bin)) continue; if (eligibility?.remote?.hasBin?.(bin)) continue; return false; } const requiredAnyBins = entry.metadata?.requires?.anyBins ?? []; if (requiredAnyBins.length > 0) { if (!(requiredAnyBins.some((bin) => hasBinary(bin)) || eligibility?.remote?.hasAnyBin?.(requiredAnyBins))) return false; } const requiredEnv = entry.metadata?.requires?.env ?? []; if (requiredEnv.length > 0) for (const envName of requiredEnv) { if (process.env[envName]) continue; if (hookConfig?.env?.[envName]) continue; return false; } const requiredConfig = entry.metadata?.requires?.config ?? []; if (requiredConfig.length > 0) { for (const configPath of requiredConfig) if (!isConfigPathTruthy(config, configPath)) return false; } return true; } //#endregion //#region src/hooks/bundled-dir.ts function resolveBundledHooksDir() { const override = process.env.OPENCLAW_BUNDLED_HOOKS_DIR?.trim(); if (override) return override; try { const execDir = path.dirname(process.execPath); const sibling = path.join(execDir, "hooks", "bundled"); if (fs.existsSync(sibling)) return sibling; } catch {} try { const moduleDir = path.dirname(fileURLToPath(import.meta.url)); const distBundled = path.join(moduleDir, "bundled"); if (fs.existsSync(distBundled)) return distBundled; } catch {} try { const moduleDir = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(moduleDir, "..", ".."); const srcBundled = path.join(root, "src", "hooks", "bundled"); if (fs.existsSync(srcBundled)) return srcBundled; } catch {} } //#endregion //#region src/hooks/workspace.ts function readHookPackageManifest(dir) { const manifestPath = path.join(dir, "package.json"); if (!fs.existsSync(manifestPath)) return null; try { const raw = fs.readFileSync(manifestPath, "utf-8"); return JSON.parse(raw); } catch { return null; } } function resolvePackageHooks(manifest) { const raw = manifest[MANIFEST_KEY]?.hooks; if (!Array.isArray(raw)) return []; return raw.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean); } function loadHookFromDir(params) { const hookMdPath = path.join(params.hookDir, "HOOK.md"); if (!fs.existsSync(hookMdPath)) return null; try { const frontmatter = parseFrontmatter(fs.readFileSync(hookMdPath, "utf-8")); const name = frontmatter.name || params.nameHint || path.basename(params.hookDir); const description = frontmatter.description || ""; const handlerCandidates = [ "handler.ts", "handler.js", "index.ts", "index.js" ]; let handlerPath; for (const candidate of handlerCandidates) { const candidatePath = path.join(params.hookDir, candidate); if (fs.existsSync(candidatePath)) { handlerPath = candidatePath; break; } } if (!handlerPath) { console.warn(`[hooks] Hook "${name}" has HOOK.md but no handler file in ${params.hookDir}`); return null; } return { name, description, source: params.source, pluginId: params.pluginId, filePath: hookMdPath, baseDir: params.hookDir, handlerPath }; } catch (err) { console.warn(`[hooks] Failed to load hook from ${params.hookDir}:`, err); return null; } } /** * Scan a directory for hooks (subdirectories containing HOOK.md) */ function loadHooksFromDir(params) { const { dir, source, pluginId } = params; if (!fs.existsSync(dir)) return []; if (!fs.statSync(dir).isDirectory()) return []; const hooks = []; const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if (!entry.isDirectory()) continue; const hookDir = path.join(dir, entry.name); const manifest = readHookPackageManifest(hookDir); const packageHooks = manifest ? resolvePackageHooks(manifest) : []; if (packageHooks.length > 0) { for (const hookPath of packageHooks) { const resolvedHookDir = path.resolve(hookDir, hookPath); const hook = loadHookFromDir({ hookDir: resolvedHookDir, source, pluginId, nameHint: path.basename(resolvedHookDir) }); if (hook) hooks.push(hook); } continue; } const hook = loadHookFromDir({ hookDir, source, pluginId, nameHint: entry.name }); if (hook) hooks.push(hook); } return hooks; } function loadHookEntries(workspaceDir, opts) { const managedHooksDir = opts?.managedHooksDir ?? path.join(CONFIG_DIR, "hooks"); const workspaceHooksDir = path.join(workspaceDir, "hooks"); const bundledHooksDir = opts?.bundledHooksDir ?? resolveBundledHooksDir(); const extraDirs = (opts?.config?.hooks?.internal?.load?.extraDirs ?? []).map((d) => typeof d === "string" ? d.trim() : "").filter(Boolean); const bundledHooks = bundledHooksDir ? loadHooksFromDir({ dir: bundledHooksDir, source: "openclaw-bundled" }) : []; const extraHooks = extraDirs.flatMap((dir) => { return loadHooksFromDir({ dir: resolveUserPath(dir), source: "openclaw-workspace" }); }); const managedHooks = loadHooksFromDir({ dir: managedHooksDir, source: "openclaw-managed" }); const workspaceHooks = loadHooksFromDir({ dir: workspaceHooksDir, source: "openclaw-workspace" }); const merged = /* @__PURE__ */ new Map(); for (const hook of extraHooks) merged.set(hook.name, hook); for (const hook of bundledHooks) merged.set(hook.name, hook); for (const hook of managedHooks) merged.set(hook.name, hook); for (const hook of workspaceHooks) merged.set(hook.name, hook); return Array.from(merged.values()).map((hook) => { let frontmatter = {}; try { frontmatter = parseFrontmatter(fs.readFileSync(hook.filePath, "utf-8")); } catch {} return { hook, frontmatter, metadata: resolveOpenClawMetadata(frontmatter), invocation: resolveHookInvocationPolicy(frontmatter) }; }); } function loadWorkspaceHookEntries(workspaceDir, opts) { return loadHookEntries(workspaceDir, opts); } //#endregion //#region src/hooks/hooks-status.ts function resolveHookKey(entry) { return entry.metadata?.hookKey ?? entry.hook.name; } function normalizeInstallOptions(entry) { const install = entry.metadata?.install ?? []; if (install.length === 0) return []; return install.map((spec, index) => { const id = (spec.id ?? `${spec.kind}-${index}`).trim(); const bins = spec.bins ?? []; let label = (spec.label ?? "").trim(); if (!label) if (spec.kind === "bundled") label = "Bundled with OpenClaw"; else if (spec.kind === "npm" && spec.package) label = `Install ${spec.package} (npm)`; else if (spec.kind === "git" && spec.repository) label = `Install from ${spec.repository}`; else label = "Run installer"; return { id, kind: spec.kind, label, bins }; }); } function buildHookStatus(entry, config, eligibility) { const hookKey = resolveHookKey(entry); const hookConfig = resolveHookConfig(config, hookKey); const managedByPlugin = entry.hook.source === "openclaw-plugin"; const disabled = managedByPlugin ? false : hookConfig?.enabled === false; const always = entry.metadata?.always === true; const emoji = entry.metadata?.emoji ?? entry.frontmatter.emoji; const homepageRaw = entry.metadata?.homepage ?? entry.frontmatter.homepage ?? entry.frontmatter.website ?? entry.frontmatter.url; const homepage = homepageRaw?.trim() ? homepageRaw.trim() : void 0; const events = entry.metadata?.events ?? []; const requiredBins = entry.metadata?.requires?.bins ?? []; const requiredAnyBins = entry.metadata?.requires?.anyBins ?? []; const requiredEnv = entry.metadata?.requires?.env ?? []; const requiredConfig = entry.metadata?.requires?.config ?? []; const requiredOs = entry.metadata?.os ?? []; const missingBins = requiredBins.filter((bin) => { if (hasBinary(bin)) return false; if (eligibility?.remote?.hasBin?.(bin)) return false; return true; }); const missingAnyBins = requiredAnyBins.length > 0 && !(requiredAnyBins.some((bin) => hasBinary(bin)) || eligibility?.remote?.hasAnyBin?.(requiredAnyBins)) ? requiredAnyBins : []; const missingOs = requiredOs.length > 0 && !requiredOs.includes(process.platform) && !eligibility?.remote?.platforms?.some((platform) => requiredOs.includes(platform)) ? requiredOs : []; const missingEnv = []; for (const envName of requiredEnv) { if (process.env[envName]) continue; if (hookConfig?.env?.[envName]) continue; missingEnv.push(envName); } const configChecks = requiredConfig.map((pathStr) => { return { path: pathStr, value: resolveConfigPath(config, pathStr), satisfied: isConfigPathTruthy(config, pathStr) }; }); const missingConfig = configChecks.filter((check) => !check.satisfied).map((check) => check.path); const missing = always ? { bins: [], anyBins: [], env: [], config: [], os: [] } : { bins: missingBins, anyBins: missingAnyBins, env: missingEnv, config: missingConfig, os: missingOs }; const eligible = !disabled && (always || missing.bins.length === 0 && missing.anyBins.length === 0 && missing.env.length === 0 && missing.config.length === 0 && missing.os.length === 0); return { name: entry.hook.name, description: entry.hook.description, source: entry.hook.source, pluginId: entry.hook.pluginId, filePath: entry.hook.filePath, baseDir: entry.hook.baseDir, handlerPath: entry.hook.handlerPath, hookKey, emoji, homepage, events, always, disabled, eligible, managedByPlugin, requirements: { bins: requiredBins, anyBins: requiredAnyBins, env: requiredEnv, config: requiredConfig, os: requiredOs }, missing, configChecks, install: normalizeInstallOptions(entry) }; } function buildWorkspaceHookStatus(workspaceDir, opts) { return { workspaceDir, managedHooksDir: opts?.managedHooksDir ?? path.join(CONFIG_DIR, "hooks"), hooks: (opts?.entries ?? loadWorkspaceHookEntries(workspaceDir, opts)).map((entry) => buildHookStatus(entry, opts?.config, opts?.eligibility)) }; } //#endregion export { parseFrontmatter as a, shouldIncludeHook as i, loadWorkspaceHookEntries as n, resolveHookConfig as r, buildWorkspaceHookStatus as t };