Files
paperclip-docs/docs/reference/adapters/external-adapters.md
Aron PrinsandClaude Opus 5 325c9eb819 seo: hand-write titles and descriptions for all 192 pages (#115)
## Why

The build derived `<title>` from the **sidebar label** and the
description from the **first paragraph clipped to 220 characters**. Both
are the wrong source. A sidebar label is written for a 240px-wide nav
that supplies its own context; a search result has none.

On production that produced:

- **7 pages titled "Overview"**, and 32 pages sharing a title with
another page
- **109 of 193 descriptions cut off mid-word**, and 174 over 160
characters (where Google truncates)

```
/guides/welcome/key-concepts/
  BEFORE (220):  ...and if you misunderstand them early, the product wi
```

Near-identical titles are the weak-differentiation signal behind
"crawled — currently not indexed", which is the exact symptom on this
property.

## What changed

`seo_title` and `seo_description` frontmatter on **every one of the 192
pages**, written by hand against each page's real content and optimised
for the click — lead with what the reader can do, name the concrete
things covered, no keyword stuffing, no repeating the title.

| | Before | After |
|---|---|---|
| Duplicate titles | 32 pages | **0** |
| Duplicate descriptions | — | **0** |
| Descriptions cut mid-word | 109 | **0** |
| Descriptions over 160 chars | 174 | **0** |
| Title length (incl. suffix) | up to "Overview" | **26–60** |
| Description length | 31–240 | **122–158** |

**No prose was rewritten.** I measured content overlap between the
colliding pairs first — only 17–23%, so these were always distinct pages
wearing the same label, not duplicate content. The `docs/` diff is **564
insertions, zero deletions**.

### Examples

```
BEFORE — all seven identical:  "Overview | Paperclip Docs"
AFTER:  "Adapters Overview"      "API Overview"        "CLI Overview"
        "Deployment Overview"    "Bundled Skills"      "Optional Skills"
        "Experimental Features"

BEFORE — both identical:  "Issues | Paperclip Docs"
AFTER:  "Issues: Creating and Tracking Work"   /guides/day-to-day/issues/
        "Issues API"                           /reference/api/issues/
```

```
/guides/getting-started/five-minute-path/
BEFORE (220):  The shortest route from a running Paperclip instance to an agent that has
               just completed work for you. After Paperclip is installed, the path takes
               about 5 minutes: create a company, hire a CEO agent, approve its first s
AFTER  (131):  Install Paperclip, create a company, hire a CEO agent, and approve its
               first strategy — a working AI company in about five minutes.
```

## Plumbing

- **`site/build-release.mjs`** prefers the authored fields; the derived
path stays only as a fallback the CI gate prevents ever reaching.
`<title>`, `<meta name="description">`, and the `TechArticle` JSON-LD
all read from the same source.
- **`site/app.js`** uses the same fields, so a client-side transition
cannot leave a different title on screen than the crawler was served.
Verified in-browser: navigating `/guides/day-to-day/issues/` →
`/reference/api/issues/` swaps to exactly the server-rendered values, no
JS errors.
- **`scripts/verify-seo-metadata.mjs`** (new) enforces presence,
uniqueness, length, complete-sentence endings, and the
frontmatter-safety rules. Wired into `docs:test` and CI as its own step.

I negative-tested the gate rather than assuming it works — it correctly
fails on a duplicate title, a mid-word description, and a missing field,
and passes again once restored.

## Automating it going forward

`skills/sync-docs/SKILL.md` now:

- gains an **"Authored SEO metadata"** section: why the fields exist,
the enforced rules as a table, and how to actually write them (title
names the subject not the nav position; description writes for the
click; never generate mechanically from the first paragraph — *that is
the fallback the gate exists to prevent*).
- requires both fields in the **page-authoring instruction** given to
every subagent, with guidance on when to update an existing description
versus leave it.
- runs `docs:test:seo-metadata` and `docs:test:crawlable-links` as
**fail-loud gates in Phase 7** before commit.
- documents all six build gates in the files/gates section.

So once this lands, a new page cannot ship without hand-written
metadata, and a title collision cannot reach production.

## Verified

All six verifiers pass:

```
Static route verification passed
Asset fingerprint verification passed
Hierarchical Skills nav verification passed
Skill source block verification passed (11 skill pages, 1 control page)
Crawlable-link contract passed: 40886 internal links across 194 pages resolve in one hop
Authored-SEO contract passed: 192 pages carry a unique, hand-written seo_title and seo_description
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-08-25 22:51:42 +02:00

212 lines
6.5 KiB
Markdown

---
paperclip_version: v2026.609.0
seo_title: External Adapters
seo_description: Ship an adapter as its own npm package or local directory when you want independent versioning, separate distribution, or private runtime code.
---
# External Adapters
External adapters let you ship a Paperclip adapter as its own npm package or local directory. Use them when you want independent versioning, separate distribution, or a runtime that should not live inside the Paperclip repo.
---
## When To Use
- You want to distribute an adapter independently from Paperclip.
- You are building an internal adapter for your team or workspace.
- You need a local plugin that can be installed and reloaded without editing Paperclip source.
## When Not To Use
- The adapter should ship with Paperclip itself. Use a built-in adapter.
- You only need a one-off local command. Use [Process](./process.md).
- The runtime is already covered by the built-in local adapters.
---
## Built-In Vs External
| Area | Built-in | External |
|---|---|---|
| Source | Paperclip repo | Separate npm package or local directory |
| Installation | Ships with Paperclip | Installed through the Adapter Manager or `POST /api/adapters/install` |
| Updates | Requires a Paperclip release | Independent package versioning |
| UI parser | Static import inside Paperclip | Optional `./ui-parser` export |
| Registration | Hardcoded in the host | Loaded at startup from the adapter plugin store |
> **Tip:** External adapters are the right choice whenever you want to own the release lifecycle of the adapter separately from the Paperclip app.
---
## Package Shape
Minimal package layout:
```text
my-adapter/
package.json
tsconfig.json
src/
index.ts
server/
index.ts
execute.ts
test.ts
ui-parser.ts
```
`src/index.ts` should export the adapter metadata and the server factory:
```ts
export const type = "my_adapter";
export const label = "My Adapter";
export const models = [{ id: "model-a", label: "Model A" }];
export const agentConfigurationDoc = `# my_adapter agent configuration
Use when:
- ...
Don't use when:
- ...
`;
export { createServerAdapter } from "./server/index.js";
```
`package.json` should expose the runtime entry points and, if you ship a parser, the UI parser bundle:
```json
{
"name": "my-paperclip-adapter",
"version": "1.0.0",
"type": "module",
"paperclip": {
"adapterUiParser": "1.0.0"
},
"exports": {
".": "./dist/index.js",
"./server": "./dist/server/index.js",
"./ui-parser": "./dist/ui-parser.js"
}
}
```
---
## Installation
Install an external adapter from the Board UI or through the API:
```http
POST /api/adapters/install
Content-Type: application/json
```
The request body accepts:
| Field | Required | Notes |
|---|---:|---|
| `packageName` | yes | npm package name or local path. |
| `version` | no | Optional npm version. |
| `isLocalPath` | no | Set to `true` for a local checkout. |
Example:
```json
{
"packageName": "@henkey/hermes-paperclip-adapter",
"version": "latest",
"isLocalPath": false
}
```
After install, the adapter appears in `GET /api/adapters` and in the Adapter Manager UI.
---
## Server Contract
The host calls `createServerAdapter()` from your package root. That factory must return a `ServerAdapterModule` with at least:
- `type`
- `execute()`
- `testEnvironment()`
- `models`
- `agentConfigurationDoc`
The server factory is the core of an external adapter. It is what turns a package into a runnable adapter type.
---
## UI Parser Contract
If your adapter emits structured stdout, ship `./ui-parser` as a self-contained browser-safe module.
See:
- [Adapter UI Parser Contract](./adapter-ui-parser.md)
The UI parser is optional, but it is the difference between raw log text and a readable run transcript.
---
## Runtime Lifecycle
1. Paperclip loads the adapter package at startup.
2. The plugin store records the package name, local path, and installed version.
3. The adapter becomes available to the board UI and the server registry.
4. The Adapter Manager can disable, reload, reinstall, or remove the adapter later.
Useful management endpoints:
- `GET /api/adapters`
- `PATCH /api/adapters/:type`
- `POST /api/adapters/:type/reload`
- `POST /api/adapters/:type/reinstall`
- `DELETE /api/adapters/:type`
> **Note:** Built-in adapters are protected from deletion. You can still install an external adapter that *overrides* a built-in type (see below), but the built-in itself stays in place as a fallback and can't be removed unless an external override exists for it.
---
## Overriding A Built-In
You can install an external adapter whose `type` matches a built-in — for example, ship your own `hermes_local` to pick up a newer or organization-specific implementation. `POST /api/adapters/install` accepts the override, and the external adapter takes precedence while the built-in stays available as a fallback.
A few things to know:
- A first-time override of a built-in type installs without requiring a restart. Reinstalling an override you already have (an existing external plugin record for that type) is the only case that reports `requiresRestart`.
- Pausing the override from the Adapter Manager snaps every agent of that type back to the built-in implementation on their next run; resuming restores the external adapter.
- Removing the override unregisters the external adapter and falls back to the built-in. The built-in itself is never deleted.
The operator-facing walkthrough of pausing, resuming, and removing overrides lives in [Settings → Instance: Adapters](../../administration/settings.md).
---
## Example Workflow
1. Create the package and export `createServerAdapter()`.
2. Add `paperclip.adapterUiParser` if you ship a custom parser.
3. Install the package from the Adapter Manager or `POST /api/adapters/install`.
4. Use `GET /api/adapters/:type/config-schema` to drive the UI form.
5. Use `GET /api/adapters/:type/ui-parser.js` if you ship a parser.
6. Reload the adapter when you are iterating locally.
---
## Practical Notes
- Prefer a local path install while developing the adapter.
- Use npm installation when you want the package to behave like any other dependency.
- Keep the package self-contained. The host expects the adapter to load cleanly without modifying Paperclip source.
- Treat the adapter package as the source of truth for its own config documentation.
---
## Next Steps
- [Creating an Adapter](./creating-an-adapter.md)
- [Adapter UI Parser Contract](./adapter-ui-parser.md)
- [Claude Code](./claude-code.md)
- [Codex](./codex.md)