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
12 changes: 12 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "cef",
"owner": { "name": "Cere / CEF-AI" },
"description": "Tooling for authoring CEF agents",
"plugins": [
{
"name": "cef",
"source": "./plugins/cef",
"description": "Author, test, build, push, and deploy CEF agents"
}
]
}
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,28 @@
# cef-plugins
# cef-plugins

Claude Code plugin marketplace for **CEF agent development**.

## Install

```
/plugin marketplace add CEF-AI/cef-plugins
/plugin install cef@cef
```

Projects scaffolded with `cef init` auto-wire this plugin via
`.claude/settings.json` — trust the folder and Claude Code installs and keeps
it updated.

## Plugins

### `cef`

Author, test, build, push, and deploy CEF agents. Skills:

- **`cef:develop`** — author, extend, and debug agents (engagements, cubbies,
migrations, widgets, tests). Loads automatically when you work on agent code.
- **`cef:deploy`** — the `build → push → publish → deploy` workflow; confirms
before outward-facing steps and guides the human-only ROC / credentials
actions.

Depth lives in each skill's `references/`.
7 changes: 7 additions & 0 deletions plugins/cef/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "cef",
"description": "Author, test, build, push, and deploy CEF agents",
"version": "0.1.0",
"author": { "name": "Cere / CEF-AI" },
"homepage": "https://github.com/CEF-AI/cef-plugins"
}
127 changes: 127 additions & 0 deletions plugins/cef/skills/deploy/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
name: deploy
description: Build, push, and deploy a CEF agent so it runs live (including in a sandbox); marketplace publish is optional and last. Use when shipping/releasing an agent or when the user asks to build/push/deploy/publish.
---

# Deploy a CEF agent

Ship an agent through this order: **`cef init` → `cef build` → `cef push` → `cef deploy`**.

**`cef deploy` is what makes an agent live and testable** (including in a
sandbox). **`cef publish` — the marketplace listing — is OPTIONAL and the LAST
step.** Publish only places the agent's card in the marketplace for discovery;
skip it entirely for debug, test, or sandbox use. An agent runs because it is
deployed, not because it is published.

In a scaffolded project run `cef` via `npx cef …` (it's a local dev-dep).

```bash
npx cef init # scaffold a project (ships a deployments/ folder)
npx cef build # local: compile dist/<id>/{bundle.js,manifest.json}
npx cef push --bucket <id> --as-pubkey <hex> # OUTWARD: upload bundle to the DDC registry
npx cef deploy --endpoint <url> --as-pubkey <hex> # OUTWARD: apply deployments/ — makes the agent live
npx cef publish --keystore <path> --as-pubkey <hex> # OPTIONAL, LAST: list the card in the marketplace
```

## Non-negotiable rules

- **`push`, `deploy`, and `publish` are outward-facing.** Never run any of them
without confirming with the user first (see checklist below). `build` and
`init` are local and safe — run them freely.
- **Two setup steps remain human-only, done in the ROC console:** creating the
Agent Service (which yields the AS pubkey) and minting a DDC registry access
token. Hand those back as steps; never fake them. The ROC **Deploy** button
does the same thing as `cef deploy`.
- **Never invent flags or commands.** Everything here is grounded in the
references.

## The `deployments/` folder is the source of truth

An agent project declares its deployment records as files — one record per
`deployments/<name>.jsonc`, the filename stem being the record `name` (the same
convention as `migrations/<cubby>/` and `widgets/<id>/`). `cef deploy` reads the
whole folder, assembles the deployment SET, and applies it.

A record's key fields: `priority` (int, REQUIRED, **lower wins**), `targeting`
(string, REQUIRED — a CEL expression over `vault.id` / `vault.scope` /
`event.type`; `""` means always eligible), `version` (REQUIRED — a semver or the
literal `"latest"`), and `weight` (int ≥ 1, default 1, splits traffic within a
priority tier for A/B). Full field list and SET rules:
[references/roc-deploy.md](./references/roc-deploy.md).

The folder must contain **exactly one** record with empty `targeting` — the
mandatory default/fallthrough. Give it a HIGH priority number (e.g. 99) so more
specific, lower-priority records win. `version: "latest"` fails closed if
`latest` was never pushed — pin a semver for anything that must run.

## `cef deploy` — the command surface

```bash
npx cef deploy [--version <v>] [--endpoint <url>] \
[--as-pubkey <hex>] [--author <who>] [--note <msg>] [--dry-run]
```

- Reads `deployments/`; **errors if the folder is absent or empty**.
- `--endpoint` (or `$CEF_ENDPOINT`) selects WHERE to apply, defaulting to the
dev environment. It is **orthogonal** to the files: the same `deployments/`
folder applies to any environment, so never put environment names inside the
files.
- `--version` overrides the record `version` without editing files — the
everyday "pushed a new build, roll it out" step.
- `--deployments <path>` points at the records: a directory (default
`deployments/`, one record per file) or a single set/record file — like
`kubectl apply -f <file|dir>`.
- The whole set is applied at once (declarative desired state) — records removed
locally disappear remotely on the next deploy.
- `--dry-run` shows what would be applied without applying it.

## Before push / deploy / publish — confirm identity, bucket, target

Surface these to the user and get an explicit go-ahead before any outward call:

1. **AS pubkey** (`--as-pubkey <hex>`) — the Agent Service routing identity from
ROC. Same value on push, deploy, and publish. A wrong pubkey means the agent
is unroutable (`connect` rejects on an agentId prefix mismatch).
2. **Bucket** (`--bucket <numericId>`, push only) — the AS's DDC registry bucket,
in decimal.
3. **DDC auth** (push only) — exactly one of `--access-token` /
`$CEF_DDC_ACCESS_TOKEN` (ROC-minted, the common team case) or
`--secret-phrase` / `$CEF_DDC_SECRET_PHRASE` (bucket owner). Both = error;
neither = error.
4. **Endpoint** (deploy) — echo the resolved `--endpoint` / `$CEF_ENDPOINT` so
the user knows which environment goes live.
5. **AS keypair** (publish) — resolved via `--privkey/--pubkey`, `CEF_AS_*` env,
`--keystore`, or a `~/.config/cef/credentials` profile.

Echo the resolved AS pubkey, bucket, endpoint, and (for publish) marketplace
URL, then ask to proceed. See
[references/credentials.md](./references/credentials.md) for the two-identity
model and resolution order.

## Detect the missing prerequisite → hand back the exact next step

Diagnose *which* gate is unmet and return the one precise command or ROC click.
The CLI's own error strings name the gate — surface them verbatim, then act.

| Symptom / state | Missing | Hand back |
|---|---|---|
| No AS keypair anywhere (`CEF_AS_*`, keystore, credentials profile) | Publisher identity | `cef keypair generate --out ~/.cef/my-agent.key` (once; share via a secrets manager, not per-dev) |
| No AS pubkey to pass `--as-pubkey` | ROC setup step 1 | "Open ROC → your Agent Service → copy the AS pubkey (hex)" |
| `push`: "no DDC authorization" | ROC setup step 2 | "ROC → Agent Service → Access → generate token; `export CEF_DDC_ACCESS_TOKEN=…`" (note the `bucketId`) |
| `push`: "both … provided" | Ambiguous auth | Unset one of `$CEF_DDC_SECRET_PHRASE` / `$CEF_DDC_ACCESS_TOKEN` |
| `push`: bundle/version not found | Build not run | `cef build` first |
| `push`: missing `@cere-ddc-sdk/ddc-client` | Optional dep | `pnpm add @cere-ddc-sdk/ddc-client` |
| `deploy`: "no deployments/ folder" / empty folder | No records | Create `deployments/default.jsonc` (see reference), then re-run |
| Pushed, but a user's `connect` never gets served | Not deployed | `npx cef deploy --endpoint <url> --as-pubkey <hex>` (or ROC → the agent → **Deploy**) |
| `connect` rejected (agentId prefix mismatch) | Wrong AS pubkey | Re-push/deploy with the **provisioned** AS pubkey |
| Card missing from the marketplace (agent still runs) | Not published | `npx cef publish …` — optional, only for discovery |

Rule of thumb: don't guess past the current gate — resolve it, then re-run.

## Shipping a new version

Bump `version` in `cef.config.ts`, then re-run build → push → `cef deploy`
(pass `--version <semver>` to roll the new build out without editing files, or
deploy `latest`). Add a lower-priority or weighted record in `deployments/` for
a controlled rollout. Re-run `cef publish` only if the marketplace card needs
refreshing — it is not required for the new version to run.
135 changes: 135 additions & 0 deletions plugins/cef/skills/deploy/references/credentials.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Credentials, keypair & DDC bucket

Two independent identities are involved in shipping an agent, and they are
easy to confuse:

| Command | Identity | Key type | Where it goes |
| --- | --- | --- | --- |
| `cef push` | **DDC bucket owner** (or a token from them) | Sr25519 (substrate) | authorizes writes to the content-addressed bucket |
| `cef publish` | **Agent Service (AS)** | Ed25519 | signs the marketplace publish envelope |

They are unrelated keys. `cef push` signs DDC writes with the bucket-owner
key, which "can be anything with write access" — so the AS identity **cannot
be derived** from it and must be passed in explicitly with `--as-pubkey`.

## AS Ed25519 keypair (`cef publish`, and `--as-pubkey` on push)

The AS keypair is the **Agent Service identity** — the marketplace-visible
publisher of the agent — and is **typically shared across a team**, not
unique per developer. The CLI never auto-generates it; you provision it
once with `cef keypair generate` and share it through your secrets channel.
Generating a fresh one per developer fragments the agent's marketplace
history under different identities.

### Generate a keypair

`cef keypair generate` is the only sanctioned path to a new AS keypair:

```bash
# JSON to stdout — pipe to a secrets manager
cef keypair generate

# Write a 0600 file (refuses an existing file unless --force)
cef keypair generate --out keypair.json --force

# Merge into the shared credentials file under [staging]
# (atomic 0600 write; refuses an existing section unless --force)
cef keypair generate --profile staging
```

Keypair shape: `pubkey` is 64 lowercase hex chars (32 bytes); the private
key is a 32-byte Ed25519 **seed**, also 64 hex chars.

### Resolution order

`cef publish` resolves the AS keypair AWS-CLI-style — flags beat env, env
beats files, files beat the shared INI. The **first** source that yields a
complete keypair wins:

| # | Source | How |
| --- | --- | --- |
| 1 | CLI flags | `--privkey <hex> --pubkey <hex>` |
| 2 | Env vars | `CEF_AS_PRIVATE_KEY=<hex> CEF_AS_PUBLIC_KEY=<hex>` |
| 3 | Keystore via flag | `--keystore <path>` — JSON `{ pubkey, privkey }` |
| 4 | Keystore via env | `CEF_AS_KEYSTORE=<path>` — same shape |
| 5 | Profile in shared file | `~/.config/cef/credentials`, section via `--profile <name>` / `CEF_PROFILE`, default `default` |

Gotchas:

- **Pairs must be complete.** Setting only one of `--privkey`/`--pubkey`
(or only one of the two env vars) is a hard error, not a fall-through.
- **A named path or profile must exist.** `--keystore <path>`,
`CEF_AS_KEYSTORE`, and an explicit `--profile <name>` all error if the
file/section is missing — the miss is not silently skipped (that would
mask a typo). Only a *default* profile with no file present counts as
"no credentials".
- With no source at all, the CLI prints multi-line guidance listing every
source and pointing at `cef keypair generate`, then exits non-zero.

### Shared credentials file

Lives at `~/.config/cef/credentials` (or `$XDG_CONFIG_HOME/cef/credentials`),
mode `0600`, minimal INI. Multiple profiles hold identities for several
environments on one workstation:

```ini
[default]
pubkey = abc...
privkey = abc...

[staging]
pubkey = def...
privkey = def...
```

## DDC bucket + credentials (`cef push`)

`cef push` uploads `dist/<agentId>/bundle.js` to DDC as a content-addressed
DAG and rewrites `manifest.json` so `bundle` is `{ bucketId, cid }` instead
of embedded inline. It requires:

1. **`--bucket <numericId>`** (REQUIRED) — decimal DDC bucket id. The
substrate signer must own it. The bucket *is* the agent registry
(`CNS "agents" → directory → per-agent record → per-version node`).
2. **Exactly one** DDC write authorization:
- `--secret-phrase <phrase>` / `$CEF_DDC_SECRET_PHRASE` — the Sr25519
phrase that **owns** the bucket.
- `--access-token <token>` / `$CEF_DDC_ACCESS_TOKEN` — a base58 token
**granted by the bucket owner** (e.g. minted in ROC by the wallet that
owns the AS's bucket). Use this when you don't hold the bucket's seed;
the client is built with a throwaway signer and the token carries the
authority. Providing **both** phrase and token is an error; providing
**neither** is an error.
3. **`--as-pubkey <hex>`** — the AS pubkey (exactly as shown in ROC).
Because push's signing key is unrelated to the AS identity, this is how
the manifest gets stamped: `agentServicePubkey = <asPubkey>` and
`agentId = <asPubkey>:<alias>`. With it stamped, bucket consumers (the
dashboard registry read, the `connect` check) see a fully-identified
manifest immediately — no need to wait for `cef publish`.

```bash
# Owner pushes with the bucket's secret phrase
cef push --bucket 1234 \
--secret-phrase "$CEF_DDC_SECRET_PHRASE" \
--as-pubkey <hex>

# Teammate pushes with a ROC-minted access token (no bucket seed)
cef push --bucket 1234 \
--access-token "$CEF_DDC_ACCESS_TOKEN" \
--as-pubkey <hex> --preset TESTNET
```

Other push flags: `--preset MAINNET|TESTNET|DEVNET` (default MAINNET),
`--endpoint <url>` (overrides preset), `--agent <id>` (when `dist/` holds
more than one), `--out <dir>` (default `dist`).

Gotchas:

- Run `cef build` first — push errors if `dist/<id>/{bundle.js,manifest.json}`
or the manifest `version` is missing.
- `cef push` needs the optional dep `@cere-ddc-sdk/ddc-client`; install it
(`pnpm add @cere-ddc-sdk/ddc-client`) or push throws an actionable error.
- `bucketId` is recorded in the manifest as a **decimal string**.
- On a bucket's first push the `agents` CNS root doesn't exist yet; push
logs that it's creating the registry root (the internal resolve miss is
expected, not a failure). Set `CEF_DEBUG` for full ddc-client logs.
Loading
Loading