---
kind: knowledge
when-and-why-to-read: When creating a crtr plugin, packaging memory docs for
  distribution, adding top-level CLI commands via a command manifest, or
  debugging install/resolution, this knowledge should be read so installs resolve
  predictably across scopes and command surfaces do not fail from manifest drift or protocol mistakes.
short-form: How to author a crtr plugin — plugin.json manifest, directory
  layout, scopes, install mechanics, versioning, and command plugins (top-level
  CLI commands via commands.json + one executable). Use when creating a plugin,
  packaging memory docs, contributing commands, or debugging install/resolution.
system-prompt-visibility: name
file-read-visibility: none
---

# Authoring crtr plugins

A **plugin** is a directory shipping substrate docs (knowledge and preferences) and other artifact types like `rules/` and `agents/`. Plugins are how you package that content for sharing across machines, projects, and people.

Audience: LLM agents creating or maintaining a crtr plugin.

## When you need a plugin (vs scope-owned memory docs)

Scope-owned docs live at `~/.crouter/memory/` (user) or `<project>/.crouter/memory/` (project). They're personal and per-machine/per-repo.

Reach for a **plugin** when:
- You want to share memory docs across multiple projects or with other people.
- You want versioning + update mechanics (`crtr pkg plugin update --name <name>`).
- You want a marketplace to index the work — see [[internal/marketplaces]].

If it's a one-off note for yourself, scope-owned memory docs are simpler. Promote to a plugin later.

## Directory layout

```
<plugin-name>/
├── .crouter-plugin/
│   └── plugin.json                  # manifest — required
└── memory/
    ├── <name>.md                    # a kind:knowledge or kind:preference doc
    └── <area>/
        ├── INDEX.md                 # optional — a dir surfaces as one entry at its INDEX rung
        └── <name>.md
```

The `<plugin-name>` directory IS the plugin. The manifest's `name` field must match the directory name (install renames if needed). Sibling dirs (`rules/`, `agents/`, and future `hooks/`) hold the other artifact types. A plugin that contributes CLI commands adds a `commands.json` manifest plus its executable — see [Command plugins](#command-plugins).

## The manifest

`.crouter-plugin/plugin.json`:

```json
{
  "name": "my-plugin",
  "version": "0.1.0",
  "description": "One sentence — shown in `crtr pkg plugin list`.",
  "source": "https://github.com/<owner>/<repo>",
  "owner": {
    "name": "Your Name",
    "email": "you@example.com"
  }
}
```

| Field | Required | Notes |
|---|---|---|
| `name` | yes | Must match the directory name. Lowercase kebab. |
| `version` | yes | Semver. Marketplace CI may bump automatically — see marketplaces knowledge. |
| `description` | yes | One sentence. |
| `source` | recommended | Git URL where the plugin lives. Used by `crtr pkg plugin update --name <name>`. |
| `owner` | optional | Author info. |
| `commands` | optional | Plugin-root-relative path to a `commands.json` command manifest. Present ⇒ the plugin contributes top-level `crtr` commands — see [Command plugins](#command-plugins). |

## Scopes

A plugin can live in either scope:

| Scope | Path | Use case |
|---|---|---|
| user | `~/.crouter/plugins/<name>/` | Personal, available in all your projects |
| project | `<project>/.crouter/plugins/<name>/` | Pinned to a specific repo — checked in or vendored |

Project-scope plugins outrank user-scope on resolution. Both outrank marketplace-installed plugins. The builtin `crtr` plugin (ships with the CLI) sits at the bottom.

## Install mechanics

Three ways a plugin lands in a scope:

1. **From a git URL** (`crtr pkg plugin install <url> --scope user`):
   - Clones into `<scope>/plugins/<name>/` using the manifest's name.
   - `crtr pkg plugin update --name <name>` does `git pull`.
   - Independent of any marketplace.

2. **From a marketplace** (`crtr pkg plugin install <mkt>/<name>`):
   - **Symlinks** the marketplace's `plugins/<name>/` into `<scope>/plugins/<name>/`.
   - `crtr pkg market update --name <mkt>` pulls updates for every installed plugin from that marketplace.
   - See [[internal/marketplaces]].

3. **Authored in place** (you're writing the plugin in a working repo):
   - Symlink for tight dev loop: `ln -s $(pwd) ~/.crouter/plugins/<name>`.
   - Or `crtr pkg plugin install file://$(pwd) --scope project` to clone-install.

## Local development loop

```bash
# Scaffold dir + manifest + first doc
mkdir -p my-plugin/.crouter-plugin my-plugin/memory
$EDITOR my-plugin/.crouter-plugin/plugin.json      # write the manifest
cd my-plugin
$EDITOR my-plugin/memory/my-first-doc.md           # author the doc — `crtr memory write -h` is the frontmatter + routing guide

# Symlink for fast iteration — no clone, edits land immediately
ln -s $(pwd) ~/.crouter/plugins/my-plugin

# Verify
crtr pkg plugin list                       # my-plugin appears
crtr pkg plugin show my-plugin             # lists its docs
crtr memory read my-plugin/my-first-doc            # resolve it under the plugin namespace
crtr sys doctor                                    # validates the manifest
crtr memory lint                                    # validates doc frontmatter
```

When ready to share: push to a git remote; anyone can `crtr pkg plugin install <url> --scope user`.

## Versioning

Standard semver:

| Change | Bump |
|---|---|
| Typo, wording polish | patch (0.1.0 → 0.1.1) |
| New doc, new section, new example | minor (0.1.0 → 0.2.0) |
| Removed doc, renamed doc, changed manifest schema | major (0.1.0 → 1.0.0) |

`crtr pkg plugin update --name <name>` reads the new version after pulling and updates the local config. Plugins published through a marketplace may have their `version` field bumped automatically by CI — see [[internal/marketplaces]].

## Enable/disable

`crtr pkg plugin disable <name>` flips the per-scope config without removing files. Disabled plugins are hidden from `crtr memory list` and don't resolve via `crtr memory read <name>`. Re-enable with `crtr pkg plugin enable <name>`.

Individual memory docs inside an enabled plugin are hidden by setting their frontmatter visibility rungs to `none` (or a gate that fails), not by a command — see `crtr memory write -h`.

## What goes in a plugin

Good plugin scope:
- A coherent set of related memory docs (3–15 typical) sharing a theme.
- All docs serve the same user persona or workflow.
- Versioned together — a bump means a bump for the whole set.

Bad plugin scope:
- One mega-plugin with every doc you've ever written. Hard to install selectively, hard to version.
- A plugin per single doc. No value-add over scope-owned memory docs.

## Cross-plugin etiquette

If your memory doc conceptually depends on another plugin's doc, link via `## Related` with `` `<plugin>/<doc>` ``. Don't fork content; link it.

## Command plugins

Beyond docs, a plugin may contribute **top-level `crtr` commands** — new noun branches with their own leaves. You declare one pointer in the manifest and ship one executable; crtr owns parsing, help, rendering, and errors, and direct-spawns your executable once per leaf invocation.

### The manifest pointer

Add `commands` to `plugin.json` — a plugin-root-relative path to one static manifest:

```json
{ "name": "deploy-tools", "version": "0.1.0", "description": "...", "commands": "commands.json" }
```

Only an installed, **enabled** plugin's `commands` manifest contributes. Discovery is per-invocation: enable/disable/update/remove takes effect on the very next `crtr` call — no daemon restart, no cache to clear.

### commands.json shape

```json
{
  "schemaVersion": 1,
  "executable": "bin/cmd.js",
  "mounts": [
    { "parent": [], "node": { "kind": "branch", "name": "app", "...": "..." } }
  ]
}
```

- `schemaVersion` — exactly the integer `1`. Anything else rejects the whole manifest.
- `executable` — plugin-root-relative path to the one command binary. Must resolve inside the plugin root, be a regular file, and carry the POSIX exec bit.
- `mounts[]` — each `{ parent: [], node }`. v1 supports **top-level mounts only**: `parent` must be `[]`.

Every top-level `node` is a **branch** (`kind: "branch"`) carrying a `rootEntry { concept, description, whenToUse }` — the representation crtr renders at root help. Branch children are nested branches (no rootEntry) or leaves. A leaf declares `params`, `output` (array of `{ name, type, required, constraint }`), `outputKind: "object"`, and a non-empty `effects` array. Params use crtr's public vocabulary — one positional max, long-form `flag`s (types `string|int|bool|path|enum`, `choices` for enum), `stdin`, `context-file`; kebab-case names, no aliases. Two client-side affordances ride on a `positional` or `flag`: `encoding: "text"|"base64"` on a `type: "path"` param sends the named local FILE's content instead of the path string, and `defaultFromEnv: "UPPER_SNAKE"` fills an omitted `string`/`path` param from that environment variable on the calling machine, counting as supplied (so it satisfies `required` and is sent) — unlike a static `default`, which is a parse convenience only and never ships. `defaultFromEnv` is rejected alongside `default` or `repeatable`. The declaration mirrors crtr's stable help descriptors, not its internal TypeScript defs — no closures, no dynamic state, no renderers.

### Execution: the trust boundary

On explicit leaf invocation — **never on install, help, or discovery** — crtr direct-spawns your executable (no shell) with `--crtr-command-protocol 1`, the caller's cwd, and the full environment. Installed command plugins are **trusted local code running with the caller's authority**: crtr does not sandbox them, filter the environment, mint a credential, or interpret your backend's auth. Your executable owns authentication to its own backend. This is an execution trust boundary, not a sandbox — installed code can already read the caller's files, so env filtering would only imply a security guarantee that does not exist.

### The protocol

crtr writes exactly one JSON request to your executable's stdin:

```json
{
  "protocolVersion": 1,
  "command": ["app", "show"],
  "input": { "appId": "app_123" },
  "context": { "cwd": "/caller/cwd", "plugin": { "name": "deploy-tools", "version": "0.1.0", "scope": "project", "root": "/plugin/root" } }
}
```

`input` keys are the parser's camelCase form; a declared `stdin` param arrives as an `input` string, not a second stream. Your executable writes exactly one JSON envelope to stdout and nothing else — diagnostics go to stderr:

```json
{ "protocolVersion": 1, "ok": true, "result": { "app_id": "app_123" } }
{ "protocolVersion": 1, "ok": false, "error": { "code": "authentication_required", "message": "...", "field": "session", "next": "..." } }
```

`ok` is the source of truth (a valid envelope is honored regardless of exit code). crtr validates `result` against your declared `output` (top-level field presence + type) and renders it; `--json` mirrors the same object. An error envelope becomes a normal crtr error with your `code` — lowercase snake_case, never crtr-reserved `internal`, `unknown_path`, `command_collision`, or `plugin_protocol_error`. No envelope at all (invalid JSON, empty, extra stdout, output over 10 MiB, signal kill) becomes `plugin_protocol_error`.

### Two rules that prevent silent breakage

- **Generate `commands.json` from your command definitions — never hand-write it.** The manifest must stay in lockstep with the executable's actual command surface; a hand-maintained copy drifts, and a drifted param or output field surfaces as a validation issue or a `plugin_protocol_error` at invocation. Emit it from the same source your executable dispatches on.
- **A required output field must be non-null.** The adapter treats an explicit `null` for a declared-required field as *absent* → `plugin_protocol_error`. If a value is genuinely optional, declare it `required: false`; if it's required, always return a real value.

### Validating your command manifest

crtr validates command manifests **statically — it never executes your binary** to check them:

- `crtr pkg plugin show <name>` — inventories the manifest path, executable, accepted top-level command names, and every current validation issue (each with received/expected/next).
- `crtr sys doctor` — validates the manifest + executable path for every effective command plugin and reports structured remediation (disable/update/remove). `--fix` never chmods or rewrites plugin content.
- `crtr pkg plugin install` / `update` — report the accepted top-level commands and any issues in their result.

A fixed manifest goes live on the next invocation; there is nothing to restart.

## Configured CLIs

A **configured CLI** is the other way a contributor adds `crtr` commands — the CLI analogue of an MCP client. Where a command plugin ships a local **executable**, a configured CLI is **definition + HTTP only**: crtr fetches a manifest from a remote endpoint, stores it locally, renders native `-h` help from it, and runs each leaf as a declarative REST call. There is no local binary to spawn and nothing to sandbox — a strictly narrower trust surface than a command plugin.

The manifest **is** the command-plugin `commands.json` schema (same `schemaVersion 1`, same node/param/output vocabulary, same reject-unknown-keys strictness), differing in exactly three ways: it drops `executable`, allows non-empty mount `parent` paths (a CLI builds a self-contained forest and may mount at depth), and every leaf carries a `rest` mapping (method/path/param placement/streaming flag) instead of `outputKind`. The `rest` mapping is transport — it never appears in generated `-h`, so a configured command is indistinguishable from a native one.

Manage them under `crtr pkg cli` — `register` (records `{name, endpoint, auth-env NAME}` in scope config and fetches+stores the manifest), `remove`, `list`, `show`, `refresh`. crtr stores only the env var **name** holding the bearer token, never the credential. The stored manifest is authoritative with **zero freshness machinery** (no TTL, ETag, or revalidation): it reloads only on a re-register (the guest-boot path — a byte-identical re-register still re-fetches) or an explicit `refresh`, and a registration with no stored manifest (its register-time fetch failed) triggers a one-time hydration fetch on an unknown-first-token miss, the sole moment an absent store is detected. Inspect a CLI's accepted commands and validation/collision issues with `crtr pkg cli show <name>` and `crtr sys doctor`; both read the same unified snapshot dispatch uses. A configured CLI may never mount onto a core, plugin, or other-CLI path — core always wins, cross-contributor path clashes drop all claimants with a `command_collision` issue.

## Validation

`crtr sys doctor` checks each plugin's manifest:
- Manifest exists and is valid JSON.
- Manifest `name` matches the directory name.
- When the plugin declares `commands`, its command manifest + executable path are validated statically (never executed) — see [Command plugins](#command-plugins).

`crtr memory lint` checks the docs under `memory/`: frontmatter parses, valid `kind`, both visibility rungs set. Run `crtr memory write -h` for the authoring + routing guide. Other sibling artifact dirs (`rules/`, `agents/`, `hooks/`) are validated by their respective specs as those land.

## Cross-publishing with Claude Code

Some plugins also publish a `.claude-plugin/` manifest alongside `.crouter-plugin/` so they can be loaded directly into Claude Code without going through crtr. Optional. Only worth doing when your memory docs or commands meaningfully stand alone in the Claude Code surface. Keep manifests in sync if you do.
