import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import "./src-BntaCZM-.js";
import { t as expectDefined } from "./expect-CyE8FADM.js";
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { i as readFileDescriptorBounded } from "./boundary-file-read-h_n3tTfV.js";
import { r as formatErrorMessage } from "./errors-Z0lU1pH3.js";
import { c as rethrowExpectedCliError } from "./failure-output-D8N-Meq1.js";
import { t as sanitizeForLog } from "./ansi-DjDeieuH.js";
import { r as defaultRuntime } from "./runtime-LRpY2Icg.js";
import { a as readBestEffortConfig } from "./io.runtime-CNGn9TXj.js";
import { g as resolveConfiguredAgentId } from "./agent-scope-config-adSj-fCz.js";
import { n as APPROVALS_SCOPE, t as ADMIN_SCOPE } from "./operator-scopes-Dw7Gu2cA.js";
import "./config-D_ojUgx2.js";
import { t as formatDocsLink } from "./links-ClIwBcy4.js";
import { n as isRich, r as theme } from "./theme-vjDs9tao.js";
import { n as callGatewayFromCli } from "./gateway-rpc-CKNX1w0e.js";
import "./method-scopes-CuEAYEiZ.js";
import { n as isWellFormedApprovalId } from "./approval-id-BTRnO3t1.js";
import { a as mergeExecApprovalsSocketDefaults } from "./exec-approvals-config-QmzK42j1.js";
import { n as redactExecApprovals, t as normalizeExecApprovals } from "./exec-approvals-BQ_sT-kU.js";
import { c as readExecApprovalsSnapshot, p as updateExecApprovals } from "./exec-approvals-generated-migration-CYtB4mAB.js";
import { t as readByteStreamWithLimit } from "./read-byte-stream-with-limit-CNew-qG0.js";
import { n as formatTimeAgo } from "./format-relative-DerIyym2.js";
import { r as renderTerminalSafeTable, t as getTerminalTableWidth } from "./table-BMW8uaYV.js";
import { t as applyParentDefaultHelpAction } from "./parent-default-help-DQUF3qKA.js";
import { a as nodesCallOpts, u as resolveCliNodeId } from "./rpc-DPZO5ieL.js";
import { n as collectExecPolicyScopeSnapshots, t as SESSION_EXEC_OVERRIDES_NOTE } from "./exec-approvals-effective-dL93mnqB.js";
import fs from "node:fs/promises";
import JSON5 from "json5";
//#region src/cli/exec-approvals-cli.ts
const APPROVALS_GET_DEFAULT_TIMEOUT_MS = 6e4;
const EXEC_APPROVALS_STDIN_MAX_BYTES = 1024 * 1024;
const APPROVAL_DECISIONS = [
	"allow-once",
	"allow-always",
	"deny"
];
const PENDING_APPROVAL_SUMMARY_MAX_LENGTH = 96;
const APPROVAL_ID_TOKEN_PREFIX = "id64_";
const APPROVAL_TERMINAL_UNSAFE_CHAR = /^[\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}\u00A0\u1680\u2000-\u200A\u202F\u205F\u3000\u115F\u1160\u3164\uFFA0]$/u;
async function readStdin(stream = process.stdin, maxBytes = EXEC_APPROVALS_STDIN_MAX_BYTES) {
	return (await readByteStreamWithLimit(stream, {
		maxBytes,
		onOverflow: ({ maxBytes: limit }) => /* @__PURE__ */ new Error(`Exec approvals stdin exceeds ${limit} bytes.`)
	})).toString("utf8");
}
async function readApprovalsFile(filePath) {
	const handle = await fs.open(filePath, "r");
	try {
		return (await readFileDescriptorBounded(handle.fd, EXEC_APPROVALS_STDIN_MAX_BYTES)).toString("utf8");
	} finally {
		await handle.close();
	}
}
async function resolveTargetNodeId(opts) {
	if (opts.gateway) return null;
	const raw = normalizeOptionalString(opts.node) ?? "";
	if (!raw) return null;
	return await resolveCliNodeId(opts, raw);
}
async function loadSnapshot(opts, nodeId) {
	return await callGatewayFromCli(nodeId ? "exec.approvals.node.get" : "exec.approvals.get", opts, nodeId ? { nodeId } : {});
}
function loadSnapshotLocal() {
	const snapshot = readExecApprovalsSnapshot();
	return {
		path: snapshot.path,
		exists: snapshot.exists,
		hash: snapshot.hash,
		file: snapshot.file
	};
}
function isFileApprovalsSnapshot(snapshot) {
	return "file" in snapshot;
}
function isNativeApprovalsSnapshot(snapshot) {
	return "enabled" in snapshot;
}
function parseNativeAction(value, label) {
	if (value === "allow" || value === "deny" || value === "prompt") return value;
	return exitWithError(`${label} must be allow, deny, or prompt.`);
}
function normalizeNativePolicyInput(value) {
	if (!isRecord(value)) exitWithError("Host-native exec approvals JSON must be an object.");
	const unknownKeys = Object.keys(value).filter((key) => key !== "defaultAction" && key !== "rules");
	if (unknownKeys.length > 0) exitWithError(`Unknown host-native exec approvals field: ${unknownKeys[0]}.`);
	const defaultAction = value.defaultAction === void 0 ? void 0 : parseNativeAction(value.defaultAction, "defaultAction");
	if (!Array.isArray(value.rules)) exitWithError("Host-native exec approvals rules must be an array.");
	const rules = value.rules?.map((entry, index) => {
		if (!isRecord(entry)) exitWithError(`Host-native exec approval rule ${index + 1} must be an object.`);
		const unknownRuleKeys = Object.keys(entry).filter((key) => key !== "pattern" && key !== "action" && key !== "shells" && key !== "description" && key !== "enabled");
		if (unknownRuleKeys.length > 0) exitWithError(`Unknown host-native exec approval rule ${index + 1} field: ${unknownRuleKeys[0]}.`);
		const pattern = normalizeOptionalString(entry.pattern);
		if (!pattern) exitWithError(`Host-native exec approval rule ${index + 1} requires pattern.`);
		const action = parseNativeAction(entry.action, `Host-native exec approval rule ${index + 1} action`);
		let shells;
		if (entry.shells !== void 0) {
			if (!Array.isArray(entry.shells)) exitWithError(`Host-native exec approval rule ${index + 1} shells must be an array.`);
			shells = entry.shells.map((shell) => {
				const normalized = typeof shell === "string" ? shell.trim() : "";
				if (!normalized) exitWithError(`Host-native exec approval rule ${index + 1} shells must be non-empty strings.`);
				return normalized;
			});
		}
		if (entry.description !== void 0 && typeof entry.description !== "string") exitWithError(`Host-native exec approval rule ${index + 1} description must be a string.`);
		if (entry.enabled !== void 0 && typeof entry.enabled !== "boolean") exitWithError(`Host-native exec approval rule ${index + 1} enabled must be a boolean.`);
		return {
			pattern,
			action,
			...shells ? { shells } : {},
			...entry.description !== void 0 ? { description: entry.description } : {},
			...entry.enabled !== void 0 ? { enabled: entry.enabled } : {}
		};
	});
	return {
		...defaultAction ? { defaultAction } : {},
		rules
	};
}
async function saveSnapshotLocal(file, baseHash) {
	const snapshot = await updateExecApprovals({
		baseHash,
		update: (current) => mergeExecApprovalsSocketDefaults({
			normalized: normalizeExecApprovals(file),
			current
		})
	});
	if (!snapshot) throw new Error("Exec approvals changed; reload and retry.");
	return snapshot;
}
async function loadSnapshotTarget(opts) {
	if (!opts.gateway && !opts.node) return {
		snapshot: loadSnapshotLocal(),
		nodeId: null,
		source: "local"
	};
	const nodeId = await resolveTargetNodeId(opts);
	return {
		snapshot: await loadSnapshot(opts, nodeId),
		nodeId,
		source: nodeId ? "node" : "gateway"
	};
}
function exitWithError(message) {
	throw new Error(message);
}
function requireTrimmedNonEmpty(value, message) {
	const trimmed = value.trim();
	if (!trimmed) exitWithError(message);
	return trimmed;
}
async function loadWritableSnapshotTarget(opts) {
	const { snapshot, nodeId, source } = await loadSnapshotTarget(opts);
	const targetLabel = source === "local" ? "local" : nodeId ? `node:${nodeId}` : "gateway";
	if (isNativeApprovalsSnapshot(snapshot) && !snapshot.enabled) exitWithError("Host-native exec approvals are disabled on this node and cannot be configured remotely.");
	const baseHash = "hash" in snapshot ? snapshot.hash : void 0;
	if (!baseHash) exitWithError("Exec approvals hash missing; reload and retry.");
	return {
		snapshot,
		nodeId,
		source,
		targetLabel,
		baseHash,
		kind: isNativeApprovalsSnapshot(snapshot) ? "native" : "file"
	};
}
async function saveSnapshotTargeted(params) {
	let next;
	if ("native" in params) {
		if (params.source !== "node" || !params.nodeId) exitWithError("Host-native exec approvals can only target a node.");
		await callGatewayFromCli("exec.approvals.node.set", params.opts, {
			nodeId: params.nodeId,
			native: params.native,
			baseHash: params.baseHash
		});
		next = await loadSnapshot(params.opts, params.nodeId);
	} else if (params.source === "local") {
		defaultRuntime.log(theme.muted("Writing local approvals."));
		next = await saveSnapshotLocal(params.file, params.baseHash);
	} else next = await saveSnapshot(params.opts, params.nodeId, params.file, params.baseHash);
	if (params.opts.json) {
		defaultRuntime.writeJson(isFileApprovalsSnapshot(next) ? redactExecApprovals(next) : next, 0);
		return;
	}
	defaultRuntime.log(theme.muted(`Target: ${params.targetLabel}`));
	renderApprovalsSnapshot(next, params.targetLabel);
}
function formatCliError(err) {
	const msg = formatErrorMessage(err);
	const safe = sanitizeForLog(expectDefined(msg.includes("\n") ? msg.split("\n")[0] : msg, "exec approvals cli first line"));
	return safe.length > 300 ? `${truncateUtf16Safe(safe, 300)}...` : safe;
}
function failApprovalsCommand(err, opts) {
	rethrowExpectedCliError(err);
	const message = formatCliError(err);
	if (opts.json) throw new Error(message);
	defaultRuntime.error(message);
	defaultRuntime.exit(1);
}
function isApprovalDecision(value) {
	return APPROVAL_DECISIONS.includes(value);
}
function shortenPendingApprovalSummary(value) {
	if (value.length <= PENDING_APPROVAL_SUMMARY_MAX_LENGTH) return value;
	return `${truncateUtf16Safe(value, PENDING_APPROVAL_SUMMARY_MAX_LENGTH - 3)}...`;
}
function escapeApprovalTextForTerminal(value) {
	let escaped = "";
	for (const char of value) {
		if (char === "\\") {
			escaped += "\\\\";
			continue;
		}
		if (APPROVAL_TERMINAL_UNSAFE_CHAR.test(char)) {
			escaped += `\\u{${char.codePointAt(0)?.toString(16).toUpperCase() ?? "FFFD"}}`;
			continue;
		}
		escaped += char;
	}
	return escaped;
}
const APPROVAL_ID_TERMINAL_SAFE_RE = /^[A-Za-z0-9._:][A-Za-z0-9._:-]{0,127}$/;
function formatApprovalIdForTerminal(value) {
	if (APPROVAL_ID_TERMINAL_SAFE_RE.test(value)) return value;
	return `${APPROVAL_ID_TOKEN_PREFIX}${Buffer.from(value, "utf16le").toString("base64url")}`;
}
function decodeDisplayedApprovalId(value) {
	if (!value.startsWith(APPROVAL_ID_TOKEN_PREFIX)) return null;
	const encoded = value.slice(5);
	if (!encoded || !/^[a-zA-Z0-9_-]+$/.test(encoded)) return null;
	const decoded = Buffer.from(encoded, "base64url").toString("utf16le");
	return Buffer.from(decoded, "utf16le").toString("base64url") === encoded ? decoded : null;
}
function readPendingApprovalEntry(value, kind) {
	if (!isRecord(value) || !isRecord(value.request)) return null;
	const id = typeof value.id === "string" && isWellFormedApprovalId(value.id) ? value.id : null;
	const createdAtMs = value.createdAtMs;
	const expiresAtMs = value.expiresAtMs;
	if (!id || typeof createdAtMs !== "number" || !Number.isFinite(createdAtMs) || typeof expiresAtMs !== "number" || !Number.isFinite(expiresAtMs)) return null;
	const request = value.request;
	const agentId = normalizeOptionalString(request.agentId) ?? null;
	const sessionKey = normalizeOptionalString(request.sessionKey) ?? null;
	const command = typeof request.command === "string" && request.command ? request.command : null;
	const title = typeof request.title === "string" && request.title ? request.title : null;
	const description = typeof request.description === "string" && request.description ? request.description : null;
	const prose = title && description ? `${title}: ${description}` : title ?? description;
	return {
		id,
		kind,
		agentId,
		sessionKey,
		createdAtMs,
		expiresAtMs,
		summary: (kind === "exec" ? command : kind === "plugin" && command ? `${prose ? `${prose} — ` : ""}Command: ${command}` : prose) ?? "(summary unavailable)"
	};
}
function readPendingApprovalList(value, kind) {
	if (!Array.isArray(value)) throw new Error(`Invalid ${kind} approval list response.`);
	return value.flatMap((entry) => {
		const parsed = readPendingApprovalEntry(entry, kind);
		return parsed ? [parsed] : [];
	});
}
async function loadPendingApprovals(opts) {
	const listCall = (method) => callGatewayFromCli(method, opts, {}, { scopes: [ADMIN_SCOPE] });
	const [exec, plugin, systemAgent] = await Promise.all([
		listCall("exec.approval.list"),
		listCall("plugin.approval.list"),
		listCall("openclaw.approval.list")
	]);
	return [
		...readPendingApprovalList(exec, "exec"),
		...readPendingApprovalList(plugin, "plugin"),
		...readPendingApprovalList(systemAgent, "system-agent")
	].toSorted((a, b) => b.createdAtMs - a.createdAtMs);
}
function formatPendingAgentSession(entry) {
	const parts = [entry.agentId, entry.sessionKey].filter((value) => Boolean(value));
	return parts.length > 0 ? escapeApprovalTextForTerminal(parts.join(" / ")) : "-";
}
function describeGrantState(grant, nowMs) {
	if (grant.revokedAtMs !== null) return `revoked${grant.revokedBy ? ` by ${escapeApprovalTextForTerminal(grant.revokedBy)}` : ""}`;
	if (grant.expiresAtMs !== null && grant.expiresAtMs <= nowMs) return "expired";
	if (grant.expiresAtMs !== null) return `expires in ${Math.max(1, Math.ceil((grant.expiresAtMs - nowMs) / 864e5))}d`;
	return "until revoked";
}
function renderStandingGrants(grants) {
	if (grants.length === 0) {
		defaultRuntime.log(theme.muted("No standing grants."));
		return;
	}
	const now = Date.now();
	defaultRuntime.log(`${theme.heading("Standing grants")} ${theme.muted(`(${grants.length})`)}`);
	defaultRuntime.log(renderTerminalSafeTable({
		width: getTerminalTableWidth(),
		columns: [
			{
				key: "ID",
				header: "ID",
				minWidth: 12,
				flex: true
			},
			{
				key: "Automation",
				header: "Automation",
				minWidth: 12,
				flex: true
			},
			{
				key: "Command",
				header: "Command",
				minWidth: 16,
				flex: true
			},
			{
				key: "Uses",
				header: "Uses",
				minWidth: 4
			},
			{
				key: "State",
				header: "State",
				minWidth: 12
			}
		],
		rows: grants.map((grant) => ({
			ID: grant.grantId,
			Automation: escapeApprovalTextForTerminal(grant.cronJobName ?? grant.cronJobId),
			Command: escapeApprovalTextForTerminal(grant.command),
			Uses: String(grant.useCount),
			State: describeGrantState(grant, now)
		}))
	}));
	defaultRuntime.log(theme.muted("Revoke with: openclaw approvals grants revoke <grant-id>"));
}
function renderPendingApprovals(entries) {
	if (entries.length === 0) {
		defaultRuntime.log(theme.muted("No pending approvals."));
		return;
	}
	const now = Date.now();
	defaultRuntime.log(`${theme.heading("Pending approvals")} ${theme.muted(`(${entries.length})`)}`);
	defaultRuntime.log(renderTerminalSafeTable({
		width: getTerminalTableWidth(),
		columns: [
			{
				key: "ID",
				header: "ID",
				minWidth: 16,
				flex: true
			},
			{
				key: "Kind",
				header: "Kind",
				minWidth: 12
			},
			{
				key: "AgentSession",
				header: "Agent / Session",
				minWidth: 16,
				flex: true
			},
			{
				key: "Requested",
				header: "Requested",
				minWidth: 12
			},
			{
				key: "Expires",
				header: "Expires In",
				minWidth: 10
			},
			{
				key: "Summary",
				header: "Command / Summary",
				minWidth: 20,
				flex: true
			}
		],
		rows: entries.map((entry) => {
			const summary = escapeApprovalTextForTerminal(entry.summary);
			return {
				ID: formatApprovalIdForTerminal(entry.id),
				Kind: entry.kind,
				AgentSession: formatPendingAgentSession(entry),
				Requested: formatTimeAgo(Math.max(0, now - entry.createdAtMs)),
				Expires: formatTimeAgo(Math.max(0, entry.expiresAtMs - now), { suffix: false }),
				Summary: shortenPendingApprovalSummary(summary)
			};
		})
	}).trimEnd());
	defaultRuntime.log(theme.heading("Full request text"));
	for (const entry of entries) defaultRuntime.log(`${formatApprovalIdForTerminal(entry.id)}: ${escapeApprovalTextForTerminal(entry.summary)}`);
}
function approvalRecordedDecision(approval) {
	return "decision" in approval && isApprovalDecision(approval.decision) ? approval.decision : null;
}
function formatResolver(approval) {
	const resolver = approval.resolver;
	if (!resolver) return "unknown resolver";
	return resolver.id ? `${resolver.kind}:${escapeApprovalTextForTerminal(resolver.id)}` : resolver.kind;
}
function describeTerminalApprovalFailure(approval) {
	const id = formatApprovalIdForTerminal(approval.id);
	if (approval.status === "expired") return `Approval ${id} expired.`;
	if (approval.status === "cancelled") return `Approval ${id} was cancelled (${approval.reason}).`;
	return `Approval ${id} did not settle to a recorded decision.`;
}
async function resolvePendingApproval(idInput, decisionInput, opts) {
	if (idInput.length === 0) exitWithError("Approval id required.");
	const rawId = idInput;
	const decision = requireTrimmedNonEmpty(decisionInput, "Decision required.");
	if (!isApprovalDecision(decision)) exitWithError(`Decision must be one of: ${APPROVAL_DECISIONS.join(", ")}.`);
	const reason = opts.reason === void 0 ? null : normalizeOptionalString(opts.reason);
	if (opts.reason !== void 0 && !reason) exitWithError("Reason must not be empty.");
	const approvalCallOptions = { scopes: [ADMIN_SCOPE, APPROVALS_SCOPE] };
	const lookupOne = async (id, tolerateNotFound = false) => {
		try {
			return await callGatewayFromCli("approval.get", opts, { id }, approvalCallOptions);
		} catch (error) {
			if (tolerateNotFound && formatErrorMessage(error).toLowerCase().includes("approval not found")) return null;
			throw error;
		}
	};
	const decodedId = decodeDisplayedApprovalId(rawId);
	let id = rawId;
	let lookup;
	if (decodedId && decodedId !== rawId) {
		const [rawLookup, decodedLookup] = await Promise.all([lookupOne(rawId, true), lookupOne(decodedId, true)]);
		if (rawLookup && decodedLookup) exitWithError("Approval id is ambiguous: it matches both a raw id and a displayed id token. This CLI cannot resolve it safely.");
		if (rawLookup) lookup = rawLookup;
		else if (decodedLookup) {
			id = decodedId;
			lookup = decodedLookup;
		} else exitWithError("Approval not found.");
	} else lookup = expectDefined(await lookupOne(rawId), "approval lookup result");
	const displayId = formatApprovalIdForTerminal(id);
	const current = lookup.approval;
	if (current.status === "pending") {
		const allowedDecisions = current.presentation.allowedDecisions;
		if (!allowedDecisions.includes(decision)) exitWithError(`Decision ${decision} is not allowed for ${current.presentation.kind} approvals; allowed decisions: ${allowedDecisions.join(", ")}.`);
	}
	const expiresInDaysRaw = opts.expiresInDays === void 0 ? void 0 : Number.parseInt(opts.expiresInDays, 10);
	if (expiresInDaysRaw !== void 0 && (!Number.isInteger(expiresInDaysRaw) || expiresInDaysRaw < 1 || expiresInDaysRaw > 3650)) exitWithError("--expires-in-days must be a whole number of days between 1 and 3650.");
	if (expiresInDaysRaw !== void 0 && decision !== "allow-always") exitWithError("--expires-in-days only applies to allow-always.");
	const result = await callGatewayFromCli("approval.resolve", opts, {
		id,
		kind: current.presentation.kind,
		decision,
		...expiresInDaysRaw !== void 0 ? { grantExpiresInDays: expiresInDaysRaw } : {}
	}, approvalCallOptions);
	const recordedDecision = approvalRecordedDecision(result.approval);
	if (!recordedDecision) exitWithError(describeTerminalApprovalFailure(result.approval));
	if (recordedDecision !== decision) exitWithError(`Approval ${displayId} was already resolved with ${recordedDecision} by ${formatResolver(result.approval)}.`);
	if (opts.json) {
		defaultRuntime.writeJson({
			...result,
			alreadyResolved: !result.applied,
			...reason ? { cliReason: reason } : {}
		}, 0);
		return;
	}
	const settled = result.applied ? `resolved ${recordedDecision}` : `already resolved (same decision: ${recordedDecision})`;
	const reasonSuffix = reason ? `; CLI reason: ${shortenPendingApprovalSummary(escapeApprovalTextForTerminal(reason))}` : "";
	defaultRuntime.log(`Approval ${displayId} ${settled} by ${formatResolver(result.approval)}${reasonSuffix}.`);
}
async function loadConfigForApprovalsTarget(params) {
	try {
		if (params.source === "local") return {
			config: await readBestEffortConfig(),
			timedOut: false
		};
		const snapshot = await callGatewayFromCli("config.get", params.opts, {});
		return {
			config: snapshot.config && typeof snapshot.config === "object" ? snapshot.config : null,
			timedOut: false
		};
	} catch (err) {
		return {
			config: null,
			timedOut: /^gateway timeout after \d+ms\b/i.test(formatCliError(err))
		};
	}
}
function buildEffectivePolicyReport(params) {
	const cfg = params.configLoad.config;
	const timeoutNote = params.configLoad.timedOut ? "Config fetch timed out. Re-run with a higher --timeout to inspect Effective Policy." : null;
	if (!params.approvals) return {
		scopes: [],
		note: params.nativePolicy ? "This node enforces a host-native exec policy; OpenClaw approvals-file policy math does not apply." : "Host approvals policy unavailable."
	};
	if (params.source === "node") {
		if (!cfg) return {
			scopes: [],
			note: timeoutNote ?? "Gateway config unavailable. Node output above shows host approvals state only, and final runtime policy still intersects with gateway tools.exec."
		};
		if (!params.resolvedDefaults) return {
			scopes: [],
			note: "This node does not expose a complete resolved host policy, so Effective Policy is unavailable."
		};
		return {
			scopes: collectExecPolicyScopeSnapshots({
				cfg,
				approvals: params.approvals,
				hostPath: params.hostPath,
				hostDefaults: params.resolvedDefaults,
				hostDefaultSource: "node-reported resolved defaults"
			}),
			note: "Effective exec policy is the node host approvals policy intersected with gateway tools.exec policy. " + SESSION_EXEC_OVERRIDES_NOTE
		};
	}
	if (!cfg) return {
		scopes: [],
		note: timeoutNote ?? "Config unavailable."
	};
	return {
		scopes: collectExecPolicyScopeSnapshots({
			cfg,
			approvals: params.approvals,
			hostPath: params.hostPath
		}),
		note: "Effective exec policy is the host approvals policy intersected with requested tools.exec policy. " + SESSION_EXEC_OVERRIDES_NOTE
	};
}
function renderEffectivePolicy(params) {
	const rich = isRich();
	const heading = (text) => rich ? theme.heading(text) : text;
	const muted = (text) => rich ? theme.muted(text) : text;
	if (params.report.scopes.length === 0 && !params.report.note) return;
	defaultRuntime.log("");
	defaultRuntime.log(heading("Effective Policy"));
	if (params.report.scopes.length === 0) {
		defaultRuntime.log(muted(params.report.note ?? "No effective policy details available."));
		return;
	}
	const rows = params.report.scopes.map((summary) => ({
		Scope: summary.scopeLabel,
		Requested: `security=${summary.security.requested} (${summary.security.requestedSource})\nask=${summary.ask.requested} (${summary.ask.requestedSource})`,
		Host: `security=${summary.security.host} (${summary.security.hostSource})\nask=${summary.ask.host} (${summary.ask.hostSource})\naskFallback=${summary.askFallback.effective} (${summary.askFallback.source})`,
		Effective: `security=${summary.security.effective}\nask=${summary.ask.effective}`,
		Notes: `${summary.security.note}; ${summary.ask.note}`
	}));
	defaultRuntime.log(renderTerminalSafeTable({
		width: getTerminalTableWidth(),
		columns: [
			{
				key: "Scope",
				header: "Scope",
				minWidth: 12
			},
			{
				key: "Requested",
				header: "Requested",
				minWidth: 24,
				flex: true
			},
			{
				key: "Host",
				header: "Host",
				minWidth: 24,
				flex: true
			},
			{
				key: "Effective",
				header: "Effective",
				minWidth: 16
			},
			{
				key: "Notes",
				header: "Notes",
				minWidth: 20,
				flex: true
			}
		],
		rows
	}).trimEnd());
	defaultRuntime.log("");
	defaultRuntime.log(muted(`Precedence: ${params.report.note}`));
}
function renderApprovalsSnapshot(snapshot, targetLabel) {
	if (isNativeApprovalsSnapshot(snapshot)) {
		renderNativeApprovalsSnapshot(snapshot, targetLabel);
		return;
	}
	const rich = isRich();
	const heading = (text) => rich ? theme.heading(text) : text;
	const muted = (text) => rich ? theme.muted(text) : text;
	const tableWidth = getTerminalTableWidth();
	const file = snapshot.file ?? { version: 1 };
	const defaults = file.defaults ?? {};
	const defaultsParts = [
		defaults.security ? `security=${defaults.security}` : null,
		defaults.ask ? `ask=${defaults.ask}` : null,
		defaults.askFallback ? `askFallback=${defaults.askFallback}` : null,
		typeof defaults.autoAllowSkills === "boolean" ? `autoAllowSkills=${defaults.autoAllowSkills ? "on" : "off"}` : null
	].filter((part) => part != null);
	const agents = file.agents ?? {};
	const allowlistRows = [];
	const now = Date.now();
	for (const [agentId, agent] of Object.entries(agents)) {
		const allowlist = Array.isArray(agent.allowlist) ? agent.allowlist : [];
		for (const entry of allowlist) {
			const pattern = normalizeOptionalString(entry?.pattern) ?? "";
			if (!pattern) continue;
			const lastUsedAt = typeof entry.lastUsedAt === "number" ? entry.lastUsedAt : null;
			allowlistRows.push({
				Target: targetLabel,
				Agent: agentId,
				Pattern: pattern,
				LastUsed: lastUsedAt ? formatTimeAgo(Math.max(0, now - lastUsedAt)) : muted("unknown")
			});
		}
	}
	const summaryRows = [
		{
			Field: "Target",
			Value: targetLabel
		},
		{
			Field: "Path",
			Value: snapshot.path
		},
		{
			Field: "State",
			Value: snapshot.exists ? "stored" : "defaults (no stored overrides)"
		},
		{
			Field: "Hash",
			Value: snapshot.hash
		},
		{
			Field: "Version",
			Value: String(file.version ?? 1)
		},
		{
			Field: "Socket",
			Value: file.socket?.path ?? "default"
		},
		{
			Field: "Defaults",
			Value: defaultsParts.length > 0 ? defaultsParts.join(", ") : "none"
		},
		{
			Field: "Agents",
			Value: String(Object.keys(agents).length)
		},
		{
			Field: "Allowlist",
			Value: String(allowlistRows.length)
		}
	];
	defaultRuntime.log(heading("Approvals"));
	defaultRuntime.log(renderTerminalSafeTable({
		width: tableWidth,
		columns: [{
			key: "Field",
			header: "Field",
			minWidth: 8
		}, {
			key: "Value",
			header: "Value",
			minWidth: 24,
			flex: true
		}],
		rows: summaryRows
	}).trimEnd());
	if (allowlistRows.length === 0) {
		defaultRuntime.log("");
		defaultRuntime.log(muted("No allowlist entries."));
		return;
	}
	defaultRuntime.log("");
	defaultRuntime.log(heading("Allowlist"));
	defaultRuntime.log(renderTerminalSafeTable({
		width: tableWidth,
		columns: [
			{
				key: "Target",
				header: "Target",
				minWidth: 10
			},
			{
				key: "Agent",
				header: "Agent",
				minWidth: 8
			},
			{
				key: "Pattern",
				header: "Pattern",
				minWidth: 20,
				flex: true
			},
			{
				key: "LastUsed",
				header: "Last Used",
				minWidth: 10
			}
		],
		rows: allowlistRows
	}).trimEnd());
}
function renderNativeApprovalsSnapshot(snapshot, targetLabel) {
	const rich = isRich();
	const heading = (text) => rich ? theme.heading(text) : text;
	const muted = (text) => rich ? theme.muted(text) : text;
	const rules = snapshot.enabled ? snapshot.rules : [];
	const summaryRows = [
		{
			Field: "Target",
			Value: targetLabel
		},
		{
			Field: "Kind",
			Value: "host-native"
		},
		{
			Field: "Enabled",
			Value: snapshot.enabled ? "yes" : "no"
		},
		{
			Field: "Hash",
			Value: snapshot.enabled ? snapshot.hash : "unavailable"
		},
		{
			Field: "Default",
			Value: snapshot.enabled ? snapshot.defaultAction : snapshot.message ?? "unavailable"
		},
		{
			Field: "Rules",
			Value: String(rules.length)
		}
	];
	defaultRuntime.log(heading("Approvals"));
	defaultRuntime.log(renderTerminalSafeTable({
		width: getTerminalTableWidth(),
		columns: [{
			key: "Field",
			header: "Field",
			minWidth: 8
		}, {
			key: "Value",
			header: "Value",
			minWidth: 24,
			flex: true
		}],
		rows: summaryRows
	}).trimEnd());
	if (rules.length === 0) {
		defaultRuntime.log("");
		defaultRuntime.log(muted("No host-native rules."));
		return;
	}
	defaultRuntime.log("");
	defaultRuntime.log(heading("Rules"));
	defaultRuntime.log(renderTerminalSafeTable({
		width: getTerminalTableWidth(),
		columns: [
			{
				key: "Pattern",
				header: "Pattern",
				minWidth: 20,
				flex: true
			},
			{
				key: "Action",
				header: "Action",
				minWidth: 8
			},
			{
				key: "Shells",
				header: "Shells",
				minWidth: 10,
				flex: true
			},
			{
				key: "Enabled",
				header: "Enabled",
				minWidth: 7
			}
		],
		rows: rules.map((rule) => ({
			Pattern: rule.pattern,
			Action: rule.action,
			Shells: rule.shells?.join(", ") || "all",
			Enabled: rule.enabled === false ? "no" : "yes"
		}))
	}).trimEnd());
}
async function saveSnapshot(opts, nodeId, file, baseHash) {
	return await callGatewayFromCli(nodeId ? "exec.approvals.node.set" : "exec.approvals.set", opts, nodeId ? {
		nodeId,
		file,
		baseHash
	} : {
		file,
		baseHash
	});
}
function resolveAgentKey(value) {
	return value == null ? "*" : requireTrimmedNonEmpty(value, "--agent must not be blank");
}
function normalizeAllowlistEntry(entry) {
	const pattern = normalizeOptionalString(entry?.pattern) ?? "";
	return pattern ? pattern : null;
}
function ensureAgent(file, agentKey) {
	const agents = file.agents ?? {};
	const entry = agents[agentKey] ?? {};
	file.agents = agents;
	return entry;
}
function isEmptyAgent(agent) {
	const allowlist = Array.isArray(agent.allowlist) ? agent.allowlist : [];
	return !agent.security && !agent.ask && !agent.askFallback && agent.autoAllowSkills === void 0 && allowlist.length === 0;
}
async function loadWritableAllowlistAgent(opts) {
	const agentKey = resolveAgentKey(opts.agent);
	if (agentKey !== "*") {
		const { config } = await loadConfigForApprovalsTarget({
			opts,
			source: !opts.gateway && !opts.node ? "local" : opts.gateway ? "gateway" : "node"
		});
		if (!config) exitWithError("Config unavailable; cannot validate --agent.");
		resolveConfiguredAgentId(config, agentKey);
	}
	const { snapshot, nodeId, source, targetLabel, baseHash, kind } = await loadWritableSnapshotTarget(opts);
	if (kind === "native" || !isFileApprovalsSnapshot(snapshot)) exitWithError("Host-native node approvals do not support allowlist mutations; use approvals set --node with host-native JSON.");
	const file = snapshot.file;
	file.version = 1;
	const agent = ensureAgent(file, agentKey);
	return {
		nodeId,
		source,
		targetLabel,
		baseHash,
		file,
		agentKey,
		agent,
		allowlistEntries: Array.isArray(agent.allowlist) ? agent.allowlist : []
	};
}
async function runAllowlistMutation(pattern, opts, mutate) {
	try {
		const trimmedPattern = requireTrimmedNonEmpty(pattern, "Pattern required.");
		const context = await loadWritableAllowlistAgent(opts);
		if (!await mutate({
			...context,
			trimmedPattern
		})) return;
		await saveSnapshotTargeted({
			opts,
			source: context.source,
			nodeId: context.nodeId,
			file: context.file,
			baseHash: context.baseHash,
			targetLabel: context.targetLabel
		});
	} catch (err) {
		failApprovalsCommand(err, opts);
	}
}
function registerAllowlistMutationCommand(params) {
	const command = params.allowlist.command(`${params.name} <pattern>`).description(params.description).option("--node <node>", "Target node id/name/IP").option("--gateway", "Force gateway approvals", false).option("--agent <id>", "Agent id (defaults to \"*\")").action(async (pattern, opts) => {
		await runAllowlistMutation(pattern, opts, params.mutate);
	});
	nodesCallOpts(command);
	return command;
}
function registerExecApprovalsCli(program) {
	const formatExample = (cmd, desc) => `  ${theme.command(cmd)}\n    ${theme.muted(desc)}`;
	const approvals = program.command("approvals").alias("exec-approvals").description("Manage approval policy and pending requests").addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/approvals", "docs.openclaw.ai/cli/approvals")}\n`);
	nodesCallOpts(approvals.command("pending").description("List pending exec, plugin, and system-agent approvals").action(async (opts) => {
		try {
			const entries = await loadPendingApprovals(opts);
			if (opts.json) {
				defaultRuntime.writeJson({ approvals: entries }, 0);
				return;
			}
			renderPendingApprovals(entries);
		} catch (err) {
			failApprovalsCommand(err, opts);
		}
	}));
	nodesCallOpts(approvals.command("resolve <id> <decision>").description("Resolve a pending approval").option("--reason <text>", "Add a local note to the CLI confirmation").option("--expires-in-days <days>", "Allow-always on an automation approval: freeze this grant lifetime instead of the configured default").action(async (id, decision, opts) => {
		try {
			await resolvePendingApproval(id, decision, opts);
		} catch (err) {
			failApprovalsCommand(err, opts);
		}
	}));
	const grants = approvals.command("grants").description("Standing grants minted by allow-always on automation approvals");
	nodesCallOpts(grants.command("list").description("List standing grants, newest first").option("--limit <n>", "Maximum rows to return (default 200)").action(async (opts) => {
		try {
			const limitRaw = opts.limit === void 0 ? void 0 : Number.parseInt(opts.limit, 10);
			const limit = limitRaw !== void 0 && Number.isInteger(limitRaw) && limitRaw >= 1 ? limitRaw : void 0;
			const result = await callGatewayFromCli("exec.approval.grants.list", opts, limit ? { limit } : {});
			if (opts.json) {
				defaultRuntime.writeJson(result, 0);
				return;
			}
			renderStandingGrants(result.grants);
		} catch (err) {
			failApprovalsCommand(err, opts);
		}
	}));
	nodesCallOpts(grants.command("revoke <grantId>").description("Revoke a standing grant; the next occurrence prompts again").action(async (grantId, opts) => {
		try {
			const result = await callGatewayFromCli("exec.approval.grants.revoke", opts, { grantId });
			if (opts.json) {
				defaultRuntime.writeJson(result, 0);
				return;
			}
			if (result.outcome === "revoked") defaultRuntime.log(`Grant ${grantId} revoked. The next occurrence prompts again.`);
			else if (result.outcome === "already-revoked") defaultRuntime.log(`Grant ${grantId} was already revoked.`);
			else exitWithError(`Grant ${grantId} not found.`);
		} catch (err) {
			failApprovalsCommand(err, opts);
		}
	}));
	nodesCallOpts(approvals.command("get").description("Fetch exec approvals snapshot").option("--node <node>", "Target node id/name/IP").option("--gateway", "Force gateway approvals", false).action(async (opts) => {
		try {
			const { snapshot, nodeId, source } = await loadSnapshotTarget(opts);
			const nativePolicy = isNativeApprovalsSnapshot(snapshot);
			const configLoad = nativePolicy ? {
				config: null,
				timedOut: false
			} : await loadConfigForApprovalsTarget({
				opts,
				source
			});
			const fileSnapshot = isFileApprovalsSnapshot(snapshot) ? snapshot : null;
			const effectivePolicy = buildEffectivePolicyReport({
				configLoad,
				source,
				approvals: fileSnapshot?.file,
				resolvedDefaults: fileSnapshot?.resolvedDefaults,
				hostPath: fileSnapshot?.path ?? "",
				nativePolicy
			});
			if (opts.json) {
				const outputSnapshot = fileSnapshot ? redactExecApprovals(fileSnapshot) : snapshot;
				defaultRuntime.writeJson({
					...outputSnapshot,
					effectivePolicy
				}, 0);
				return;
			}
			const muted = (text) => isRich() ? theme.muted(text) : text;
			if (source === "local") {
				defaultRuntime.log(muted("Showing local approvals."));
				defaultRuntime.log("");
			}
			renderApprovalsSnapshot(snapshot, source === "local" ? "local" : nodeId ? `node:${nodeId}` : "gateway");
			renderEffectivePolicy({ report: effectivePolicy });
		} catch (err) {
			failApprovalsCommand(err, opts);
		}
	}), { timeoutMs: APPROVALS_GET_DEFAULT_TIMEOUT_MS });
	nodesCallOpts(approvals.command("set").description("Replace exec approvals with a JSON file").option("--node <node>", "Target node id/name/IP").option("--gateway", "Force gateway approvals", false).option("--file <path>", "Path to JSON file to upload").option("--stdin", "Read JSON from stdin", false).action(async (opts) => {
		try {
			if (!opts.file && !opts.stdin) exitWithError("Provide --file or --stdin.");
			if (opts.file && opts.stdin) exitWithError("Use either --file or --stdin (not both).");
			const { source, nodeId, targetLabel, baseHash, kind } = await loadWritableSnapshotTarget(opts);
			const raw = opts.stdin ? await readStdin() : await readApprovalsFile(String(opts.file));
			let input;
			try {
				input = JSON5.parse(raw);
			} catch (err) {
				exitWithError(`Failed to parse approvals JSON: ${String(err)}`);
			}
			if (kind === "native") {
				await saveSnapshotTargeted({
					opts,
					source,
					nodeId,
					native: normalizeNativePolicyInput(input),
					baseHash,
					targetLabel
				});
				return;
			}
			if (!isRecord(input)) exitWithError("Exec approvals JSON must be an object.");
			const file = input;
			file.version = 1;
			await saveSnapshotTargeted({
				opts,
				source,
				nodeId,
				file,
				baseHash,
				targetLabel
			});
		} catch (err) {
			failApprovalsCommand(err, opts);
		}
	}));
	const allowlist = approvals.command("allowlist").description("Edit the per-agent allowlist").addHelpText("after", () => `\n${theme.heading("Examples:")}\n${formatExample("openclaw approvals allowlist add \"~/Projects/**/bin/rg\"", "Allowlist a local binary pattern for the main agent.")}\n${formatExample("openclaw approvals allowlist add --agent main --node <id|name|ip> \"/usr/bin/uptime\"", "Allowlist on a specific node/agent.")}\n${formatExample("openclaw approvals allowlist add --agent \"*\" \"/usr/bin/uname\"", "Allowlist for all agents (wildcard).")}\n${formatExample("openclaw approvals allowlist remove \"~/Projects/**/bin/rg\"", "Remove an allowlist pattern.")}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/approvals", "docs.openclaw.ai/cli/approvals")}\n`);
	registerAllowlistMutationCommand({
		allowlist,
		name: "add",
		description: "Add a glob pattern to an allowlist",
		mutate: ({ trimmedPattern, file, agent, agentKey, allowlistEntries }) => {
			if (allowlistEntries.some((entry) => normalizeAllowlistEntry(entry) === trimmedPattern)) {
				defaultRuntime.log("Already allowlisted.");
				return false;
			}
			allowlistEntries.push({
				pattern: trimmedPattern,
				lastUsedAt: Date.now()
			});
			agent.allowlist = allowlistEntries;
			file.agents = {
				...file.agents,
				[agentKey]: agent
			};
			return true;
		}
	});
	registerAllowlistMutationCommand({
		allowlist,
		name: "remove",
		description: "Remove a glob pattern from an allowlist",
		mutate: ({ trimmedPattern, file, agent, agentKey, allowlistEntries }) => {
			const nextEntries = allowlistEntries.filter((entry) => normalizeAllowlistEntry(entry) !== trimmedPattern);
			if (nextEntries.length === allowlistEntries.length) {
				defaultRuntime.log("Pattern not found.");
				return false;
			}
			if (nextEntries.length === 0) delete agent.allowlist;
			else agent.allowlist = nextEntries;
			if (isEmptyAgent(agent)) {
				const agents = { ...file.agents };
				delete agents[agentKey];
				file.agents = Object.keys(agents).length > 0 ? agents : void 0;
			} else file.agents = {
				...file.agents,
				[agentKey]: agent
			};
			return true;
		}
	});
	applyParentDefaultHelpAction(approvals);
}
const testing = {
	formatCliError,
	readStdin
};
//#endregion
export { registerExecApprovalsCli, testing };
