import { appConfigFileExists } from "../../config/file.ts"; import type { CliConfigureTargets, CliProgram } from "../../core/types.ts"; import { resolveCapabilities } from "../../runtime/capabilities.ts"; import type { InstallPaths } from "./paths.ts"; import { type InstallPlanMode, type InstallScope, type InstallTargetPreview, resolveEffectiveInstallTargets, resolveInstallPlanMode, } from "./target-effective.ts"; import { INSTALL_ARTIFACT_KEYS, INSTALL_TARGETS, installTargetForKey, MCP_KEYS, SKILL_KEYS, } from "./target-registry.ts"; import type { CliInstallArtifactKey, DetectedSnapshot, InstalledArtifacts, InstallOpts, TargetPlanContext, } from "./target-types.ts"; export { mcpCategoryEnabled } from "./target-effective.ts"; function emptyInstalledArtifacts(): InstalledArtifacts { return { app: false, skill: false, agentsMcp: false, }; } /** Detects which install artifacts are currently present. */ export function detectInstalledArtifacts(paths: InstallPaths, root: CliProgram): InstalledArtifacts { const out = emptyInstalledArtifacts(); for (const target of INSTALL_TARGETS) { target.applyDetected(paths, root, out); } return out; } /** Scope flags for install/uninstall. */ export function resolveInstallScope(opts: InstallOpts): InstallScope { return { all: opts.all, skill: opts.skill, mcp: opts.mcp, configure: opts.configure, uninstall: opts.uninstall, }; } /** Builds detected snapshot including app config for plan scoping. */ export function buildDetectedSnapshot(root: CliProgram, paths: InstallPaths): DetectedSnapshot { const base = detectInstalledArtifacts(paths, root); return { ...base, appConfig: appConfigFileExists(root), }; } function targetExplicitlyConfigured(user: CliConfigureTargets | undefined, key: CliInstallArtifactKey): boolean { if (key === "skill" || key === "agentsMcp") return false; return user?.[key] !== undefined; } function agentCategoryInScope( key: CliInstallArtifactKey, categoryKeys: readonly CliInstallArtifactKey[], category: "skill" | "mcp", effective: Record, targets: CliConfigureTargets | undefined, root: CliProgram, ): boolean { if (!categoryKeys.includes(key)) return false; if (!effective[key].enabled) return false; if (category === "mcp" && !resolveCapabilities(root).mcp) return false; return effective[key].includedInAll || targetExplicitlyConfigured(targets, key); } /** Whether an artifact is in scope for the current CLI flags and install mode. */ export function isArtifactInScope( key: CliInstallArtifactKey, scope: InstallScope, effective: Record, mode: InstallPlanMode, root: CliProgram, targets?: CliConfigureTargets, ): boolean { const userTargets = targets ?? root.configure?.targets; if (mode === "uninstall-all") { return true; } if (mode === "refresh") { return effective[key].enabled; } if (mode === "install-all") { const t = effective[key]; return t.enabled && t.includedInAll; } if (mode === "uninstall-scoped" || mode === "install-scoped") { if (!effective[key].enabled) return false; let inScope = false; if (scope.skill) { inScope = inScope || agentCategoryInScope(key, SKILL_KEYS, "skill", effective, userTargets, root); } if (scope.mcp) { inScope = inScope || agentCategoryInScope(key, MCP_KEYS, "mcp", effective, userTargets, root); } if (scope.configure && key === "configure") { inScope = true; } return inScope; } return false; } /** Whether to include an artifact in the current install/uninstall/refresh plan. */ export function shouldIncludeArtifact( key: CliInstallArtifactKey, root: CliProgram, paths: InstallPaths, scope: InstallScope, mode: InstallPlanMode, effective: Record, detected?: Partial>, ): boolean { const target = installTargetForKey(key); const targets = root.configure?.targets; if (key !== "configure" && target && !target.isAvailable(root, paths)) { return false; } if (key === "configure" && !root.appConfig) { return false; } if (mode === "refresh" && detected) { if (key === "app") { // app is status-only; refresh skills/MCP only } else if (!detected[key]) { return false; } } if ((mode === "uninstall-all" || mode === "uninstall-scoped") && detected) { const det = detected[key]; if (key === "configure") { if (!det) return false; } else if (mode === "uninstall-scoped") { if (!isArtifactInScope(key, scope, effective, mode, root, targets)) return false; if (!det) return false; } else if (!det) { return false; } } if (mode === "uninstall-all") { return true; } if (mode === "uninstall-scoped") { return isArtifactInScope(key, scope, effective, mode, root, targets); } if (mode === "refresh") { if (key === "app") return false; return effective[key].enabled; } return isArtifactInScope(key, scope, effective, mode, root, targets); } /** Builds plan context shared by install and uninstall planners. */ export function buildTargetPlanContext( root: CliProgram, paths: InstallPaths, opts: InstallOpts, detected: DetectedSnapshot, ): TargetPlanContext { const effective = resolveEffectiveInstallTargets(root.configure, root); const mode = resolveInstallPlanMode(opts); const scope = resolveInstallScope(opts); const detPartial = Object.fromEntries( INSTALL_TARGETS.map((t) => [t.key, t.detectedForSnapshot(detected)]), ) as Partial>; const include = (key: CliInstallArtifactKey) => shouldIncludeArtifact(key, root, paths, scope, mode, effective, detPartial); return { root, paths, opts, dry: !!opts.dry, detected, effective, scope, mode, include, }; } /** Maps detected snapshot to artifact key (for tests and legacy callers). */ export function detectedForArtifact(key: CliInstallArtifactKey, detected: DetectedSnapshot): boolean { const target = installTargetForKey(key); if (key === "configure") { return detected.appConfig ?? false; } return target?.detectedForSnapshot(detected) ?? false; } /** Resolved artifact keys for `--all`, `--mcp`, and `--skill` (availability-gated). */ export function resolveInstallTargetPreview(program: CliProgram, paths: InstallPaths): InstallTargetPreview { const effective = resolveEffectiveInstallTargets(program.configure, program); const keysForScope = (scope: InstallScope, mode: InstallPlanMode): CliInstallArtifactKey[] => INSTALL_ARTIFACT_KEYS.filter((key) => shouldIncludeArtifact(key, program, paths, scope, mode, effective)); return { all: keysForScope({ all: true }, "install-all"), mcp: keysForScope({ mcp: true }, "install-scoped"), skill: keysForScope({ skill: true }, "install-scoped"), }; }