For AI agents: the complete documentation index is available at https://scriptedalchemy.github.io/agent-bundle/llms.txt, the full documentation bundle is available at https://scriptedalchemy.github.io/agent-bundle/llms-full.txt, and this page is available as Markdown at https://scriptedalchemy.github.io/agent-bundle/guide/distribution/installation.md.
Skip to main content
  • English
  • Host installation

    The composite plugin root build emits contains one generated INSTALL.md, a section per selected host, with commands that use the bundle's real plugin and marketplace names. Every command in it is a host command (claude plugin …, codex plugin …) or the bundled install.mjs, so a consumer who receives the built root, from npm, a marketplace checkout, or a zip, never needs the agent-bundle CLI to install, reinstall, or uninstall it; INSTALL.md says so and marks the CLI optional wherever it is mentioned. The framework CLI performs those same operations for developers who have it, and every host installs from the same directory:

    npx agent-bundle install claude --from artifact --scope user
    npx agent-bundle install codex --from artifact
    npx agent-bundle install cursor --from artifact
    npx agent-bundle install amp --from artifact --scope user

    --from names the composite root: the directory that holds agent-bundle.manifest.json. The installer reads that one file and nothing else to learn what it is installing, the plugin name and version come from application, the marketplace name from projections[host].marketplace, and the file the host is pointed at from projections[host].documents.plugin (or documents.entry for Amp; see the Artifact manifest). It needs no project sources, and there is no <from>/<host> lookup: builds write no artifact/<host> subdirectory, and the root is the bundle. AB7001 refuses a root whose manifest is missing or not canonical, that has no projection for the host, whose projection has no host plugin manifest (or whose pointed file is absent), that declares no marketplace identity for claude or codex, or whose plugin name is not a safe local plugin name for cursor.

    What each host accepts

    HostMechanismScopes
    Amp (experimental)Copies only .amp/plugins/<name>/ into the project root or $XDG_CONFIG_HOME/amp/plugins/<name> (falling back to ~/.config/amp/plugins/<name>). It does not edit settings, trust, disabled state, personal/workspace repositories, or any other plugin. amp plugins list inspects the copy when an Amp account is available; reloading a running session remains the interactive Ctrl+O → plugins: reload action. Live activation is contract-verified, runtime-unverified.project, user (system root)
    Claude CodeDelegates to claude plugin marketplace add and claude plugin install. Claude Code loads hooks/hooks.json and .mcp.json from the plugin root on its own, so .claude-plugin/plugin.json carries no hooks pointer (Claude Code reports one at that file as a duplicate hooks file); the Codex and Cursor documents beside their own manifests in the same root are never read by Claude Code. For a directory marketplace, Claude Code resolves ${CLAUDE_PLUGIN_ROOT} to that directory itself, not to its plugin cache, so rebuilding it changes the hooks a running session executes.user, project, local
    CodexDelegates to codex plugin marketplace add and codex plugin add. .codex-plugin/plugin.json points at its own .codex-plugin/hooks.json and .codex-plugin/mcp.json, so Codex never falls back to discovering another host's documents in the root.user
    CursorCopies the root into ~/.cursor/plugins/local/<name> (--mode local, the default), because Cursor publishes no non-interactive install verb; --mode marketplace instead stages a committed local marketplace repository under ~/.cursor/agent-bundle/marketplaces/<name> and prints the Customize → Plugins → "Add Plugins from Local Repository" step. Either way Cursor loads the hook and MCP documents .cursor-plugin/plugin.json names, .cursor-plugin/hooks.json and .cursor-plugin/mcp.json, beside the manifest, so plugin hooks run with ${CURSOR_PLUGIN_ROOT} substituted and need no ~/.cursor/hooks.json entry.user
    Grok BotStages the Cursor projection (the bundle must list cursor in targets) as a committed marketplace repository under ~/.grokbot/agent-bundle/marketplaces/<name> ($GROK_BOT_HOME overrides ~/.grokbot) and records a store receipt. Grok Bot installs Cursor-format plugins into the user's account only from hosted Git marketplaces, through its Marketplace UI, the in-chat plugin install tool, or grokbot://app/v1/plugin/add?id=<plugin id>, all keyed by a server-assigned plugin id; there is no local-folder import or CLI verb, so install grokbot prints the remaining steps (host the repository, add it as a marketplace, install from Grok Bot) instead of claiming a registration. On a computer with Grok Bot agent-data it also sideloads the plugin into Grok Bot's own clone of the scriptedalchemy/plugins marketplace (its manifest included) and plugin cache, under the clone commit Grok Bot already uses; Grok Bot loads that copy only once its account plugin listing names the plugin, and its plugin sync otherwise removes the cache copy (--no-sideload, --sideload-repo, and --sideload-slug in the CLI reference). doctor --host grokbot reports the plugin id and installed commit from the Grok Bot computer's plugin cache (AB7334) and the sideload state (AB7335); uninstall grokbot removes the staging, the receipted sideload, and the receipt, and names the plugin id to uninstall in Grok Bot.user

    Because the Claude and Codex projections always ship local marketplace manifests, their public CLIs can install the root directly. The installer fails with a typed diagnostic when the selected host binary is unavailable, rather than reporting a success it did not achieve. Host installation diagnostics are the AB700x family: bundle identity, host availability, scope, command failure, and collision checks.

    Amp is experimental: contract-verified, runtime-unverified. It does not expose a non-interactive reload or standalone validator. Installation therefore returns the interactive reload step and never runs it. In this account-less validation environment, amp plugins list and amp skills list --json require login; live registration, enabled state, and tool execution are recorded as unverified, not unsupported.

    The standalone installer

    A root built with cursor or portable among its targets includes an install.mjs that copies the root into ~/.cursor/plugins/local/<name> without overwriting collisions:

    npx agent-bundle install cursor --from artifact
    # or, from the root itself:
    node ./install.mjs

    Its staged copy is idempotent for identical content and refuses version or content collisions. It never invokes sudo and never changes PATH. When agent-bundle.manifest.json is present, the installer reads the selected projection's plugin and MCP pointers from it; conventional .cursor-plugin/* and root Agent Plugins paths are fallback discovery for manifest-less packs only.

    For a root Cursor loads as an Agent Plugins 1.0.0 pack, portable selected without cursor, so the root plugin.json is the only manifest Cursor finds, the installer also expands the standard's placeholders in the Cursor copy of mcp.json, because Cursor 3.18.25 does not: it leaves ${PLUGIN_ROOT} / ${PLUGIN_DATA} unexpanded in args, env values, and cwd, provides no PLUGIN_ROOT / PLUGIN_DATA variables (spec §9.1), defaults an omitted cwd to the home directory, and resolves plugin-relative ./ commands against the workspace folder (spec §7.2.1), so every spec-shaped stdio server fails to spawn. The Cursor copy gets the absolute plugin root, ~/.cursor/agent-bundle/plugin-data/<name> (created by the installer) as the data directory, a plugin-root cwd, resolved ./ commands, and PLUGIN_ROOT / PLUGIN_DATA in each stdio server's environment. The bundle itself is untouched and stays spec-conformant for other Agent Plugins clients; the pre-expansion document is kept in the install receipt (cursorExpansion), and agent-bundle doctor --host cursor proves the expansion with AB7326. Nothing here is Cursor behaviour: the provenance is derived, and a root that carries .cursor-plugin/plugin.json, cursor selected, is copied unchanged, because Cursor loads that manifest instead.

    Artifact validation rejects a root whose required install surface is missing, so a bundle cannot ship without the installer it promises.

    The npm-installed CLI

    The npm package root contains every manifest-owned composite-plugin byte plus npm-only metadata and authored package entries. Commands such as validate --artifact still take the bare composite artifact; install --from is manifest-aware and accepts either root. For a generated routed CLI, package.json points its bin at the same bin/<name>.mjs executable the artifact exposes, without a second compilation or wrapper. Its complete command surface, including web, therefore works from node_modules exactly as it does from a copied artifact. No npm lifecycle performs a host installation, installing a package never mutates a host's plugin state.

    Repository AGENTS.md instructions are copied as authored package metadata. They are never replaced by generated projection instructions; a path collision fails the package build.

    Install any supported host explicitly from that root with npx agent-bundle install <host> --from node_modules/<package>. This manifest-aware path excludes npm-only metadata and authored package entries from host ownership comparisons. The packed host proof reconstructs the manifest-owned artifact from the installed package and exercises agent-bundle install --from after deleting the source checkout.

    A package-bound installer bin

    A published plugin that wants <name> install <host> without asking its users to install the agent-bundle CLI declares one bin entry and delegates to agent-bundle/install:

    // src/install-bin.ts — the whole installer a package ships
    import { fileURLToPath } from 'node:url';
    
    import { runInstallCli } from 'agent-bundle/install';
    
    export const main = (argv: readonly string[]): Promise<number> =>
      runInstallCli(argv, {
        // The npm root is the artifact, so a bin under bin/ binds its parent directory.
        from: fileURLToPath(new URL('..', import.meta.url)),
        name: 'my-plugin-install',
      });

    runInstallCli is the agent-bundle CLI's own install <host>, uninstall <host>, and doctor commands, the same flags, receipts, replacement rules, --plan, data policy, --json output, and exit codes, with the bundle root pinned to from, so the bin exposes no --from and never searches for another artifact root. The package build inlines the lifecycle into the executable; the installed package needs no agent-bundle at run time. The entry also exports installBundle, uninstallBundle, runDoctor, and the format* helpers for a bin that composes its own command surface. The read-only status check is doctor --host <host>: it reports the installed copy as current, stale, version-mismatched, foreign, or absent.

    Exit codes follow the agent-bundle CLI. 0 is success. doctor always writes its report (human or --json) to stdout and exits 1 when that report carries an error diagnostic, read the report, not stderr. An install or uninstall that fails, or any command that throws, writes its diagnostics as one JSON line to stderr and exits 1. A usage error exits 2.

    Development installs are a different thing

    agent-bundle dev --install-host <host> maintains a marked development install that follows successful rebuild epochs, with atomic generation switching and a stable proxy command. That is covered in Developer Workbench and is not the same operation as agent-bundle install. Amp is intentionally not a development-install host; use its owned agent-bundle install amp copy and the interactive reload action.

    Reinstalling after a rebuild

    Every emitted installer, agent-bundle install <host> and the standalone install.mjs, shares one replace policy. An identical copy is an already-installed no-op. A copy of the same version whose content hash differs is replaced automatically, so rebuilding without a version bump no longer needs an uninstall and rm -rf. A different version is refused with AB7005 unless you pass --replace, and a foreign directory, one this plugin's installer did not place, is refused either way. A copy placed before install receipts existed carries no receipt, so it is foreign too, byte-identical or not: remove it by hand and reinstall. The standalone install.mjs hashes a receipt-less destination from the files present there, so the refusal can name the content comparison. When agent-bundle.manifest.json is present, the artifact hash uses that manifest's files[], and a listed path missing from the artifact fails the run. A listed path missing from a foreign or staged destination is omitted from its hash, so the hashes differ and install refuses that copy instead of throwing ENOENT. Owned same-version copies can be repaired. Cursor and Amp copies carry an install receipt (.agent-bundle-install.json: plugin, version, host, content hash, owned files); replacement is in place and touches owned files only, never unowned entries such as a state/ directory beside the plugin. Artifact builds keep framework state under ~/.agent-bundle/state/<plugin>-<digest> instead (AGENT_BUNDLE_STATE_ROOT overrides that location); uninstall --purge-data --confirm-purge removes only roots whose receipt proves that installation owns them. Pre-existing, shared, marker-less, and otherwise unproven override roots are retained. Claude replacement runs claude plugin uninstall --keep-data before reinstalling because plugin update is version-gated; Codex replacement is add-only (codex plugin add) so plugin settings and nested MCP overrides in config.toml survive. Native add would reset a plugin-level enabled = false to true; the native plugin CLI has no qualified settings-preserving update API, so the installer refuses disabled or unknown-enablement replacements (AB7004) and leaves those installs unchanged. The emitted INSTALL.md documents the same recipe per host.

    The framework CLI derives the artifact-side hash and copy set from agent-bundle.manifest.json files[], verifies every listed digest, and does not walk the artifact directory. The manifest itself and a conventional operator .env / .env.local overlay are also copied and hashed; an unlisted stray file is ignored. A missing or changed listed file fails as AB7001.

    Operator configuration: the installed pack's .env

    A plugin configured through environment variables, session cookies, API tokens, a host name, is configured at the install, not in the artifact: manifest env is compiled, content-addressed, and re-emitted on every build, which is the wrong place for a secret. Instead, put a .env file at the plugin root of the installed copy (the directory that holds .claude-plugin/, .codex-plugin/, or .cursor-plugin/, beside mcp/, hooks/, and bin/), optionally with a .env.local beside it:

    # ~/.cursor/plugins/local/movie-library/.env
    IPT_SESSION=…
    RTORRENT_SSH_HOST=nas.local

    Every shell the pack emits reads that file when it starts, the stdio MCP server entries, the hook wrappers that run plugin code, and the artifact CLI bin/<name>.mjs, under one rule:

    PrecedenceLayerContents
    4 (highest)Host environmentWhat the host exported. An exported variable always wins.
    3.env.localFills gaps only; beats .env for the same key.
    2.envFills gaps only.
    1 (lowest)Manifest envWhat the server's manifest env block declared, as the host merged it into the process environment.

    The same order agent-bundle mcp run composes, manifest env under the files under the operator's exports. A host hands a stdio server its manifest env and its own exports as one environment, so the generated entry carries the server's declared env block as build-time literals and treats a variable that still holds its manifest default as unset: the file may override it. A variable whose value differs from the default was exported by the host or the operator and is kept. Two consequences follow. An operator export that happens to equal the manifest default is indistinguishable from the pass-through and yields to the file too. A manifest value carrying a path token (AGENT_BUNDLE_PLUGIN_ROOT, or a declared env value that uses one) is expanded by the host before launch, so it never equals its literal default and is always kept. Hook wrappers and the artifact CLI have no manifest env, so for them the host environment is simply what the host exported.

    The location is the plugin root the pack resolved, AGENT_BUNDLE_PLUGIN_ROOT when the host set it, otherwise the directory above the shell's own (mcp/, hooks/, bin/). AGENT_BUNDLE_ENV_FILE names one or more files (platform path delimiter) to read instead, and AGENT_BUNDLE_ENV_FILE=none disables the layer. The grammar is dotenv's: KEY=value, export KEY=value, # comments, single-, double-, or backtick-quoted values (double quotes expand \n), multi-line quoted values, and no ${VAR} interpolation. A missing file is the normal case and costs nothing; an unreadable one is skipped so the pack still starts. Values are never logged. The file is not owned by the install receipt, so install --replace and a same-version rebuild leave it in place, and uninstall never removes unowned entries: the file survives an uninstall too (Doctor then reports the directory as a remnant, AB7307), so delete it by hand when the credentials should go. The layer is the first import of every shell that applies it, ahead of the server module, the hook handler, and the CLI route, provider, and state modules, so plugin code sees the composed environment even in a process.env read at module top level, not only when it runs. agent-bundle mcp run composes the same layers itself and hands --env-file / --no-env down as AGENT_BUNDLE_ENV_FILE, so a rehearsal and an installed pack read the same files.

    agent-bundle doctor reports whether an installed copy carries .env or .env.local and how many variables each declares (AB7331, informational), never a name or a value.

    Uninstalling by receipt

    # exact paths, nothing changes
    npx agent-bundle uninstall cursor --from artifact --plan
    npx agent-bundle uninstall amp --from artifact --scope user --plan
    # receipt-owned files; unowned entries and recorded state roots kept
    npx agent-bundle uninstall cursor --from artifact
    # claude plugin uninstall --keep-data + marketplace remove
    npx agent-bundle uninstall claude --from artifact
    npx agent-bundle uninstall codex --from artifact --purge-data --confirm-purge
    node artifact/install.mjs --uninstall [--mode marketplace]

    Every install writes a lifecycle receipt (agent-bundle-install-receipt/2): version, content hash, delivery mode, scope, the owned files and directories, the host directories the installer created, the host registrations it performed, and timestamps. Cursor local copies carry it as .agent-bundle-install.json; Amp project/system copies carry the same sidecar inside their generated directory; Claude, Codex, and Cursor marketplace-mode installs keep theirs under <host root>/agent-bundle/receipts/. uninstall removes exactly what the receipt owns and reverses exactly the registrations it recorded, never anything else; unowned entries are listed as retained, a state/ directory beside the plugin included. The framework state roots the receipt records with ownership evidence, derived web-data, and for a Cursor copy of an Agent Plugins pack the PLUGIN_DATA directory the receipt records are kept unless you pass --purge-data --confirm-purge, and the result states honestly what the host itself decided where Agent Bundle cannot (Claude orphans its cached copy for a ~14-day grace period; Codex deletes the cached tree and offers no keep-data option). A missing store receipt for a host-registered install (AB7009) or a content mismatch (AB7007) is refused unless --force. Even with --force, a copy without a store receipt is never purged: --purge-data --confirm-purge is refused (AB7009) because nothing proves the bundle owns its data, so uninstall without it and remove the data by hand. A Cursor directory without a receipt, or one that belongs to another plugin, is foreign and refused regardless (AB7007); a second run is a not-installed no-op. Only format 2 receipts are read; a copy carrying an older receipt is foreign. When a receipt records no state ownership, a root resolved from the current environment or home is listed as unproven and retained; --keep-data cannot turn that observation into later purge authority.

    The host-install proofs snapshot an isolated home before install and after uninstall: byte-identical for Cursor local and the portable Agent Plugins package, and zero Agent Bundle residue plus an enumerated set of host-owned bookkeeping for Claude and Codex.

    Inspect an install without changing it

    npx agent-bundle doctor --host claude --host cursor --json
    npx agent-bundle doctor --from artifact --json

    Doctor is read-only. --from is the same composite root install takes, read the same way: the plugin identity and each inspected host's plugin manifest come from agent-bundle.manifest.json (application, projections[host]), and a root the manifest does not describe for that host is AB7001. It probes hosts, inventories installed bundles, compares them against a supplied bundle, checks registration proof, samples runtime endpoint health and identity, inventories durable state and the operator .env layer (AB7331), and re-runs the pinned process-free document and loader validators over installed bytes. It never repairs anything. With --from, it reports the installed copy per host as current, stale (AB7308), version-mismatch (AB7309), foreign (AB7321), load-failed (AB7325), or not-installed (AB7307), and its lifecycle stage, placed → registered → enabled → active, with every stage either observed from the host or typed unavailable with the reason (AB7330). load-failed means Claude Code lists the copy but refused to load it, its claude plugin list --json row carries an errors array, which Doctor reports verbatim in place of current, because a refused copy contributes no hooks, MCP servers, or skills to a session. The installer reads the same array: agent-bundle install claude fails with AB7006 when the freshly installed (or byte-identical existing) copy carries errors, instead of reporting installed. Doctor also inventories the receipt store under each host root and cross-checks it against the host (AB7328).

    For Claude, Doctor also reads each row's enabled flag and runs the Claude Code developer validator. A copy listed with enabled: false (switched off with claude plugin disable or the /plugin menu) is reported as disabled in the inventory, keeps its content verdict in the --from comparison, and adds an AB7327 warning with the claude plugin enable command, a disabled plugin loads nothing, and reinstalling does not enable it. The validator (claude plugin validate --strict against plugin.json, then marketplace.json, the same runs build and validate --artifact perform) covers the --from bundle and every installed copy, and its findings keep their AB6019–AB6022 codes prefixed with Bundle at … or Installed copy at … (scope …). Doctor never runs the --plugin-dir load check twice: its registration proof and the rows' errors already hold that verdict.

    CodeSeverityMeaning
    AB7330infoThe bundle's lifecycle stage on this host and its four observations; unobservable stages (a live session's loaded plugins on every host, Cursor's server-assigned enabled state) are typed unavailable, never guessed.
    AB7328warningA store receipt records a registration the host no longer holds (orphaned), or the receipt store cannot be read; agent-bundle uninstall consumes an orphaned receipt.
    AB7316warningAn installed bundle's effective state directory is not writable, or the directory or one of its *.sqlite, -wal, or -shm files cannot be read with filesystem metadata operations. Doctor inventories state by directory entry and metadata only; it never opens a database.
    AB7318errorA live event runtime became unavailable, timed out, or returned an invalid status response during the bounded read-only identity probe.
    AB7319errorA host tree resolved from doctor --from violates its pinned document schemas or process-free loader rules; the message retains the originating build-validator code.
    AB7322info / errorInfo when an installed Cursor plugin registers plugin-scoped hooks from its manifest and every command's script exists under the plugin root; error when the declared hooks file is missing or malformed, or an executed script is absent.
    AB7323warning~/.cursor/hooks.json registers a command that points into an installed plugin directory, so Cursor would deliver that hook twice; manifest registration alone is sufficient.
    AB7324info / warning / errorA marketplace staged by install cursor --mode marketplace is imported (registered), still awaiting the Customize import step (unregistered), or incomplete (corrupt).
    AB7326info / warning / errorAn Agent Plugins install under ~/.cursor/plugins/local had its mcp.json placeholders expanded by the emitted install.mjs and every expanded path still resolves (expanded); still carries the spec forms Cursor 3.18.25 does not resolve, so its stdio servers cannot spawn (unexpanded); or was moved, duplicated, or edited after the expansion was recorded (drifted, entry corrupt). Reinstall with the bundle's install.mjs at the current location.
    AB7327warningdoctor --from compared an installed Claude copy whose claude plugin list --json row reports enabled: false; the recovery is claude plugin enable <name>@<marketplace> [--scope <scope>].
    AB7320error / infoError when a .cursor-plugin/plugin.json install violates Cursor's pinned document schemas or token-location rules (the hooks document checked is the one the manifest hooks field names, .cursor-plugin/hooks.json, so Claude Code's hooks/hooks.json in the same root is not a finding), or when a local plugin contains a symlink escaping ~/.cursor/plugins/local; the inventory entry is reported as corrupt. Info when a .claude-plugin/plugin.json or root plugin.json install has no Cursor-side pinned static document contract, and the loader-recognized entry stays installed.

    Next