// Executes validated plugin, marketplace, ClawHub, and hook-pack install requests. import { theme } from "../../packages/terminal-core/src/theme.js"; import { assertConfigWriteAllowedInCurrentMode } from "../config/config.js"; import { parseClawHubPluginSpec, reportClawHubPluginInstallTelemetry } from "../infra/clawhub.js"; import { formatErrorMessage } from "../infra/errors.js"; import { CLAWHUB_INSTALL_ERROR_CODE } from "../plugins/clawhub.js"; import { resolveDefaultPluginExtensionsDir } from "../plugins/install-paths.js"; import { persistPluginInstall } from "../plugins/install-persistence.js"; import { installManagedPluginSource } from "../plugins/management-service.js"; import { installPluginFromMarketplace } from "../plugins/marketplace.js"; import { withPluginLifecycleLease } from "../plugins/plugin-lifecycle-lease.js"; import { tracePluginLifecyclePhaseAsync } from "../plugins/plugin-lifecycle-trace.js"; import { defaultRuntime } from "../runtime.js"; import { markClawPackageIndependentlyOwned } from "../state/claw-package-adoption.js"; import { withClawPackageLifecycleLease } from "../state/claw-package-lifecycle-lease.js"; import { shortenHomePath } from "../utils.js"; import { resolveClawHubRiskAcknowledgementCliOptions } from "./clawhub-risk-acknowledgement.js"; import { confirmNonClawHubInstall, type NonClawHubInstallSourceClass, } from "./non-clawhub-install-acknowledgement.js"; import { createPluginInstallLogger, formatPluginInstallWithHookFallbackError, } from "./plugins-command-helpers.js"; import { loadConfigForInstall, resolveFullyBlockedConfigMutationReason, } from "./plugins-install-config.js"; import { isTerminalPluginInstallFailure, probeHookPackFromPath, resolveInstallSafetyOverrides, tryInstallHookPackFromLocalPath, tryInstallPluginOrHookPackFromNpmSpec, } from "./plugins-install-hook-fallback.js"; import { resolvePluginInstallPreflight, type PluginInstallPreflight, type RunPluginInstallCommandParams, } from "./plugins-install-preflight.js"; const DEPRECATED_DANGEROUS_FORCE_UNSAFE_INSTALL_WARNING = "--dangerously-force-unsafe-install is deprecated and no longer affects plugin installs because built-in install-time dangerous-code scanning has been removed. Configure security.installPolicy for operator-owned install decisions."; function isClawHubBlockedCliFailure(result: { code?: string; warning?: string }): boolean { return ( result.code === CLAWHUB_INSTALL_ERROR_CODE.CLAWHUB_DOWNLOAD_BLOCKED && typeof result.warning === "string" && result.warning.trim().length > 0 ); } /** Validate install intent before opening the SQLite-backed plugin lifecycle lease. */ export async function runPluginInstallCommand(params: RunPluginInstallCommandParams) { assertConfigWriteAllowedInCurrentMode(); const runtime = params.runtime ?? defaultRuntime; const preflight = await resolvePluginInstallPreflight(params); if (!preflight.ok) { runtime.error(preflight.error); return runtime.exit(1); } return await withPluginLifecycleLease( {}, async () => await runPluginInstallCommandUnlocked(params, preflight), ); } async function runPluginInstallCommandUnlocked( params: RunPluginInstallCommandParams, preflight: Extract, ) { assertConfigWriteAllowedInCurrentMode(); const runtime = params.runtime ?? defaultRuntime; const invalidateRuntimeCache = params.invalidateRuntimeCache ?? true; const { raw, opts, installMode, request } = preflight; if (opts.dangerouslyForceUnsafeInstall) { runtime.log(theme.warn(DEPRECATED_DANGEROUS_FORCE_UNSAFE_INSTALL_WARNING)); } const snapshot = await loadConfigForInstall(request).catch((error: unknown) => { runtime.error(formatErrorMessage(error)); return null; }); if (!snapshot) { return runtime.exit(1); } const safetyOverrides = resolveInstallSafetyOverrides({ ...opts, config: snapshot.config }); const acknowledgeNonClawHubSource = async ( sourceClass: NonClawHubInstallSourceClass, spec: string, ): Promise => await confirmNonClawHubInstall({ acknowledged: opts.force, runtime, sourceClass, spec, }); if (preflight.sourcePlan === null) { if ( !(await acknowledgeNonClawHubSource("marketplace", `${raw} from ${preflight.marketplace}`)) ) { return runtime.exit(1); } const result = await installPluginFromMarketplace({ ...safetyOverrides, marketplace: preflight.marketplace, mode: installMode, plugin: raw, extensionsDir: resolveDefaultPluginExtensionsDir(), logger: createPluginInstallLogger(runtime), }); if (!result.ok) { if (!isClawHubBlockedCliFailure(result)) { runtime.error(result.error); } return runtime.exit(1); } await persistPluginInstall({ snapshot, pluginId: result.pluginId, install: { source: "marketplace", installPath: result.targetDir, version: result.version, marketplaceName: result.marketplaceName, marketplaceSource: result.marketplaceSource, marketplacePlugin: result.marketplacePlugin, }, invalidateRuntimeCache, runtime, }); return; } const { sourcePlan } = preflight; if ( sourcePlan.acknowledgement && !(await acknowledgeNonClawHubSource( sourcePlan.acknowledgement.sourceClass, sourcePlan.acknowledgement.spec, )) ) { return runtime.exit(1); } const sourceRequest = sourcePlan.request; switch (sourceRequest.source) { case "local": { const resolved = sourceRequest.path; if (sourceRequest.link) { sourceRequest.successMessage = `Linked plugin path: ${shortenHomePath(resolved)}`; } const fullyBlockedReason = resolveFullyBlockedConfigMutationReason(snapshot); if (fullyBlockedReason) { runtime.error(fullyBlockedReason); return runtime.exit(1); } if (snapshot.pluginMutation.mode === "blocked" || snapshot.hookMutation.mode === "blocked") { const hookProbe = await probeHookPackFromPath({ ...safetyOverrides, path: resolved, mode: installMode, inspection: "package-kind", }); if (hookProbe.ok && hookProbe.packageKind === "hook-only") { if (snapshot.hookMutation.mode === "blocked") { runtime.error(snapshot.hookMutation.reason); return runtime.exit(1); } const hookFallback = await tryInstallHookPackFromLocalPath({ snapshot, installMode, resolvedPath: resolved, safetyOverrides, ...(opts.link ? { link: true } : {}), expectedPackageKind: "hook-only", runtime, }); if (hookFallback.ok) { return; } runtime.error(hookFallback.error); return runtime.exit(1); } if (snapshot.pluginMutation.mode === "blocked") { runtime.error(snapshot.pluginMutation.reason); return runtime.exit(1); } } const result = await installManagedPluginSource({ request: sourceRequest, snapshot, safetyOverrides, logger: createPluginInstallLogger(runtime), invalidateRuntimeCache, runtime, }); if (result.ok) { return; } if (isTerminalPluginInstallFailure(result.code)) { runtime.error(result.error); return runtime.exit(1); } const hookFallback = await tryInstallHookPackFromLocalPath({ snapshot, installMode, resolvedPath: resolved, safetyOverrides, ...(sourceRequest.link ? { link: true } : {}), runtime, }); if (hookFallback.ok) { return; } runtime.error(formatPluginInstallWithHookFallbackError(result.error, hookFallback)); return runtime.exit(1); } case "npm-pack": case "git": { const result = await installManagedPluginSource({ request: sourceRequest, snapshot, safetyOverrides, logger: createPluginInstallLogger(runtime), invalidateRuntimeCache, runtime, }); if (!result.ok) { runtime.error(result.error); return runtime.exit(1); } return; } case "bundled": { const result = await tracePluginLifecyclePhaseAsync( "install execution", () => installManagedPluginSource({ request: sourceRequest, snapshot, invalidateRuntimeCache, runtime, }), { command: "install", source: "bundled", pluginId: sourceRequest.bundledSource.pluginId, }, ); if (!result.ok) { runtime.error(result.error); return runtime.exit(1); } return; } case "official": { const result = await tryInstallPluginOrHookPackFromNpmSpec({ snapshot, installMode, spec: sourceRequest.spec, pin: sourceRequest.pin, safetyOverrides, allowBundledFallback: false, expectedPluginId: sourceRequest.pluginId, expectedIntegrity: sourceRequest.expectedIntegrity, trustedSourceLinkedOfficialInstall: true, official: true, invalidateRuntimeCache, runtime, }); if (!result.ok) { return runtime.exit(1); } return; } case "clawhub": { const installFromClawHub = async ( installSnapshot = snapshot, installSafetyOverrides = safetyOverrides, ) => { const acknowledgement = resolveClawHubRiskAcknowledgementCliOptions({ acknowledgeClawHubRisk: opts.acknowledgeClawHubRisk, action: "installing", }); const result = await installManagedPluginSource({ request: { ...sourceRequest, ...(opts.expectedIntegrity ? { expectedIntegrity: opts.expectedIntegrity } : {}), ...(opts.expectedPluginId ? { expectedPluginId: opts.expectedPluginId } : {}), ...(acknowledgement.acknowledgeClawHubRisk ? { acknowledgeClawHubRisk: true } : {}), ...(acknowledgement.onClawHubRisk ? { onClawHubRisk: acknowledgement.onClawHubRisk } : {}), }, snapshot: installSnapshot, safetyOverrides: installSafetyOverrides, logger: createPluginInstallLogger(runtime), invalidateRuntimeCache, runtime, }); if (!result.ok) { if (!isClawHubBlockedCliFailure(result)) { runtime.error(result.error); } return runtime.exit(1); } if (!result.clawhub) { runtime.error("ClawHub plugin install completed without source metadata."); return runtime.exit(1); } if (!params.clawManaged && result.clawhub.version) { markClawPackageIndependentlyOwned({ kind: "plugin", source: "clawhub", ref: result.clawhub.clawhubPackage, version: result.clawhub.version, }); } await reportClawHubPluginInstallTelemetry({ baseUrl: result.clawhub.clawhubUrl, packageName: result.clawhub.clawhubPackage, version: result.clawhub.version, }).catch(() => undefined); }; if (params.clawManaged) { return await installFromClawHub(); } return await withClawPackageLifecycleLease( { kind: "plugin", source: "clawhub", ref: parseClawHubPluginSpec(sourceRequest.spec)?.name ?? sourceRequest.spec, }, async () => { const leasedSnapshot = await loadConfigForInstall(request).catch((error: unknown) => { runtime.error(formatErrorMessage(error)); return null; }); if (!leasedSnapshot) { return runtime.exit(1); } return await installFromClawHub( leasedSnapshot, resolveInstallSafetyOverrides({ ...opts, config: leasedSnapshot.config }), ); }, ); } case "npm": { const result = await tryInstallPluginOrHookPackFromNpmSpec({ snapshot, installMode, spec: sourceRequest.spec, pin: sourceRequest.pin, safetyOverrides, allowBundledFallback: sourceRequest.allowBundledFallback ?? false, invalidateRuntimeCache, expectedPluginId: sourceRequest.expectedPluginId, expectedIntegrity: sourceRequest.expectedIntegrity, trustedSourceLinkedOfficialInstall: sourceRequest.trustedSourceLinkedOfficialInstall, runtime, }); if (!result.ok) { return runtime.exit(1); } } } }