From 137079fc2165ae0f0e837bdb80a14b99c6b386a7 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 15 Feb 2026 18:39:29 +0000 Subject: [PATCH] refactor(shared): share entry requirements evaluation --- src/agents/skills-status.ts | 42 +++++++++++----------------- src/hooks/hooks-status.ts | 32 ++++++++------------- src/shared/entry-status.ts | 56 +++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 45 deletions(-) create mode 100644 src/shared/entry-status.ts diff --git a/src/agents/skills-status.ts b/src/agents/skills-status.ts index 8bcf1cd6689..029b8e4254b 100644 --- a/src/agents/skills-status.ts +++ b/src/agents/skills-status.ts @@ -1,7 +1,6 @@ import path from "node:path"; import type { OpenClawConfig } from "../config/config.js"; -import { resolveEmojiAndHomepage } from "../shared/entry-metadata.js"; -import { evaluateRequirementsFromMetadataWithRemote } from "../shared/requirements.js"; +import { evaluateEntryMetadataRequirements } from "../shared/entry-status.js"; import { CONFIG_DIR } from "../utils.js"; import { hasBinary, @@ -184,34 +183,27 @@ function buildSkillStatus( const allowBundled = resolveBundledAllowlist(config); const blockedByAllowlist = !isBundledSkillAllowed(entry, allowBundled); const always = entry.metadata?.always === true; - const { emoji, homepage } = resolveEmojiAndHomepage({ - metadata: entry.metadata, - frontmatter: entry.frontmatter, - }); const bundled = bundledNames && bundledNames.size > 0 ? bundledNames.has(entry.skill.name) : entry.skill.source === "openclaw-bundled"; - const { - required, - missing, - eligible: requirementsSatisfied, - configChecks, - } = evaluateRequirementsFromMetadataWithRemote({ - always, - metadata: entry.metadata, - hasLocalBin: hasBinary, - localPlatform: process.platform, - remote: eligibility?.remote, - isEnvSatisfied: (envName) => - Boolean( - process.env[envName] || - skillConfig?.env?.[envName] || - (skillConfig?.apiKey && entry.metadata?.primaryEnv === envName), - ), - isConfigSatisfied: (pathStr) => isConfigPathTruthy(config, pathStr), - }); + const { emoji, homepage, required, missing, requirementsSatisfied, configChecks } = + evaluateEntryMetadataRequirements({ + always, + metadata: entry.metadata, + frontmatter: entry.frontmatter, + hasLocalBin: hasBinary, + localPlatform: process.platform, + remote: eligibility?.remote, + isEnvSatisfied: (envName) => + Boolean( + process.env[envName] || + skillConfig?.env?.[envName] || + (skillConfig?.apiKey && entry.metadata?.primaryEnv === envName), + ), + isConfigSatisfied: (pathStr) => isConfigPathTruthy(config, pathStr), + }); const eligible = !disabled && !blockedByAllowlist && requirementsSatisfied; return { diff --git a/src/hooks/hooks-status.ts b/src/hooks/hooks-status.ts index b8db68abbfe..0a9867f76fe 100644 --- a/src/hooks/hooks-status.ts +++ b/src/hooks/hooks-status.ts @@ -1,8 +1,7 @@ import path from "node:path"; import type { OpenClawConfig } from "../config/config.js"; import type { HookEligibilityContext, HookEntry, HookInstallSpec } from "./types.js"; -import { resolveEmojiAndHomepage } from "../shared/entry-metadata.js"; -import { evaluateRequirementsFromMetadataWithRemote } from "../shared/requirements.js"; +import { evaluateEntryMetadataRequirements } from "../shared/entry-status.js"; import { CONFIG_DIR } from "../utils.js"; import { hasBinary, isConfigPathTruthy, resolveHookConfig } from "./config.js"; import { loadWorkspaceHookEntries } from "./workspace.js"; @@ -101,26 +100,19 @@ function buildHookStatus( const managedByPlugin = entry.hook.source === "openclaw-plugin"; const disabled = managedByPlugin ? false : hookConfig?.enabled === false; const always = entry.metadata?.always === true; - const { emoji, homepage } = resolveEmojiAndHomepage({ - metadata: entry.metadata, - frontmatter: entry.frontmatter, - }); const events = entry.metadata?.events ?? []; - const { - required, - missing, - eligible: requirementsSatisfied, - configChecks, - } = evaluateRequirementsFromMetadataWithRemote({ - always, - metadata: entry.metadata, - hasLocalBin: hasBinary, - localPlatform: process.platform, - remote: eligibility?.remote, - isEnvSatisfied: (envName) => Boolean(process.env[envName] || hookConfig?.env?.[envName]), - isConfigSatisfied: (pathStr) => isConfigPathTruthy(config, pathStr), - }); + const { emoji, homepage, required, missing, requirementsSatisfied, configChecks } = + evaluateEntryMetadataRequirements({ + always, + metadata: entry.metadata, + frontmatter: entry.frontmatter, + hasLocalBin: hasBinary, + localPlatform: process.platform, + remote: eligibility?.remote, + isEnvSatisfied: (envName) => Boolean(process.env[envName] || hookConfig?.env?.[envName]), + isConfigSatisfied: (pathStr) => isConfigPathTruthy(config, pathStr), + }); const eligible = !disabled && requirementsSatisfied; diff --git a/src/shared/entry-status.ts b/src/shared/entry-status.ts new file mode 100644 index 00000000000..0ac4ea29116 --- /dev/null +++ b/src/shared/entry-status.ts @@ -0,0 +1,56 @@ +import { resolveEmojiAndHomepage } from "./entry-metadata.js"; +import { + evaluateRequirementsFromMetadataWithRemote, + type RequirementConfigCheck, + type Requirements, + type RequirementsMetadata, +} from "./requirements.js"; + +export function evaluateEntryMetadataRequirements(params: { + always: boolean; + metadata?: (RequirementsMetadata & { emoji?: string; homepage?: string }) | null; + frontmatter?: { + emoji?: string; + homepage?: string; + website?: string; + url?: string; + } | null; + hasLocalBin: (bin: string) => boolean; + localPlatform: string; + remote?: { + hasBin?: (bin: string) => boolean; + hasAnyBin?: (bins: string[]) => boolean; + platforms?: string[]; + }; + isEnvSatisfied: (envName: string) => boolean; + isConfigSatisfied: (pathStr: string) => boolean; +}): { + emoji?: string; + homepage?: string; + required: Requirements; + missing: Requirements; + requirementsSatisfied: boolean; + configChecks: RequirementConfigCheck[]; +} { + const { emoji, homepage } = resolveEmojiAndHomepage({ + metadata: params.metadata, + frontmatter: params.frontmatter, + }); + const { required, missing, eligible, configChecks } = evaluateRequirementsFromMetadataWithRemote({ + always: params.always, + metadata: params.metadata ?? undefined, + hasLocalBin: params.hasLocalBin, + localPlatform: params.localPlatform, + remote: params.remote, + isEnvSatisfied: params.isEnvSatisfied, + isConfigSatisfied: params.isConfigSatisfied, + }); + return { + ...(emoji ? { emoji } : {}), + ...(homepage ? { homepage } : {}), + required, + missing, + requirementsSatisfied: eligible, + configChecks, + }; +}