/**
 * Plugin discovery, validation, and syncing.
 *
 * Plugins are bundles in ~/.agents/plugins/ that package skills, hooks,
 * commands, agents, workflows, bin scripts, MCP servers, and settings under a
 * single manifest (plugin.json). They are user-authored resources, sitting
 * alongside skills/, commands/, hooks/, etc. — git-tracked as source of truth.
 * This module discovers plugins, validates their manifests, and syncs their
 * contents into agent version homes. Workflows under a plugin’s workflows/
 * are resolved at run time by resolveWorkflowRef (Phase 5 packaging).
 */
import type { AgentId, DiscoveredPlugin, PluginManifest, MarketplaceSpec } from '../types.js';
export interface PluginCapabilities {
    hasHooks: boolean;
    hasMcp: boolean;
    hasBin: boolean;
    hasScripts: boolean;
    hasSettings: boolean;
    hasPermissions: boolean;
}
export declare const PLUGIN_EXEC_SURFACE_LABELS: Record<keyof PluginCapabilities, string>;
/**
 * Discover all plugins in a given plugins directory (e.g. ~/.agents/plugins/,
 * ~/.agents/.system/plugins/, <cwd>/.agents/plugins/, ~/.agents-<alias>/plugins/).
 * A valid plugin has a .claude-plugin/plugin.json manifest.
 *
 * `spec` stamps marketplace provenance onto each discovered plugin. Callers that
 * scan a single source dir without a marketplace identity (e.g. project-launch)
 * may omit it; those plugins default to the user marketplace.
 */
export declare function discoverPluginsInDir(pluginsDir: string, spec?: MarketplaceSpec): DiscoveredPlugin[];
/**
 * Discover every plugin across ALL marketplaces — the user repo (~/.agents/),
 * each enabled extra repo (~/.agents-<alias>/), and the project repo
 * (<cwd>/.agents/) — stamping marketplace provenance onto each.
 *
 * Plugin names are NOT deduplicated across marketplaces: a `code` plugin in both
 * the user repo and an extra repo yields two entries (`code@agents-cli` and
 * `code@agents-<alias>`), each installing into its own marketplace directory.
 */
export declare function discoverPlugins(opts?: {
    cwd?: string;
}): DiscoveredPlugin[];
export declare function buildDiscoveredPlugin(pluginRoot: string, manifest: PluginManifest, spec?: MarketplaceSpec): DiscoveredPlugin;
/** One category of resources a plugin packages, for display breakdowns. */
export interface PluginResourceGroup {
    /** Category key: 'skills' | 'commands' | 'subagents' | 'hooks' | 'mcp' | 'lsp' | 'monitors' | 'bin' | 'scripts' | 'settings'. */
    label: string;
    /** Display names — slash-prefixed for skills/commands (e.g. `/code:dispatch`), raw names otherwise. */
    items: string[];
}
/**
 * Ordered, non-empty resource groups a plugin packages. Single source of truth
 * for the breakdown shown by the plugin picker, `agents inspect --plugins`, and
 * its detail view. Empty categories are omitted; `settings` appears only when
 * the plugin merges non-permission settings.
 */
export declare function pluginResourceGroups(plugin: DiscoveredPlugin): PluginResourceGroup[];
export declare function inspectPluginCapabilities(pluginRoot: string): PluginCapabilities;
export declare function hasPluginExecSurfaces(capabilities: PluginCapabilities): boolean;
export declare function pluginCapabilityLabels(capabilities: PluginCapabilities): string[];
/**
 * Load a plugin manifest from a plugin directory.
 */
export declare function loadPluginManifest(pluginRoot: string): PluginManifest | null;
export declare function validatePluginName(name: string): boolean;
export declare function assertPluginTargetContained(targetRoot: string, pluginsDir: string): void;
/**
 * Get a specific plugin by name. On a cross-marketplace name collision the
 * highest-precedence scope wins (project > extra > user > system) — the same
 * resolution the sync writer's Map(last-wins) dedupe and collectPluginScopes()
 * use. discoverPlugins() yields low→high precedence order, so the LAST match is
 * the winner; returning the first match would resolve to the lowest scope (e.g.
 * a system plugin over the user's same-named one), which is exactly backwards.
 */
export declare function getPlugin(name: string): DiscoveredPlugin | null;
/**
 * Check if an agent supports a specific plugin.
 * If the plugin specifies agents, only those are supported.
 * Otherwise defaults to all plugin-capable agents.
 */
export declare function pluginSupportsAgent(plugin: DiscoveredPlugin, agent: AgentId): boolean;
/** Fact basenames (no .md) from a plugin's memory/ directory. */
export declare function discoverPluginMemory(pluginRoot: string): string[];
/**
 * The lifecycle events a plugin hooks into, read from hooks/hooks.json.
 *
 * The official plugin format wraps the event map under a `hooks` key
 * (`{ description, hooks: { SessionStart: [...], PreToolUse: [...] } }`), so the
 * meaningful keys are the events — NOT the top-level keys (`description`,
 * `hooks`). Older/flat files put the event names at the top level directly; we
 * read whichever object actually holds the event map.
 */
export declare function discoverPluginHooks(pluginRoot: string): string[];
/** Discover command .md files inside a plugin's commands/ directory. */
export declare function discoverPluginCommands(pluginRoot: string): string[];
/** Discover agent definition .md files inside a plugin's agents/ directory. */
export declare function discoverPluginAgentDefs(pluginRoot: string): string[];
/**
 * Discover workflow directories inside a plugin's `workflows/` folder.
 * A valid workflow is a directory containing WORKFLOW.md (same contract as
 * project/user/system workflows). Phase 5: plugins package workflows as
 * entrypoints so `agents run <name>` can resolve them without a separate
 * install into ~/.agents/workflows/.
 */
export declare function discoverPluginWorkflows(pluginRoot: string): string[];
/** Discover executable files in a plugin's bin/ directory. */
export declare function discoverPluginBin(pluginRoot: string): string[];
/** Discover MCP server names from .mcp.json at the plugin root. */
export declare function discoverPluginMcpServers(pluginRoot: string): string[];
/** Discover LSP server keys from .lsp.json at the plugin root. */
export declare function discoverPluginLspServers(pluginRoot: string): string[];
/** Discover monitor names from monitors/monitors.json. */
export declare function discoverPluginMonitors(pluginRoot: string): string[];
/**
 * Expand plugin variables in a string.
 *
 * Variables:
 *   ${CLAUDE_PLUGIN_ROOT}      -> absolute path to plugin directory
 *   ${CLAUDE_PLUGIN_DATA}      -> per-version data directory for this plugin
 *   ${user_config.<key>}       -> value from plugin's .user-config.json
 */
export declare function expandPluginVars(str: string, pluginRoot: string, pluginName: string, agentId: AgentId, versionHome: string, userConfig?: Record<string, string>): string;
/**
 * Load persisted user config for a plugin from .user-config.json.
 */
export declare function loadUserConfig(pluginName: string): Record<string, string>;
/**
 * Persist user config for a plugin to .user-config.json.
 */
export declare function saveUserConfig(pluginName: string, config: Record<string, string>): void;
/**
 * Check plugin dependencies against installed plugins.
 * Returns names of missing dependencies (warning only — not a hard error).
 */
export declare function checkPluginDependencies(manifest: PluginManifest): string[];
/**
 * Reconstruct a MarketplaceSpec from a marketplace name. The inverse of
 * marketplaceNameFor(): "agents-cli" → user, "agents-project" → project,
 * "agents-system" → system, "agents-<alias>" → extra. The per-version
 * marketplace operations only key off the name (never spec.root), but we
 * resolve the real source root anyway so the spec is honest for any caller that
 * inspects it (e.g. descriptionFor, which would otherwise label the system
 * marketplace as an extra repo named "system").
 */
export declare function marketplaceSpecForName(name: string | undefined, cwd?: string): MarketplaceSpec;
/**
 * Sync a plugin to a specific agent version's home directory.
 *
 * For plugins-capable agents (claude, openclaw):
 *   1. Copy plugin source into <versionHome>/.<agent>/plugins/marketplaces/agents-cli/plugins/<name>/
 *   2. Pre-expand ${user_config.*} variables in copied text files (Claude doesn't know this var).
 *   3. (Re-)synthesize the marketplace.json catalog from the installed plugins.
 *   4. Register the synthetic marketplace in known_marketplaces.json.
 *   5. Mark <plugin>@agents-cli enabled in settings.json#enabledPlugins.
 *   6. Migrate (remove) legacy dual-dash skills/commands/agents/bin/hooks/mcp entries.
 *
 * Claude/OpenClaw natively handle the plugin's skills, commands, agents, hooks,
 * MCP servers, bin/, settings.json, and permissions once the plugin lives at the
 * native install path and is marked enabled — see
 * https://code.claude.com/docs/en/plugins.
 *
 * Droid (Factory CLI) reuses this same marketplace layout but additionally
 * requires an installed_plugins.json registry entry and a "local"-source
 * known_marketplaces.json entry before `droid plugin list` will see the plugin
 * (steps handled by registerMarketplace's droid branch + step 5c below).
 */
export declare function syncPluginToVersion(plugin: DiscoveredPlugin, agent: AgentId, versionHome: string, options?: {
    allowExecSurfaces?: boolean;
    version?: string;
}): {
    success: boolean;
    skills: string[];
    commands: string[];
    agentDefs: string[];
    bin: string[];
    hooks: string[];
    permissions: boolean;
    mcp: boolean;
    settings: boolean;
};
/**
 * OpenCode loads JS/TS modules from `$HOME/.config/opencode/plugins/` (global)
 * and `<project>/.opencode/plugins/` (project). Under agents-cli version
 * isolation HOME is the version home, so we write:
 *   {versionHome}/.config/opencode/plugins/
 *
 * Claude-style marketplace plugins are NOT auto-converted; we install modules
 * from (in order):
 *   1. pluginRoot/opencode/*.{ts,js,mjs,cjs}
 *   2. pluginRoot/plugins/*.{ts,js,mjs,cjs}
 *   3. pluginRoot/*.{ts,js,mjs,cjs} (excluding *.test.* / *.spec.*)
 * If none exist, install still succeeds by writing a marker + copying any
 * package.json so empty plugins don't break sync; opencode simply has nothing
 * to load until a real module appears.
 */
export declare function openCodePluginsDir(versionHome: string): string;
/** Resolve source module files for an agents-cli plugin to install into OpenCode. */
export declare function resolveOpenCodePluginSources(pluginRoot: string): string[];
export declare function installOpenCodePlugin(plugin: DiscoveredPlugin, versionHome: string): boolean;
export declare function isOpenCodePluginInstalled(pluginName: string, versionHome: string): boolean;
export declare function removeOpenCodePlugin(pluginName: string, versionHome: string): boolean;
/**
 * Goose auto-discovers Open Plugins at `$HOME/.agents/plugins/<name>/`.
 * Under agents-cli version isolation HOME is the version home, so we install to:
 *   {versionHome}/.agents/plugins/<name>/
 *
 * Full plugin directory copy (not marketplace) — same layout goose and
 * agents-cli share for source plugins.
 */
export declare function goosePluginsDir(versionHome: string): string;
export declare function installGoosePlugin(plugin: DiscoveredPlugin, versionHome: string): boolean;
export declare function isGoosePluginInstalled(pluginName: string, versionHome: string): boolean;
export declare function removeGoosePlugin(pluginName: string, versionHome: string): boolean;
/**
 * Hermes (Nous Research) loads plugins from a flat `$HOME/.hermes/plugins/<name>/`
 * directory holding a `plugin.yaml` manifest — NOT the Claude marketplace layout.
 * Under agents-cli version isolation HOME is the version home, so we install to:
 *   {versionHome}/.hermes/plugins/<name>/
 * A plugin does not load until its name is added to `plugins.enabled` (a YAML
 * array) in `{versionHome}/.hermes/config.yaml`; a deny-list `plugins.disabled`
 * wins on conflict, so agents-cli only manages the `enabled` allowlist and never
 * touches `disabled` (user-owned).
 */
export declare function hermesPluginsDir(versionHome: string): string;
/**
 * Add or remove a plugin name in `plugins.enabled` within ~/.hermes/config.yaml,
 * preserving every other key (read → mutate → write). Never touches
 * `plugins.disabled`. No-op (no rewrite) when the desired state already holds.
 */
export declare function setHermesPluginEnabled(pluginName: string, versionHome: string, enabled: boolean): void;
export declare function installHermesPlugin(plugin: DiscoveredPlugin, versionHome: string, enable: boolean): boolean;
export declare function isHermesPluginInstalled(pluginName: string, versionHome: string): boolean;
export declare function removeHermesPlugin(pluginName: string, versionHome: string): boolean;
/**
 * Check if a plugin is synced to a version. True when the plugin lives at the
 * native marketplace install path. Legacy dual-dash entries are not counted —
 * they're treated as stale and migrated away on the next sync.
 */
export declare function isPluginSynced(plugin: DiscoveredPlugin, agent: AgentId, versionHome: string): boolean;
/**
 * Remove a plugin from a specific agent version's home directory.
 * Inverse of syncPluginToVersion.
 *
 * Works whether or not the plugin source still exists on disk.
 */
export declare function removePluginFromVersion(pluginName: string, pluginRoot: string, agent: AgentId, versionHome: string): {
    skills: string[];
    commands: string[];
    agentDefs: string[];
    bin: string[];
    hooks: string[];
    permissions: number;
    mcp: number;
};
/**
 * The active plugin set, either as bare names (legacy callers / the dual-dash
 * sweep, which has no marketplace to key on) or as the discovered plugins
 * themselves (which carry marketplace provenance, enabling per-marketplace
 * orphan detection — the PHNX-2618 shadow case below).
 */
export type ActivePluginsInput = Set<string> | Array<{
    name: string;
    marketplace?: string;
}>;
/**
 * Remove orphaned plugin entries from a version home. A marketplace-plugin
 * install is "orphan" when no active source plugin matches its (marketplace,
 * name) pair (see isOrphanMarketplacePlugin). Soft-deletes the affected
 * marketplace plugin dir to ~/.agents/.trash/plugins/. Also cleans up any
 * legacy dual-dash skills/ directories from older agents-cli versions.
 *
 * Pass the discovered plugins (`discoverPlugins()`) for marketplace-aware
 * detection; a bare `Set<string>` of names keeps the original name-only behavior.
 */
export declare function cleanOrphanedPluginSkills(agent: AgentId, versionHome: string, activePlugins: ActivePluginsInput, version?: string): string[];
export interface VersionPluginDiff {
    agent: AgentId;
    version: string;
    orphans: string[];
}
export declare function diffVersionPlugins(agent: AgentId, version: string): VersionPluginDiff;
export declare function iterPluginsCapableVersions(filter?: {
    agent?: AgentId;
    version?: string;
}): Array<{
    agent: AgentId;
    version: string;
}>;
export declare function removePluginSkillFromVersion(agent: AgentId, version: string, skillName: string): {
    success: boolean;
    error?: string;
};
/**
 * Parse an install spec of the form `name@source` or just `source`.
 * Source can be a git URL or an absolute/relative local path.
 */
export declare function parseInstallSpec(spec: string): {
    name: string | null;
    source: string;
};
/**
 * Install a plugin from a git URL or local path.
 * Clones/copies to ~/.agents/plugins/<name>/.
 * Returns the installed plugin name and root path.
 */
export declare function installPlugin(spec: string): Promise<{
    name: string;
    root: string;
    isNew: boolean;
    capabilities: PluginCapabilities;
}>;
/** Parsed `.source` provenance written by install/update. `version` is the
 *  upstream manifest version captured at the last pull (absent on pre-existing
 *  installs from before baseline tracking). */
export interface PluginSourceInfo {
    source: string;
    isGit: boolean;
    version?: string;
}
/** Read a plugin's `.source` provenance, or null when absent/unreadable. */
export declare function readPluginSourceInfo(root: string): PluginSourceInfo | null;
/**
 * Resolve the CURRENT upstream manifest version for a local-sourced plugin
 * (the `.system`/local-path case). Returns null for git sources — reading their
 * upstream version would need a network fetch, so git plugins are refreshed only
 * via the explicit `agents plugins update`.
 */
export declare function getUpstreamManifestVersion(info: PluginSourceInfo): string | null;
/**
 * Update an installed plugin by re-pulling from its original source.
 * Returns true if the update succeeded.
 */
/**
 * Labels of exec surfaces present in `after` that were NOT present in `before`.
 * Used by updatePlugin to distinguish a newly-appearing execution surface
 * (upstream compromise → renewed consent required) from one the user already
 * trusted (leave enablement alone).
 */
export declare function newExecSurfaceLabels(before: PluginCapabilities, after: PluginCapabilities): string[];
/**
 * Re-fetch a plugin from its recorded source and apply the update to disk.
 *
 * Security (RUSH-1757): a plugin's upstream is mutable. `updatePlugin` never
 * mutates the live plugin tree before it has inspected the incoming content —
 * the new revision is fetched into a **quarantine** dir first, its capabilities
 * are diffed against the current on-disk baseline, and the update is applied to
 * `plugin.root` only after the trust decision. If the update introduces a NEW
 * executable surface (hooks/, .mcp.json, bin/, scripts/, settings.json,
 * permissions/) that the current revision did not carry, the update is refused
 * unless `options.allowExecSurfaces` is set — the last-good content is kept in
 * place, so a benign-then-compromised upstream can never execute on the next
 * update without renewed consent. A surface the user already trusted is not a
 * "new" surface and does not re-trigger the gate.
 */
export declare function updatePlugin(name: string, options?: {
    allowExecSurfaces?: boolean;
}): Promise<{
    success: boolean;
    error?: string;
    /** True when the update was refused because it introduced new exec surfaces. */
    blockedByExecSurfaces?: boolean;
    /** Labels of exec surfaces newly introduced by this update, if any. */
    newExecSurfaces?: string[];
    /** Whether the applied revision carries executable surfaces (for the caller's re-sync). */
    hasExecSurfaces?: boolean;
}>;
