import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { r as stripAnsi, t as sanitizeForLog } from "./ansi-BI9w76Cm.js";
import { t as formatCliCommand } from "./command-format-CKGmlpAQ.js";
import { t as formatDocsLink } from "./links-By4cOnuz.js";
import { r as theme } from "./theme-CLQ2HbnQ.js";
import { o as sanitizeHostExecEnv } from "./host-env-security-0A1VaBlB.js";
import { g as shortenHomePath, p as resolveUserPath, t as CONFIG_DIR } from "./utils-CCC-BEJH.js";
import { n as defaultRuntime } from "./runtime-pp2OBTec.js";
import { s as fetchClawHubSkillCard, u as fetchClawHubSkillVerification } from "./clawhub-BCJqJHJi.js";
import { m as writeJson } from "./json-files-2umMHm0W.js";
import { n as isImmutableGitCommitRef, r as parseGitPluginSpec } from "./git-install-DT1sXm1v.js";
import { l as resolveAgentIdByWorkspacePath } from "./agent-scope-93WG1XkL.js";
import { c as resolveDefaultAgentId, o as resolveAgentWorkspaceDir } from "./agent-scope-config-DIAx4Oyb.js";
import { r as runCommandWithTimeout } from "./exec-CEH5Br3m.js";
import { i as getRuntimeConfig } from "./io-meLURSOC.js";
import { s as withTempDir } from "./install-source-utils-DxQ0QLl9.js";
import "./config-R-DNt3t0.js";
import { o as redactSensitiveUrlLikeString } from "./redact-sensitive-url-DT6j3eLK.js";
import { s as parseFrontmatter } from "./config-BYUi5aZZ.js";
import { a as readTrackedClawHubSkillSlugs, c as untrackClawHubSkill, f as validateRequestedSkillSlug, l as updateSkillsFromClawHub, n as resolveSkillStatusEntry, o as resolveClawHubSkillVerificationTarget, r as installSkillFromClawHub, s as searchSkillsFromClawHub, u as installExtractedSkillRoot } from "./status-CqoSOPHM.js";
import { a as proposeUpdateSkill, c as readSkillProposalDraftFile, d as reviseSkillProposal, i as proposeCreateSkill, l as rejectSkillProposal, n as inspectSkillProposal, o as quarantineSkillProposal, r as listSkillProposals, s as readSkillProposalDraftDirectory, t as applySkillProposal } from "./service-xbHNJ5wk.js";
import { n as decorativePrefix, t as decorativeEmoji } from "./decorative-emoji-CZiXLsCh.js";
import { n as renderTable, t as getTerminalTableWidth } from "./table-D7IbR5VI.js";
import { t as resolveOptionFromCommand } from "./cli-utils-DUGESl4i.js";
import { r as parseStrictPositiveIntOption } from "./helpers-gBVG4H2O.js";
import path from "node:path";
import fs from "node:fs/promises";
//#region src/skills/lifecycle/source-install.ts
const SKILL_SOURCE_ORIGIN_RELATIVE_PATH = path.join(".openclaw", "source-origin.json");
const DEFAULT_GIT_TIMEOUT_MS = 12e4;
function createGitCommandEnv() {
	return sanitizeHostExecEnv({
		baseEnv: {
			...process.env,
			GIT_CONFIG_NOSYSTEM: "1",
			GIT_TERMINAL_PROMPT: "0"
		},
		blockPathOverrides: false
	});
}
function formatGitCommandFailure(params) {
	const detail = sanitizeForLog(redactSensitiveUrlLikeString(params.stderr.trim() || params.stdout.trim() || "git failed"));
	return `failed to ${params.action} ${sanitizeForLog(redactSensitiveUrlLikeString(params.label))}: ${detail}`;
}
async function runGitCommand(params) {
	const result = await runCommandWithTimeout(params.argv, {
		baseEnv: {},
		cwd: params.cwd,
		timeoutMs: params.timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS,
		env: createGitCommandEnv()
	});
	if (result.code !== 0) return {
		ok: false,
		error: formatGitCommandFailure({
			action: params.action,
			label: params.label,
			stdout: result.stdout,
			stderr: result.stderr
		})
	};
	return {
		ok: true,
		stdout: result.stdout
	};
}
async function resolveGitCommitish(params) {
	const candidates = params.ref.startsWith("origin/") ? [params.ref] : [params.ref, `origin/${params.ref}`];
	for (const candidate of candidates) {
		const resolved = await runCommandWithTimeout([
			"git",
			"rev-parse",
			"--verify",
			"--quiet",
			`${candidate}^{commit}`
		], {
			baseEnv: {},
			cwd: params.repoDir,
			timeoutMs: params.timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS,
			env: createGitCommandEnv()
		});
		const commit = normalizeOptionalString(resolved.stdout);
		if (resolved.code === 0 && commit) return {
			ok: true,
			commitish: commit
		};
	}
	return {
		ok: false,
		error: `failed to resolve ref ${sanitizeForLog(redactSensitiveUrlLikeString(params.ref))} in ${sanitizeForLog(redactSensitiveUrlLikeString(params.label))}`
	};
}
async function readSkillNameFromFrontmatter(skillDir) {
	try {
		return normalizeOptionalString(parseFrontmatter(await fs.readFile(path.join(skillDir, "SKILL.md"), "utf8")).name) ?? null;
	} catch {
		return null;
	}
}
function resolveFallbackSlugFromPath(sourcePath) {
	return path.basename(path.resolve(sourcePath)).trim();
}
async function resolveSkillInstallSlug(params) {
	const explicit = normalizeOptionalString(params.slug);
	if (explicit) return validateRequestedSkillSlug(explicit);
	const frontmatterName = await readSkillNameFromFrontmatter(params.sourceDir);
	if (frontmatterName) try {
		return validateRequestedSkillSlug(frontmatterName);
	} catch {}
	return validateRequestedSkillSlug(params.fallbackLabel);
}
async function writeSkillSourceOrigin(targetDir, origin) {
	await writeJson(path.join(targetDir, SKILL_SOURCE_ORIGIN_RELATIVE_PATH), origin, { trailingNewline: true });
}
async function removeClawHubInstallMetadata(targetDir) {
	await Promise.all([fs.rm(path.join(targetDir, ".clawhub"), {
		recursive: true,
		force: true
	}), fs.rm(path.join(targetDir, ".clawdhub"), {
		recursive: true,
		force: true
	})]);
}
async function copyGitWorktreeExport(params) {
	try {
		await fs.cp(params.repoDir, params.exportDir, {
			recursive: true,
			filter: (source) => !path.relative(params.repoDir, source).split(path.sep).includes(".git")
		});
		return { ok: true };
	} catch (err) {
		return {
			ok: false,
			error: `failed to prepare git skill source: ${String(err)}`
		};
	}
}
async function installLocalSkillDir(params) {
	const slug = await resolveSkillInstallSlug({
		sourceDir: params.sourceDir,
		fallbackLabel: params.fallbackLabel,
		slug: params.slug
	});
	const install = await installExtractedSkillRoot({
		workspaceDir: params.workspaceDir,
		slug,
		extractedRoot: params.sourceDir,
		mode: params.force ? "update" : "install",
		timeoutMs: params.timeoutMs,
		logger: params.logger,
		policy: {
			config: params.config,
			installId: params.source,
			origin: {
				type: params.source,
				spec: params.sourceSpec,
				...params.git?.commit ? { commit: params.git.commit } : {},
				...params.git?.ref ? { ref: params.git.ref } : {}
			},
			source: params.source === "git" ? {
				kind: "git",
				authority: "third-party",
				mutable: !isImmutableGitCommitRef(params.git?.ref),
				network: true
			} : {
				kind: "local-path",
				authority: "user",
				mutable: true,
				network: false
			},
			requestedSpecifier: params.sourceSpec
		}
	});
	if (!install.ok) return {
		ok: false,
		error: install.error
	};
	await removeClawHubInstallMetadata(install.targetDir);
	await writeSkillSourceOrigin(install.targetDir, {
		version: 1,
		source: params.source,
		spec: params.sourceSpec,
		slug,
		installedAt: Date.now(),
		...params.git ? { git: params.git } : {}
	});
	await untrackClawHubSkill(params.workspaceDir, slug);
	return {
		ok: true,
		slug,
		targetDir: install.targetDir,
		source: params.source,
		...params.git ? { git: params.git } : {}
	};
}
async function installGitSkill(params) {
	const parsed = parseGitPluginSpec(params.spec);
	if (!parsed) return {
		ok: false,
		error: `Unsupported git skill spec: ${params.spec}`
	};
	return await withTempDir("openclaw-git-skill-", async (tmpDir) => {
		const repoDir = path.join(tmpDir, "repo");
		const exportDir = path.join(tmpDir, "export");
		params.logger?.info?.(`Cloning ${sanitizeForLog(redactSensitiveUrlLikeString(parsed.label))}...`);
		const clone = await runGitCommand({
			argv: parsed.ref ? [
				"git",
				"clone",
				parsed.url,
				repoDir
			] : [
				"git",
				"clone",
				"--depth",
				"1",
				parsed.url,
				repoDir
			],
			action: "clone",
			label: parsed.label,
			timeoutMs: params.timeoutMs
		});
		if (!clone.ok) return clone;
		if (parsed.ref) {
			const commitish = await resolveGitCommitish({
				repoDir,
				ref: parsed.ref,
				label: parsed.label,
				timeoutMs: params.timeoutMs
			});
			if (!commitish.ok) return commitish;
			const checkout = await runGitCommand({
				argv: [
					"git",
					"switch",
					"--detach",
					"--",
					commitish.commitish
				],
				action: `checkout ${parsed.ref}`,
				label: parsed.label,
				cwd: repoDir,
				timeoutMs: params.timeoutMs
			});
			if (!checkout.ok) return checkout;
		}
		const rev = await runGitCommand({
			argv: [
				"git",
				"rev-parse",
				"HEAD"
			],
			action: "resolve commit for",
			label: parsed.label,
			cwd: repoDir,
			timeoutMs: params.timeoutMs
		});
		if (!rev.ok) return rev;
		const git = {
			url: redactSensitiveUrlLikeString(parsed.url),
			...parsed.ref ? { ref: parsed.ref } : {},
			commit: normalizeOptionalString(rev.stdout),
			resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
		};
		const exported = await copyGitWorktreeExport({
			repoDir,
			exportDir
		});
		if (!exported.ok) return exported;
		return await installLocalSkillDir({
			workspaceDir: params.workspaceDir,
			sourceDir: exportDir,
			sourceSpec: redactSensitiveUrlLikeString(parsed.normalizedSpec),
			source: "git",
			fallbackLabel: path.basename(parsed.label),
			slug: params.slug,
			force: params.force,
			timeoutMs: params.timeoutMs,
			logger: params.logger,
			config: params.config,
			git
		});
	});
}
async function installPathSkill(params) {
	const sourceDir = resolveUserPath(params.spec);
	let stat;
	try {
		stat = await fs.stat(sourceDir);
	} catch {
		return {
			ok: false,
			error: `Skill path not found: ${sourceDir}`
		};
	}
	if (!stat.isDirectory()) return {
		ok: false,
		error: `Skill path is not a directory: ${sourceDir}`
	};
	return await installLocalSkillDir({
		workspaceDir: params.workspaceDir,
		sourceDir,
		sourceSpec: params.spec,
		source: "path",
		fallbackLabel: resolveFallbackSlugFromPath(sourceDir),
		slug: params.slug,
		force: params.force,
		timeoutMs: params.timeoutMs,
		logger: params.logger,
		config: params.config
	});
}
function isSkillSourceInstallSpec(raw) {
	const trimmed = raw.trim();
	return trimmed.toLowerCase().startsWith("git:") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~/") || path.isAbsolute(trimmed);
}
async function installSkillFromSource(params) {
	const spec = params.spec.trim();
	if (spec.toLowerCase().startsWith("git:")) return await installGitSkill({
		...params,
		spec
	});
	return await installPathSkill({
		...params,
		spec
	});
}
//#endregion
//#region src/cli/skills-cli.format.ts
function appendClawHubHint(output, json) {
	if (json) return output;
	return `${output}\n\nTip: use \`openclaw skills search\`, \`openclaw skills install\`, and \`openclaw skills update\` for ClawHub-backed skills.`;
}
function formatSkillStatus(skill) {
	if (skill.disabled) return theme.warn(decorativePrefix("⏸", "disabled"));
	if (skill.blockedByAllowlist) return theme.warn(decorativePrefix("🚫", "blocked"));
	if (skill.blockedByAgentFilter) return theme.warn(decorativePrefix("🚫", "excluded"));
	if (skill.eligible) return theme.success("✓ ready");
	return theme.warn("△ needs setup");
}
function normalizeSkillEmoji(emoji) {
	if (emoji) return emoji.replaceAll("︎", "️");
	return decorativeEmoji("📦");
}
const REMAINING_ESC_SEQUENCE_REGEX = new RegExp(String.raw`\u001b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])`, "g");
const JSON_CONTROL_CHAR_REGEX = new RegExp(String.raw`[\u0000-\u001f\u007f-\u009f]`, "g");
function sanitizeJsonString(value) {
	return stripAnsi(value).replace(REMAINING_ESC_SEQUENCE_REGEX, "").replace(JSON_CONTROL_CHAR_REGEX, "");
}
function sanitizeJsonValue(value) {
	if (typeof value === "string") return sanitizeJsonString(value);
	if (Array.isArray(value)) return value.map((item) => sanitizeJsonValue(item));
	if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, sanitizeJsonValue(entryValue)]));
	return value;
}
function formatSkillName(skill) {
	const emoji = normalizeSkillEmoji(skill.emoji);
	const name = theme.command(sanitizeForLog(skill.name));
	return emoji ? `${emoji} ${name}` : name;
}
function formatSkillMissingSummary(skill) {
	const missing = [];
	if (skill.missing.bins.length > 0) missing.push(`bins: ${skill.missing.bins.join(", ")}`);
	if (skill.missing.anyBins.length > 0) missing.push(`anyBins: ${skill.missing.anyBins.join(", ")}`);
	if (skill.missing.env.length > 0) missing.push(`env: ${skill.missing.env.join(", ")}`);
	if (skill.missing.config.length > 0) missing.push(`config: ${skill.missing.config.join(", ")}`);
	if (skill.missing.os.length > 0) missing.push(`os: ${skill.missing.os.join(", ")}`);
	return missing.join("; ");
}
/** Render skill discovery status as sanitized JSON or a terminal table. */
function formatSkillsList(report, opts) {
	const isReadyForAgent = (skill) => skill.eligible && !skill.blockedByAgentFilter;
	const skills = opts.eligible ? report.skills.filter(isReadyForAgent) : report.skills;
	if (opts.json) {
		const jsonReport = sanitizeJsonValue({
			workspaceDir: report.workspaceDir,
			managedSkillsDir: report.managedSkillsDir,
			skills: skills.map((s) => ({
				name: s.name,
				description: s.description,
				emoji: s.emoji,
				eligible: s.eligible,
				disabled: s.disabled,
				blockedByAllowlist: s.blockedByAllowlist,
				blockedByAgentFilter: s.blockedByAgentFilter,
				modelVisible: s.modelVisible,
				userInvocable: s.userInvocable,
				commandVisible: s.commandVisible,
				source: s.source,
				bundled: s.bundled,
				primaryEnv: s.primaryEnv,
				homepage: s.homepage,
				missing: s.missing
			}))
		});
		return JSON.stringify(jsonReport, null, 2);
	}
	if (skills.length === 0) return appendClawHubHint(opts.eligible ? `No eligible skills found. Run \`${formatCliCommand("openclaw skills list")}\` to see all skills.` : "No skills found.", opts.json);
	const ready = skills.filter(isReadyForAgent);
	const tableWidth = getTerminalTableWidth();
	const rows = skills.map((skill) => {
		const missing = formatSkillMissingSummary(skill);
		return {
			Status: formatSkillStatus(skill),
			Skill: formatSkillName(skill),
			Description: theme.muted(skill.description),
			Source: skill.source,
			Missing: missing ? theme.warn(missing) : ""
		};
	});
	const columns = [
		{
			key: "Status",
			header: "Status",
			minWidth: 10
		},
		{
			key: "Skill",
			header: "Skill",
			minWidth: 22
		},
		{
			key: "Description",
			header: "Description",
			minWidth: 24,
			flex: true
		},
		{
			key: "Source",
			header: "Source",
			minWidth: 10
		}
	];
	if (opts.verbose) columns.push({
		key: "Missing",
		header: "Missing",
		minWidth: 18,
		flex: true
	});
	const lines = [];
	lines.push(`${theme.heading("Skills")} ${theme.muted(`(${ready.length}/${skills.length} ready)`)}`);
	lines.push(renderTable({
		width: tableWidth,
		columns,
		rows
	}).trimEnd());
	return appendClawHubHint(lines.join("\n"), opts.json);
}
/** Render one skill's status, requirements, install hints, and API-key setup details. */
function formatSkillInfo(report, skillName, opts) {
	const requestedName = skillName.trim();
	const safeRequestedName = sanitizeJsonString(sanitizeForLog(requestedName));
	const skill = resolveSkillStatusEntry(report.skills, requestedName);
	if (!skill) {
		if (opts.json) return JSON.stringify(sanitizeJsonValue({
			error: "not found",
			skill: requestedName
		}), null, 2);
		return appendClawHubHint(`Skill "${safeRequestedName}" not found. Run \`${formatCliCommand("openclaw skills list")}\` to see available skills.`, opts.json);
	}
	if (opts.json) return JSON.stringify(sanitizeJsonValue(skill), null, 2);
	const lines = [];
	const emoji = normalizeSkillEmoji(skill.emoji);
	const status = skill.disabled ? theme.warn(decorativePrefix("⏸", "Disabled")) : skill.blockedByAllowlist ? theme.warn(decorativePrefix("🚫", "Blocked by allowlist")) : skill.blockedByAgentFilter ? theme.warn(decorativePrefix("🚫", "Excluded by agent allowlist")) : skill.eligible ? theme.success("✓ Ready") : theme.warn("△ Needs setup");
	const safeName = sanitizeForLog(skill.name);
	const safeHomepage = skill.homepage ? sanitizeForLog(skill.homepage) : void 0;
	const safeSkillKey = sanitizeForLog(skill.skillKey);
	lines.push(`${emoji ? `${emoji} ` : ""}${theme.heading(safeName)} ${status}`);
	lines.push("");
	lines.push(sanitizeForLog(skill.description));
	lines.push("");
	lines.push(theme.heading("Details:"));
	lines.push(`${theme.muted("  Source:")} ${sanitizeForLog(skill.source)}`);
	lines.push(`${theme.muted("  Path:")} ${shortenHomePath(skill.filePath)}`);
	if (safeHomepage) lines.push(`${theme.muted("  Homepage:")} ${safeHomepage}`);
	lines.push(`${theme.muted("  Visible to model:")} ${skill.modelVisible ? theme.success("yes") : theme.warn("no")}`);
	lines.push(`${theme.muted("  Available as command:")} ${skill.commandVisible ? theme.success("yes") : theme.warn("no")}`);
	if (skill.blockedByAgentFilter) lines.push(`${theme.muted("  Agent allowlist:")} excludes this skill`);
	if (skill.primaryEnv) lines.push(`${theme.muted("  Primary env:")} ${skill.primaryEnv}`);
	if (skill.requirements.bins.length > 0 || skill.requirements.anyBins.length > 0 || skill.requirements.env.length > 0 || skill.requirements.config.length > 0 || skill.requirements.os.length > 0) {
		lines.push("");
		lines.push(theme.heading("Requirements:"));
		if (skill.requirements.bins.length > 0) {
			const binsStatus = skill.requirements.bins.map((bin) => {
				return skill.missing.bins.includes(bin) ? theme.error(`✗ ${bin}`) : theme.success(`✓ ${bin}`);
			});
			lines.push(`${theme.muted("  Binaries:")} ${binsStatus.join(", ")}`);
		}
		if (skill.requirements.anyBins.length > 0) {
			const anyBinsMissing = skill.missing.anyBins.length > 0;
			const anyBinsStatus = skill.requirements.anyBins.map((bin) => {
				return anyBinsMissing ? theme.error(`✗ ${bin}`) : theme.success(`✓ ${bin}`);
			});
			lines.push(`${theme.muted("  Any binaries:")} ${anyBinsStatus.join(", ")}`);
		}
		if (skill.requirements.env.length > 0) {
			const envStatus = skill.requirements.env.map((env) => {
				return skill.missing.env.includes(env) ? theme.error(`✗ ${env}`) : theme.success(`✓ ${env}`);
			});
			lines.push(`${theme.muted("  Environment:")} ${envStatus.join(", ")}`);
		}
		if (skill.requirements.config.length > 0) {
			const configStatus = skill.requirements.config.map((cfg) => {
				return skill.missing.config.includes(cfg) ? theme.error(`✗ ${cfg}`) : theme.success(`✓ ${cfg}`);
			});
			lines.push(`${theme.muted("  Config:")} ${configStatus.join(", ")}`);
		}
		if (skill.requirements.os.length > 0) {
			const osStatus = skill.requirements.os.map((osName) => {
				return skill.missing.os.includes(osName) ? theme.error(`✗ ${osName}`) : theme.success(`✓ ${osName}`);
			});
			lines.push(`${theme.muted("  OS:")} ${osStatus.join(", ")}`);
		}
	}
	if (skill.install.length > 0 && !skill.eligible) {
		lines.push("");
		lines.push(theme.heading("Install options:"));
		for (const inst of skill.install) lines.push(`  ${theme.warn("→")} ${inst.label}`);
	}
	if (skill.primaryEnv && skill.missing.env.includes(skill.primaryEnv)) {
		lines.push("");
		lines.push(theme.heading("API key setup:"));
		if (safeHomepage) lines.push(`  Get your key: ${safeHomepage}`);
		lines.push(`  Save via UI: ${theme.muted("Control UI → Skills → ")}${safeName}${theme.muted(" → Save key")}`);
		lines.push(`  Save via CLI: ${formatCliCommand(`openclaw config set skills.entries.${safeSkillKey}.apiKey YOUR_KEY`)}`);
		lines.push(`  Stored in: ${theme.muted("$OPENCLAW_CONFIG_PATH")} ${theme.muted("(default: ~/.openclaw/openclaw.json)")}`);
	}
	return appendClawHubHint(lines.join("\n"), opts.json);
}
/** Render aggregate setup health for all discovered skills. */
function formatSkillsCheck(report, opts) {
	const eligible = report.skills.filter((s) => s.eligible);
	const modelVisible = report.skills.filter((s) => s.modelVisible);
	const commandVisible = report.skills.filter((s) => s.commandVisible);
	const disabled = report.skills.filter((s) => s.disabled);
	const blocked = report.skills.filter((s) => s.blockedByAllowlist && !s.disabled);
	const agentFiltered = report.skills.filter((s) => s.eligible && s.blockedByAgentFilter);
	const promptHidden = report.skills.filter((s) => s.eligible && !s.blockedByAgentFilter && !s.modelVisible);
	const missingReqs = report.skills.filter((s) => !s.eligible && !s.disabled && !s.blockedByAllowlist && !s.blockedByAgentFilter);
	const agentId = report.agentId ?? opts.agent;
	if (opts.json) return JSON.stringify(sanitizeJsonValue({
		agentId,
		agentSkillFilter: report.agentSkillFilter,
		workspaceDir: report.workspaceDir,
		managedSkillsDir: report.managedSkillsDir,
		summary: {
			total: report.skills.length,
			eligible: eligible.length,
			modelVisible: modelVisible.length,
			commandVisible: commandVisible.length,
			disabled: disabled.length,
			blocked: blocked.length,
			agentFiltered: agentFiltered.length,
			notInjected: promptHidden.length,
			missingRequirements: missingReqs.length
		},
		eligible: eligible.map((s) => s.name),
		modelVisible: modelVisible.map((s) => s.name),
		commandVisible: commandVisible.map((s) => s.name),
		disabled: disabled.map((s) => s.name),
		blocked: blocked.map((s) => s.name),
		agentFiltered: agentFiltered.map((s) => s.name),
		notInjected: promptHidden.map((s) => ({
			name: s.name,
			reason: "disable-model-invocation"
		})),
		missingRequirements: missingReqs.map((s) => ({
			name: s.name,
			missing: s.missing,
			install: s.install
		}))
	}), null, 2);
	const lines = [];
	lines.push(theme.heading("Skills Status Check"));
	if (agentId) lines.push(`${theme.muted("Agent:")} ${sanitizeForLog(agentId)}`);
	lines.push("");
	lines.push(`${theme.muted("Total:")} ${report.skills.length}`);
	lines.push(`${theme.success("✓")} ${theme.muted("Eligible:")} ${eligible.length}`);
	lines.push(`${theme.success("✓")} ${theme.muted("Visible to model:")} ${modelVisible.length}`);
	lines.push(`${theme.success("✓")} ${theme.muted("Available as command:")} ${commandVisible.length}`);
	lines.push(`${theme.warn(decorativePrefix("⏸", "Disabled:"))} ${theme.muted(String(disabled.length))}`);
	lines.push(`${theme.warn(decorativePrefix("🚫", "Blocked by allowlist:"))} ${theme.muted(String(blocked.length))}`);
	if (agentId || agentFiltered.length > 0) lines.push(`${theme.warn(decorativePrefix("🚫", "Excluded by agent allowlist:"))} ${theme.muted(String(agentFiltered.length))}`);
	if (promptHidden.length > 0) lines.push(`${theme.warn("△")} ${theme.muted("Ready but hidden from model prompt:")} ${promptHidden.length}`);
	lines.push(`${theme.error("✗")} ${theme.muted("Missing requirements:")} ${missingReqs.length}`);
	if (modelVisible.length > 0 || commandVisible.length > 0 || promptHidden.length > 0) {
		lines.push("");
		lines.push(theme.heading("What this means:"));
		lines.push(`  ${theme.muted("Eligible:")} installed and requirements pass; the agent may still exclude it.`);
		if (modelVisible.length > 0) lines.push(`  ${theme.muted("Visible to model:")} the agent can see the skill instructions during normal chat.`);
		if (commandVisible.length > 0) lines.push(`  ${theme.muted("Available as command:")} people, scripts, or cron jobs can call the skill explicitly.`);
		if (promptHidden.length > 0) lines.push(`  ${theme.muted("Hidden from model prompt:")} installed and ready, but kept out of normal chat.`);
	}
	if (modelVisible.length > 0) {
		lines.push("");
		lines.push(theme.heading("Ready and visible to model:"));
		for (const skill of modelVisible) {
			const emoji = normalizeSkillEmoji(skill.emoji);
			lines.push(`  ${emoji ? `${emoji} ` : ""}${sanitizeForLog(skill.name)}`);
		}
	}
	if (promptHidden.length > 0) {
		lines.push("");
		lines.push(theme.heading("Ready but hidden from model prompt:"));
		for (const skill of promptHidden) {
			const emoji = normalizeSkillEmoji(skill.emoji);
			const reason = skill.commandVisible ? "skill hides its instructions from the model; commands/cron may still use it" : "skill hides its instructions from the model and is not exposed as a command";
			lines.push(`  ${emoji ? `${emoji} ` : ""}${sanitizeForLog(skill.name)} ${theme.muted(`(${reason})`)}`);
		}
	}
	if (agentFiltered.length > 0) {
		lines.push("");
		lines.push(theme.heading("Excluded by agent allowlist:"));
		for (const skill of agentFiltered) {
			const emoji = normalizeSkillEmoji(skill.emoji);
			lines.push(`  ${emoji ? `${emoji} ` : ""}${sanitizeForLog(skill.name)} ${theme.muted("(loaded, but this agent is not allowed to see/use it)")}`);
		}
	}
	if (missingReqs.length > 0) {
		lines.push("");
		lines.push(theme.heading("Missing requirements:"));
		for (const skill of missingReqs) {
			const emoji = normalizeSkillEmoji(skill.emoji);
			const missing = formatSkillMissingSummary(skill);
			lines.push(`  ${emoji ? `${emoji} ` : ""}${sanitizeForLog(skill.name)} ${theme.muted(`(${missing})`)}`);
		}
	}
	return appendClawHubHint(lines.join("\n"), opts.json);
}
//#endregion
//#region src/cli/skills-cli.ts
function resolveSkillsWorkspace(options) {
	const config = getRuntimeConfig();
	const explicitAgentId = normalizeOptionalString(options?.agentId);
	const inferredAgentId = explicitAgentId ? void 0 : resolveAgentIdByWorkspacePath(config, options?.cwd ?? process.cwd());
	const agentId = explicitAgentId ?? inferredAgentId ?? resolveDefaultAgentId(config);
	return {
		config,
		agentId,
		workspaceDir: resolveAgentWorkspaceDir(config, agentId)
	};
}
function resolveAgentOption(command, opts) {
	return resolveOptionFromCommand(command, "agent") ?? opts?.agent;
}
async function loadSkillsStatusReport(options) {
	const { config, workspaceDir, agentId } = resolveSkillsWorkspace(options);
	const { buildWorkspaceSkillStatus } = await import("./status-yUyFl--_.js");
	return buildWorkspaceSkillStatus(workspaceDir, {
		config,
		agentId
	});
}
async function runSkillsAction(render, options) {
	try {
		const report = await loadSkillsStatusReport(options);
		defaultRuntime.writeStdout(render(report));
	} catch (err) {
		defaultRuntime.error(String(err));
		defaultRuntime.exit(1);
	}
}
function resolveActiveWorkspaceDir(options) {
	return resolveSkillsWorkspace(options).workspaceDir;
}
function resolveSkillsWorkspaceForCommand(command, opts) {
	return resolveSkillsWorkspace({ agentId: resolveAgentOption(command ?? void 0, opts) });
}
function resolveClawHubTargetWorkspaceDir(command, opts) {
	const agentId = resolveAgentOption(command, opts);
	if (opts.global && normalizeOptionalString(agentId)) {
		defaultRuntime.error("Use either --global or --agent, not both.");
		defaultRuntime.exit(1);
		return;
	}
	if (opts.global) return CONFIG_DIR;
	return resolveActiveWorkspaceDir({ agentId });
}
function shouldFailSkillVerification(result) {
	const envelope = result;
	return envelope.ok !== true || envelope.decision !== "pass";
}
function buildSkillVerificationOutput(result, target) {
	return {
		...result,
		openclaw: { resolution: {
			source: target.resolution.source,
			selector: target.resolution.selector,
			registry: target.resolution.registry,
			installedVersion: target.resolution.installedVersion
		} }
	};
}
function readVerifiedSkillCardUrl(result) {
	if (!result.card || typeof result.card !== "object" || Array.isArray(result.card)) return {
		ok: false,
		error: "ClawHub verification response did not include a Skill Card URL."
	};
	const card = result.card;
	if (card.available === false) return {
		ok: false,
		error: "Skill Card is not available."
	};
	const url = normalizeOptionalString(card.url);
	if (!url) return {
		ok: false,
		error: "ClawHub verification response did not include a Skill Card URL."
	};
	return {
		ok: true,
		url
	};
}
function formatSkillProposalList(manifest) {
	if (manifest.proposals.length === 0) return "No skill proposals.\n";
	return `${manifest.proposals.map((entry) => `${entry.id}  ${entry.status}  ${entry.kind}  ${entry.skillKey}  ${entry.title}`).join("\n")}\n`;
}
function formatSkillProposalInspect(read) {
	const { record } = read;
	const supportFiles = read.supportFiles && read.supportFiles.length > 0 ? [
		"",
		"Support files:",
		...read.supportFiles.flatMap((file) => [
			"",
			`--- ${file.path} ---`,
			file.content
		])
	] : [];
	return [
		`ID: ${record.id}`,
		`Status: ${record.status}`,
		`Kind: ${record.kind}`,
		`Skill: ${record.target.skillName}`,
		`Target: ${record.target.skillFile}`,
		`Scanner: ${record.scan.state}`,
		record.statusReason ? `Reason: ${record.statusReason}` : void 0,
		"",
		read.content,
		...supportFiles
	].filter((line) => line !== void 0).join("\n");
}
async function readSkillProposalInput(options) {
	const proposal = normalizeOptionalString(options.proposal);
	const proposalDir = normalizeOptionalString(options.proposalDir);
	if (proposal && proposalDir) throw new Error("Use either --proposal or --proposal-dir, not both.");
	if (!proposal && !proposalDir) throw new Error("Provide --proposal or --proposal-dir.");
	if (proposalDir) return await readSkillProposalDraftDirectory(proposalDir);
	return { content: await readSkillProposalDraftFile(proposal) };
}
/**
* Register the skills CLI commands
*/
function registerSkillsCli(program) {
	const skills = program.command("skills").description("List and inspect available skills").option("--agent <id>", "Target agent workspace (defaults to cwd-inferred, then default agent)").addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/skills", "docs.openclaw.ai/cli/skills")}\n`);
	skills.command("search").description("Search ClawHub skills").argument("[query...]", "Optional search query").option("--limit <n>", "Max results", (value) => parseStrictPositiveIntOption(value, "--limit")).option("--json", "Output as JSON", false).action(async (queryParts, opts) => {
		try {
			const results = await searchSkillsFromClawHub({
				query: normalizeOptionalString(queryParts.join(" ")),
				limit: opts.limit
			});
			if (opts.json) {
				defaultRuntime.writeJson({ results });
				return;
			}
			if (results.length === 0) {
				defaultRuntime.log("No ClawHub skills found.");
				return;
			}
			for (const entry of results) {
				const version = entry.version ? ` v${entry.version}` : "";
				const summary = entry.summary ? `  ${entry.summary}` : "";
				defaultRuntime.log(`${entry.slug}${version}  ${entry.displayName}${summary}`);
			}
		} catch (err) {
			defaultRuntime.error(String(err));
			defaultRuntime.exit(1);
		}
	});
	skills.command("install").description("Install a skill from ClawHub, git, or a local directory").argument("<slug>", "ClawHub skill slug, git:<repo>, or local skill directory").option("--version <version>", "Install a specific version").option("--force", "Overwrite an existing workspace skill", false).option("--global", "Install into the shared managed skills directory", false).option("--agent <id>", "Target agent workspace (defaults to cwd-inferred, then default agent)").option("--as <slug>", "Install a git/local skill under this slug").action(async (slug, opts, command) => {
		try {
			const workspaceDir = resolveClawHubTargetWorkspaceDir(command, opts);
			if (!workspaceDir) return;
			if (isSkillSourceInstallSpec(slug)) {
				if (opts.version) {
					defaultRuntime.error("--version is only supported for ClawHub skill installs.");
					defaultRuntime.exit(1);
					return;
				}
				const result = await installSkillFromSource({
					workspaceDir,
					spec: slug,
					slug: opts.as,
					force: Boolean(opts.force),
					logger: {
						info: (message) => defaultRuntime.log(message),
						warn: (message) => defaultRuntime.log(theme.warn(message))
					}
				});
				if (!result.ok) {
					defaultRuntime.error(result.error);
					defaultRuntime.exit(1);
					return;
				}
				defaultRuntime.log(`Installed ${result.slug} from ${result.source} -> ${result.targetDir}`);
				return;
			}
			if (opts.as) {
				defaultRuntime.error("--as is only supported for git and local directory skill installs.");
				defaultRuntime.exit(1);
				return;
			}
			const result = await installSkillFromClawHub({
				workspaceDir,
				slug,
				version: opts.version,
				force: Boolean(opts.force),
				logger: { info: (message) => defaultRuntime.log(message) }
			});
			if (!result.ok) {
				defaultRuntime.error(result.error);
				defaultRuntime.exit(1);
				return;
			}
			defaultRuntime.log(`Installed ${result.slug}@${result.version} -> ${result.targetDir}`);
		} catch (err) {
			defaultRuntime.error(String(err));
			defaultRuntime.exit(1);
		}
	});
	skills.command("update").description("Update ClawHub-installed skills in the active or shared managed directory").argument("[slug]", "Single skill slug").option("--all", "Update all tracked ClawHub skills", false).option("--global", "Update skills in the shared managed skills directory", false).option("--agent <id>", "Target agent workspace (defaults to cwd-inferred, then default agent)").action(async (slug, opts, command) => {
		try {
			if (!slug && !opts.all) {
				defaultRuntime.error("Provide a skill slug or use --all.");
				defaultRuntime.exit(1);
				return;
			}
			if (slug && opts.all) {
				defaultRuntime.error("Use either a skill slug or --all.");
				defaultRuntime.exit(1);
				return;
			}
			const workspaceDir = resolveClawHubTargetWorkspaceDir(command, opts);
			if (!workspaceDir) return;
			const tracked = await readTrackedClawHubSkillSlugs(workspaceDir);
			if (opts.all && tracked.length === 0) {
				defaultRuntime.log("No tracked ClawHub skills to update.");
				return;
			}
			const results = await updateSkillsFromClawHub({
				workspaceDir,
				slug,
				logger: { info: (message) => defaultRuntime.log(message) }
			});
			let failed = false;
			for (const result of results) {
				if (!result.ok) {
					failed = true;
					defaultRuntime.error(result.error);
					continue;
				}
				if (result.changed) {
					defaultRuntime.log(`Updated ${result.slug}: ${result.previousVersion ?? "unknown"} -> ${result.version}`);
					continue;
				}
				defaultRuntime.log(`${result.slug} already at ${result.version}`);
			}
			if (failed) defaultRuntime.exit(1);
		} catch (err) {
			defaultRuntime.error(String(err));
			defaultRuntime.exit(1);
		}
	});
	skills.command("verify").description("Verify a ClawHub skill with ClawHub").argument("<slug>", "ClawHub skill slug").option("--version <version>", "Verify a specific version").option("--tag <tag>", "Verify a dist tag").option("--card", "Print the generated Skill Card Markdown", false).option("--global", "Resolve installed skill metadata from the shared managed skills directory", false).option("--agent <id>", "Target agent workspace (defaults to cwd-inferred, then default agent)").action(async (slug, opts, command) => {
		let exitCode;
		try {
			const workspaceDir = resolveClawHubTargetWorkspaceDir(command, opts);
			if (!workspaceDir) return;
			const target = await resolveClawHubSkillVerificationTarget({
				workspaceDir,
				slug,
				version: opts.version,
				tag: opts.tag
			});
			if (!target.ok) {
				defaultRuntime.error(target.error);
				exitCode = 1;
			} else {
				const verification = await fetchClawHubSkillVerification({
					slug: target.slug,
					version: target.version,
					tag: target.tag,
					baseUrl: target.baseUrl
				});
				if (opts.card) {
					const cardUrl = readVerifiedSkillCardUrl(verification);
					if (!cardUrl.ok) {
						defaultRuntime.error(cardUrl.error);
						exitCode = 1;
					} else {
						const card = await fetchClawHubSkillCard({
							url: cardUrl.url,
							baseUrl: target.baseUrl
						});
						defaultRuntime.writeStdout(card.endsWith("\n") ? card : `${card}\n`);
						exitCode = shouldFailSkillVerification(verification) ? 1 : void 0;
					}
				} else {
					defaultRuntime.writeJson(buildSkillVerificationOutput(verification, target));
					exitCode = shouldFailSkillVerification(verification) ? 1 : void 0;
				}
			}
		} catch (err) {
			defaultRuntime.error(String(err));
			defaultRuntime.exit(1);
			return;
		}
		if (exitCode) defaultRuntime.exit(exitCode);
	});
	const workshop = skills.command("workshop").description("Manage pending skill proposals").option("--agent <id>", "Target agent workspace (defaults to cwd-inferred, then default agent)");
	workshop.command("list").description("List pending and completed skill proposals").option("--json", "Output as JSON", false).action(async (opts) => {
		try {
			const { workspaceDir } = resolveSkillsWorkspaceForCommand(workshop, opts);
			const manifest = await listSkillProposals({ workspaceDir });
			if (opts.json) {
				defaultRuntime.writeJson(manifest);
				return;
			}
			defaultRuntime.writeStdout(formatSkillProposalList(manifest));
		} catch (err) {
			defaultRuntime.error(String(err));
			defaultRuntime.exit(1);
		}
	});
	workshop.command("inspect").description("Inspect a skill proposal").argument("<proposal-id>", "Skill proposal id").option("--json", "Output as JSON", false).action(async (proposalId, opts) => {
		try {
			const { workspaceDir } = resolveSkillsWorkspaceForCommand(workshop, opts);
			const proposal = await inspectSkillProposal(proposalId, { workspaceDir });
			if (!proposal) {
				defaultRuntime.error(`Skill proposal not found: ${proposalId}`);
				defaultRuntime.exit(1);
				return;
			}
			if (opts.json) {
				defaultRuntime.writeJson(proposal);
				return;
			}
			defaultRuntime.writeStdout(formatSkillProposalInspect(proposal));
		} catch (err) {
			defaultRuntime.error(String(err));
			defaultRuntime.exit(1);
		}
	});
	workshop.command("propose-create").description("Create a pending proposal for a new workspace skill").requiredOption("--name <name>", "Skill name").requiredOption("--description <description>", "Skill description").option("--proposal <path>", "Path to PROPOSAL.md draft content").option("--proposal-dir <path>", "Path to proposal directory with PROPOSAL.md and UTF-8 text support files").option("--goal <text>", "Proposal or improvement goal").option("--evidence <text>", "Evidence or notes for the proposal").option("--json", "Output as JSON", false).action(async (opts, command) => {
		try {
			const { config, workspaceDir } = resolveSkillsWorkspaceForCommand(command.parent, opts);
			const draft = await readSkillProposalInput(opts);
			const proposal = await proposeCreateSkill({
				workspaceDir,
				config,
				name: opts.name,
				description: opts.description,
				content: draft.content,
				supportFiles: draft.supportFiles,
				createdBy: "cli",
				goal: opts.goal,
				evidence: opts.evidence
			});
			if (opts.json) {
				defaultRuntime.writeJson(proposal);
				return;
			}
			defaultRuntime.writeStdout(`${proposal.record.id}\n`);
		} catch (err) {
			defaultRuntime.error(String(err));
			defaultRuntime.exit(1);
		}
	});
	workshop.command("propose-update").description("Create a pending proposal for an existing workspace skill").argument("<skill>", "Skill name or key").option("--proposal <path>", "Path to PROPOSAL.md draft content").option("--proposal-dir <path>", "Path to proposal directory with PROPOSAL.md and UTF-8 text support files").option("--description <text>", "Concise proposal description").option("--goal <text>", "Proposal or improvement goal").option("--evidence <text>", "Evidence or notes for the proposal").option("--json", "Output as JSON", false).action(async (skill, opts, command) => {
		try {
			const { config, workspaceDir, agentId } = resolveSkillsWorkspaceForCommand(command.parent, opts);
			const draft = await readSkillProposalInput(opts);
			const proposal = await proposeUpdateSkill({
				workspaceDir,
				config,
				agentId,
				skillName: skill,
				description: opts.description,
				content: draft.content,
				supportFiles: draft.supportFiles,
				createdBy: "cli",
				goal: opts.goal,
				evidence: opts.evidence
			});
			if (opts.json) {
				defaultRuntime.writeJson(proposal);
				return;
			}
			defaultRuntime.writeStdout(`${proposal.record.id}\n`);
		} catch (err) {
			defaultRuntime.error(String(err));
			defaultRuntime.exit(1);
		}
	});
	workshop.command("revise").description("Revise a pending skill proposal").argument("<proposal-id>", "Skill proposal id").option("--proposal <path>", "Path to revised PROPOSAL.md draft content").option("--proposal-dir <path>", "Path to revised proposal directory with PROPOSAL.md and UTF-8 text support files").option("--description <description>", "Replacement proposal description").option("--goal <text>", "Replacement research or improvement goal").option("--evidence <text>", "Replacement evidence or notes for the proposal").option("--json", "Output as JSON", false).action(async (proposalId, opts, command) => {
		try {
			const { config, workspaceDir } = resolveSkillsWorkspaceForCommand(command.parent, opts);
			const draft = await readSkillProposalInput(opts);
			const proposal = await reviseSkillProposal({
				workspaceDir,
				config,
				proposalId,
				content: draft.content,
				supportFiles: draft.supportFiles,
				description: opts.description,
				goal: opts.goal,
				evidence: opts.evidence
			});
			if (opts.json) {
				defaultRuntime.writeJson(proposal);
				return;
			}
			defaultRuntime.writeStdout(`Revised ${proposal.record.id} ${proposal.record.proposedVersion}\n`);
		} catch (err) {
			defaultRuntime.error(String(err));
			defaultRuntime.exit(1);
		}
	});
	workshop.command("apply").description("Apply a pending skill proposal").argument("<proposal-id>", "Skill proposal id").option("--json", "Output as JSON", false).action(async (proposalId, opts, command) => {
		try {
			const { workspaceDir } = resolveSkillsWorkspaceForCommand(command.parent, opts);
			const applied = await applySkillProposal({
				workspaceDir,
				proposalId
			});
			if (opts.json) {
				defaultRuntime.writeJson(applied);
				return;
			}
			defaultRuntime.writeStdout(`Applied ${applied.record.id} -> ${applied.targetSkillFile}\n`);
		} catch (err) {
			defaultRuntime.error(String(err));
			defaultRuntime.exit(1);
		}
	});
	workshop.command("reject").description("Reject a pending skill proposal").argument("<proposal-id>", "Skill proposal id").option("--reason <text>", "Reason for rejection").option("--json", "Output as JSON", false).action(async (proposalId, opts, command) => {
		try {
			const { workspaceDir } = resolveSkillsWorkspaceForCommand(command.parent, opts);
			const record = await rejectSkillProposal({
				workspaceDir,
				proposalId,
				reason: opts.reason
			});
			if (opts.json) {
				defaultRuntime.writeJson(record);
				return;
			}
			defaultRuntime.writeStdout(`Rejected ${record.id}\n`);
		} catch (err) {
			defaultRuntime.error(String(err));
			defaultRuntime.exit(1);
		}
	});
	workshop.command("quarantine").description("Quarantine a skill proposal").argument("<proposal-id>", "Skill proposal id").option("--reason <text>", "Reason for quarantine").option("--json", "Output as JSON", false).action(async (proposalId, opts, command) => {
		try {
			const { workspaceDir } = resolveSkillsWorkspaceForCommand(command.parent, opts);
			const record = await quarantineSkillProposal({
				workspaceDir,
				proposalId,
				reason: opts.reason
			});
			if (opts.json) {
				defaultRuntime.writeJson(record);
				return;
			}
			defaultRuntime.writeStdout(`Quarantined ${record.id}\n`);
		} catch (err) {
			defaultRuntime.error(String(err));
			defaultRuntime.exit(1);
		}
	});
	skills.command("list").description("List all available skills").option("--json", "Output as JSON", false).option("--eligible", "Show only eligible (ready to use) skills", false).option("-v, --verbose", "Show more details including missing requirements", false).option("--agent <id>", "Target agent workspace (defaults to cwd-inferred, then default agent)").action(async (opts, command) => {
		await runSkillsAction((report) => formatSkillsList(report, opts), { agentId: resolveAgentOption(command, opts) });
	});
	skills.command("info").description("Show detailed information about a skill").argument("<name>", "Skill name").option("--json", "Output as JSON", false).option("--agent <id>", "Target agent workspace (defaults to cwd-inferred, then default agent)").action(async (name, opts, command) => {
		await runSkillsAction((report) => formatSkillInfo(report, name, opts), { agentId: resolveAgentOption(command, opts) });
	});
	skills.command("check").description("Check which skills are ready, visible, or missing requirements").option("--agent <id>", "Target agent workspace (defaults to cwd-inferred, then default agent)").option("--json", "Output as JSON", false).action(async (opts, command) => {
		await runSkillsAction((report) => formatSkillsCheck(report, opts), { agentId: resolveAgentOption(command, opts) });
	});
	skills.action(async (opts, command) => {
		await runSkillsAction((report) => formatSkillsList(report, {}), { agentId: resolveAgentOption(command, opts) });
	});
}
//#endregion
export { formatSkillInfo, formatSkillsCheck, formatSkillsList, registerSkillsCli };
