Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/host-install-distribution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"agent-bundle": minor
---

Emit evidence-backed install instructions for every target, add safe Cursor
placement and public Claude/Codex CLI delegation through
`agent-bundle install`, and require install surfaces during artifact
validation.
23 changes: 23 additions & 0 deletions docs/framework-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,26 @@ results and never renders JSX. Routed `src/cli/**` commands and
`src/scripts/**` scripts follow one sentence: `.tsx` renders through the
Agent renderer (TTY progress, piped Markdown, `--json`, `--ndjson`); `.ts`
is plain.

## Distribution

`agent-bundle build` makes each target directory independently distributable.
Every target includes `INSTALL.md` generated with its real plugin and
marketplace names. Claude and Codex bundles include local marketplace manifests
and install through their public plugin CLIs; Cursor bundles use the documented
`~/.cursor/plugins/local/<name>` location because Cursor exposes marketplace
management but no non-interactive plugin install verb.

The framework CLI performs those same operations:

```sh
agent-bundle install claude --from artifact/claude --scope user
agent-bundle install codex --from artifact/codex
agent-bundle install cursor --from artifact/cursor
```

Cursor-compatible `cursor`, `portable`, and multi-host `plugin` targets also
include a standalone `install.mjs`. Its staged copy is idempotent for identical
content and refuses version or content collisions. It never invokes sudo or
changes PATH. Artifact validation rejects a built-in target whose required
install surface is missing.
103 changes: 103 additions & 0 deletions docs/superpowers/plans/2026-09-01-host-install-distribution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Host Install and Distribution Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Emit and validate exact per-host install surfaces and add a safe `agent-bundle install` command.

**Architecture:** Pinned target capability tables own host install facts. A
focused install module renders artifact files and implements Effect-native
host delegation/direct placement behind the existing Promise CLI boundary.
Adapters and artifact validation consume the same immutable contracts.

**Tech Stack:** TypeScript 7, Node.js 22, Effect 4 RC, Commander, Rstest.

## Global Constraints

- Claude and Codex installation delegates to their public CLIs without a shell.
- Cursor placement never uses sudo, changes PATH, or overwrites a collision.
- Cursor copy is staged, atomic, symlink-free, and idempotent.
- Every built-in target emits `INSTALL.md`; only Cursor-compatible fallback profiles require `install.mjs`.
- New orchestration is Effect-native and crosses through `src/effect/boundary.ts`.

---

### Task 1: Pin host install contracts

**Files:**
- Modify: `packages/agent-bundle/src/adapters/capabilities/*.json`
- Modify: `packages/agent-bundle/src/adapters/{claude,codex,cursor,portable,plugin}.ts`
- Test: `packages/agent-bundle/tests/adapter-capability-states.test.ts`

**Interfaces:**
- Produces: adapter capability `install` with evidence or an unavailable reason.

- [ ] Write assertions for the five target install states and exact public commands.
- [ ] Run the focused adapter test and verify it fails because `install` is absent.
- [ ] Add pinned `install` table rows, update capability hashes/revisions, and expose the states.
- [ ] Re-run the focused adapter test.

### Task 2: Emit deterministic install surfaces

**Files:**
- Create: `packages/agent-bundle/src/install/contracts.ts`
- Create: `packages/agent-bundle/src/install/surface.ts`
- Modify: `packages/agent-bundle/src/adapters/types.ts`
- Modify: `packages/agent-bundle/src/adapters/{claude,codex,cursor,portable,plugin}.ts`
- Test: `packages/agent-bundle/tests/install-surface.test.ts`

**Interfaces:**
- Produces: `installSurfaceEntries(target, model, contract): readonly TargetArtifactEntry[]`.

- [ ] Test exact `INSTALL.md`, real names, marketplace availability, and fallback script inclusion for all targets.
- [ ] Run the test and verify the install files are missing.
- [ ] Implement immutable contract snapshots and deterministic Markdown/script rendering.
- [ ] Always emit Claude/Codex local marketplaces and append install entries to each built-in plan.
- [ ] Re-run the install-surface test.

### Task 3: Implement native host installation

**Files:**
- Create: `packages/agent-bundle/src/install/install.ts`
- Modify: `packages/agent-bundle/src/api.ts`
- Modify: `packages/agent-bundle/src/cli.ts`
- Test: `packages/agent-bundle/tests/install.test.ts`
- Test: `packages/agent-bundle/tests/cli.test.ts`

**Interfaces:**
- Produces: `installBundle(options): Promise<InstallResult>`.
- Consumes: a direct bundle root or an artifact root containing a target root.

- [ ] Test Claude/Codex argv delegation, unsupported scopes, missing binaries, direct/artifact roots, Cursor copy/idempotency, unsafe entries, and collisions.
- [ ] Run the focused tests and verify missing API/command failures.
- [ ] Implement Effect orchestration with injected command runner and filesystem/home dependencies.
- [ ] Add the lazy-loaded Commander command and human/JSON output.
- [ ] Re-run focused install and CLI tests.

### Task 4: Enforce artifact install surfaces

**Files:**
- Modify: `packages/agent-bundle/src/build/artifact-diagnostics.ts`
- Modify: `packages/agent-bundle/src/build/validate-artifact.ts`
- Test: `packages/agent-bundle/tests/artifact-validator.test.ts`

**Interfaces:**
- Consumes: manifest target names and immutable install requirements.
- Produces: stable diagnostics for missing or invalid install files.

- [ ] Test missing `INSTALL.md`, missing required fallback script, and valid non-fallback targets.
- [ ] Run the focused validator test and verify it accepts the broken fixtures.
- [ ] Validate required names and reject non-regular install surface entries through existing ownership checks.
- [ ] Re-run the focused validator test.

### Task 5: Document, verify, and land

**Files:**
- Modify: `packages/agent-bundle/README.md`
- Modify: `docs/framework-mode.md`
- Create: `.changeset/<generated-name>.md`

- [ ] Document target distribution and `agent-bundle install`.
- [ ] Add a minor `agent-bundle` changeset.
- [ ] Run scoped tests, package build, typecheck, and lint.
- [ ] Rebase on the latest `origin/main`, resolve only additive conflicts, rerun verification, and commit.
- [ ] Push, open the PR, comment the design on issue #100, merge when checks are green, and report the merge SHA.
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Host Install and Distribution Design

## Status

Approved by the explicit implementation requirements in the host-install story.

## Goal

Every emitted target bundle explains an exact, evidence-backed installation
path. Claude and Codex use their public marketplace and install commands.
Cursor, which has no non-interactive plugin install command, uses a
framework-owned, safe local-plugin copy. Portable and composite bundles explain
which real hosts can consume them.

## Host contract

Pinned capability tables record an `install` section beside each host's plugin
contract:

- Claude: `claude plugin marketplace add .`, then
`claude plugin install <plugin>@<marketplace> --scope <scope>`.
- Codex: `codex plugin marketplace add .`, then
`codex plugin add <plugin>@<marketplace>`.
- Cursor: no shell install verb; copy a complete plugin to
`~/.cursor/plugins/local/<plugin>`, then reload Cursor.
- Portable: no runtime or universal install location. The emitted Agent Plugin
can be installed into a compatible host, including Cursor.
- Plugin: a multi-host distribution profile. Its document contains the exact
Claude, Codex, and Cursor procedures.

The Claude and Codex target plans always emit their local marketplace
documents, because those documents are required for the public commands to work
against a built directory.

## Emitted surface

Every target root contains `INSTALL.md`. Commands use `.` and the real compiled
plugin and marketplace names, so a user runs them from that target root without
editing placeholders.

Cursor-compatible target roots (`cursor`, `portable`, and `plugin`) also contain
`install.mjs`. The script:

- resolves the user install root from `HOME`;
- copies through a sibling staging directory and atomically renames it;
- never invokes sudo or edits PATH;
- treats a byte-identical existing tree as an idempotent success;
- refuses an existing different version or different content;
- rejects symlinks and other unsupported filesystem entries in either tree;
- prints the installed or already-installed destination.

Both files are part of the artifact manifest and provenance table. Artifact
validation requires `INSTALL.md` for all five built-in targets and
`install.mjs` only for Cursor-compatible fallback targets.

## Built-in installer

`agent-bundle install <host> [--from <bundle-dir>] [--scope <scope>]` accepts
the real destination hosts `claude`, `codex`, and `cursor`.

- Claude and Codex validate the bundle's marketplace and plugin identity, check
that the host executable exists, then execute the public CLI sequence without
a shell.
- Cursor validates a Cursor Plugin manifest and performs the same safe copy as
`install.mjs`; portable bundles use their emitted installer directly.
- `--from` accepts either a direct target root or an artifact root containing a
matching target directory.
- Claude accepts `user`, `project`, and `local`; Codex and Cursor reject scopes
their public contracts do not support.

Missing binaries, unsupported hosts/scopes, malformed bundles, unsafe trees,
and destination collisions fail as typed `DiagnosticError` diagnostics. Tests
inject a command runner and temporary home, so no real host binary is required.

## Verification

Unit tests cover exact generated documents and scripts for all targets, public
CLI argument delegation, missing-host diagnostics, Cursor copy/idempotency and
collision behavior, and artifact validation when an install surface is absent.
The landing bar is scoped tests, package build/typecheck, and lint.
30 changes: 30 additions & 0 deletions packages/agent-bundle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ manifests at files inside those payloads without compiling them. Payload files c
| Command | Purpose |
| --- | --- |
| `agent-bundle build` | Build a validated artifact from source, plus the declared `dist/` package build. |
| `agent-bundle install <host>` | Install a built bundle into Claude, Codex, or Cursor (`--from`, `--scope`, and `--json` supported). |
| `agent-bundle validate` | Validate project source, or an artifact with `--artifact`. |
| `agent-bundle inspect` | Inspect normalized targets and adapter plans from source. |
| `agent-bundle inspect --bundler` | Dump the synthesized Rslib/Rsbuild configs (post-`tools`-hatch merge) for every generated output. |
Expand Down Expand Up @@ -98,6 +99,35 @@ During development, load a built target without installing it and verify registr
claude --plugin-dir dist/claude plugin list --json
```

## Distribute and install bundles

Every built target directory contains a generated `INSTALL.md` with commands
that use the bundle's real plugin and marketplace names. Claude and Codex
targets always include local marketplace manifests, so their public CLIs can
install the emitted directory directly:

```sh
agent-bundle install claude --from artifact/claude --scope user
agent-bundle install codex --from artifact/codex
```

The installer delegates to `claude plugin marketplace add` /
`claude plugin install` and `codex plugin marketplace add` /
`codex plugin add`; it fails with a typed diagnostic when the selected host
binary is unavailable. Cursor has no non-interactive install verb, so Cursor,
portable, and composite targets include `install.mjs`, which safely copies the
bundle into `~/.cursor/plugins/local/<name>` without overwriting collisions:

```sh
agent-bundle install cursor --from artifact/cursor
# or, from the emitted target directory:
node ./install.mjs
```

Cursor installation is user-scoped. Claude also accepts `--scope project` and
`--scope local`; Codex is user-scoped. A source-free artifact root is accepted
by `--from` when it contains the selected host target directory.

## Developer workbench

`agent-bundle dev` serves a loopback-only prebuilt workbench. It shows project
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
{
"host": "claude",
"install": {
"evidence": [
"Local marketplaces accept a directory containing .claude-plugin/marketplace.json.",
"claude plugin install accepts plugin@marketplace and user, project, or local scope."
],
"marketplaceAdd": "claude plugin marketplace add .",
"pluginInstall": "claude plugin install <plugin>@<marketplace> --scope <scope>",
"scopes": ["user", "project", "local"],
"source": "https://code.claude.com/docs/en/discover-plugins",
"state": "supported"
},
"hooks": {
"config": "hooks/hooks.json",
"events": {
Expand Down
11 changes: 11 additions & 0 deletions packages/agent-bundle/src/adapters/capabilities/codex-0.147.0.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
{
"host": "codex",
"install": {
"evidence": [
"codex plugin marketplace add accepts a local marketplace root.",
"codex plugin add installs plugin@marketplace from a configured snapshot."
],
"marketplaceAdd": "codex plugin marketplace add .",
"pluginInstall": "codex plugin add <plugin>@<marketplace>",
"scopes": ["user"],
"source": "https://developers.openai.com/codex/cli/reference",
"state": "supported"
},
"hooks": {
"config": "hooks/hooks.json",
"events": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
{
"host": "cursor",
"install": {
"cliInstall": false,
"evidence": [
"Cursor CLI exposes plugin marketplace management but no non-interactive plugin install verb.",
"Cursor documents physical local plugin copies under ~/.cursor/plugins/local/<name>."
],
"localRoot": "~/.cursor/plugins/local/<name>",
"method": "copy",
"scopes": ["user"],
"source": "https://cursor.com/docs/plugins",
"state": "supported"
},
"hooks": {
"config": "hooks/hooks.json",
"events": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
"install": {
"reason": "Portable is a distribution profile, not a host runtime with one universal plugin installation location.",
"source": "https://agent-plugins.org/",
"state": "unavailable"
},
"eventRoutes": {
"agent/start": { "reason": "Agent Plugins 1.0.0 does not define hooks.", "state": "unavailable" },
"agent/stop": { "reason": "Agent Plugins 1.0.0 does not define hooks.", "state": "unavailable" },
Expand Down
16 changes: 8 additions & 8 deletions packages/agent-bundle/src/adapters/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
type TargetArtifactLayout,
type TargetArtifactPlan,
} from './types.ts';
import { withInstallSurface } from '../install/surface.ts';

/**
* One Claude Code plugin LSP server. The binary is never vendored: Claude
Expand Down Expand Up @@ -139,7 +140,7 @@ const hookContract = Object.freeze({
const metadata = Object.freeze({
adapterRevision: '1.4.0',
capabilityRevision: capabilityTable.observedCliVersion,
capabilitySha256: '6b8a3b222b49c0ad22f32ecdf8157bd353ce5be05d56e40ae5cf4ad2b9eb917f',
capabilitySha256: '58141a999ac3d39d9b7aa2bc6bb945aae145773ea6f37806eecc93d3b5c7ed38',
observedVersion: capabilityTable.observedCliVersion,
schemas: schemaDescriptorsFrom(schemaProvenance, schemaProvenance.observedCliVersion),
});
Expand Down Expand Up @@ -471,7 +472,7 @@ export const planClaudeArtifacts = (
};
diagnostics.push(...schemaDiagnostics('plugin', validatePlugin(plugin), validatePlugin.errors));

const marketplace = model.marketplace !== true ? undefined : {
const marketplace = {
description: model.metadata.description ?? model.metadata.name,
name: `${model.metadata.name}-marketplace`,
owner: { name: model.metadata.name },
Expand All @@ -482,10 +483,8 @@ export const planClaudeArtifacts = (
version: model.metadata.version,
}],
};
const marketplaceValid = marketplace !== undefined && validateMarketplace(marketplace);
if (marketplace !== undefined) {
diagnostics.push(...schemaDiagnostics('marketplace', marketplaceValid, validateMarketplace.errors));
}
const marketplaceValid = validateMarketplace(marketplace);
diagnostics.push(...schemaDiagnostics('marketplace', marketplaceValid, validateMarketplace.errors));

const basePlan = standardPluginArtifactPlan({
diagnostics,
Expand All @@ -511,13 +510,13 @@ export const planClaudeArtifacts = (
pluginRelativePath: claudeArtifactPaths.plugin,
targetName,
});
return Object.freeze({
return withInstallSurface(Object.freeze({
...basePlan,
entries: sortedEntries([
...basePlan.entries,
...commandWriteEntries(model, isSelected, claudeCommandMarkdown),
]),
});
}), model, targetName === 'plugin' ? 'plugin' : 'claude');
};

const artifactLayout: TargetArtifactLayout = Object.freeze({
Expand All @@ -538,6 +537,7 @@ export const claudeAdapter: TargetAdapter = Object.freeze({
evidence,
'The pinned Claude Code plugin contract does not support commands.',
),
install: supportedCapability(evidence),
marketplace: supportedCapability(evidence),
hooks: supportedCapability(evidence),
lsp: capabilityStateFromSupport(
Expand Down
Loading
Loading