Scaffold author overviews for plugin listings (#6226)

* feat(plugins): scaffold author listing overviews

* docs(plugins): refine the author overview contract

* docs(plugins): require repository overviews for registry listings

* docs(plugins): keep listing guidance focused on authors
This commit is contained in:
Mohamed Boudra
2026-10-06 15:58:26 +01:00
committed by GitHub
parent 396a2bc0b2
commit 06bde1e57a
4 changed files with 158 additions and 1 deletions
@@ -1,4 +1,4 @@
import { execFile } from "node:child_process";
import { exec, execFile } from "node:child_process";
import { mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import path from "node:path";
@@ -9,6 +9,7 @@ import { scaffoldPluginDirectory } from "./scaffold.js";
const directories: string[] = [];
const execFileAsync = promisify(execFile);
const execAsync = promisify(exec);
const tscPath = createRequire(import.meta.url).resolve("typescript/bin/tsc");
async function typecheckPlugin(directory: string): Promise<void> {
@@ -28,6 +29,47 @@ afterEach(async () => {
});
describe("plugin scaffold", () => {
it("creates an author overview beside the manifest", async () => {
const parent = await mkdtemp(path.join(tmpdir(), "paseo-plugin-scaffold-"));
directories.push(parent);
const directory = path.join(parent, "hello-plugin");
await scaffoldPluginDirectory(directory);
const overview = await readFile(path.join(directory, "OVERVIEW.md"), "utf8");
const comments = overview.match(/<!--[\s\S]*?-->/g) ?? [];
expect(comments).toHaveLength(1);
expect(overview.replace(/<!--[\s\S]*?-->/g, "").trim()).toBe("");
expect(overview).toContain("required to list");
expect(overview).toContain("README");
expect(overview).toContain("choosing a plugin in Paseo");
expect(overview).toContain("Length follows complexity");
expect(overview).toContain("1. Describe what your plugin is and does");
expect(overview).toContain("2. Explain how it works only when it is not obvious");
expect(overview).toContain("3. Explain setup when needed");
expect(overview).toContain("4. Explain capabilities and settings");
expect(overview).toContain("permissions");
expect(overview).toContain("known limits");
expect(overview).not.toContain("This plugin entry was imported");
const packageJson = JSON.parse(await readFile(path.join(directory, "package.json"), "utf8"));
expect(packageJson.files).toContain("OVERVIEW.md");
expect(await readdir(directory)).toContain("paseo-plugin.json");
});
it("includes the author overview in the packed npm artifact", async () => {
const parent = await mkdtemp(path.join(tmpdir(), "paseo-plugin-scaffold-"));
directories.push(parent);
const directory = path.join(parent, "packed-plugin");
await scaffoldPluginDirectory(directory);
const { stdout } = await execAsync("npm pack --dry-run --ignore-scripts --json", {
cwd: directory,
});
const [artifact] = JSON.parse(stdout) as { files: { path: string }[] }[];
expect(artifact.files.map((file) => file.path)).toEqual(
expect.arrayContaining(["OVERVIEW.md", "paseo-plugin.json"]),
);
});
it(
"creates a standalone split-runtime project that typechecks",
{ timeout: 15_000 },
@@ -60,6 +102,7 @@ describe("plugin scaffold", () => {
version: "0.0.0",
files: [
"paseo-plugin.json",
"OVERVIEW.md",
"index.client.ts",
"index.client.tsx",
"index.server.ts",
@@ -3,6 +3,34 @@ import path from "node:path";
import { PluginIdSchema } from "@getpaseo/protocol/messages";
import { resolveCliVersion } from "../../version.js";
const OVERVIEW = `<!--
OVERVIEW.md is required to list your plugin in the registry. Keep it beside
paseo-plugin.json in your repository at the pinned commit and in your npm package.
Replace this comment with useful facts before publishing.
This overview is for people choosing a plugin in Paseo, on the plugin page under
its install command. README assumes a GitHub audience and includes technical
details, installation instructions, and badges. Long, AI-generated READMEs leave
people reading past that material to understand what the plugin does.
Write these parts in order, including only what helps someone decide to install:
1. Describe what your plugin is and does in plain terms first.
2. Explain how it works only when it is not obvious.
3. Explain setup when needed: settings, accounts, tokens, providers, tools, or
other plugins. Include applicable daemon version and operating system
requirements. Setup guidance is allowed; installation instructions are not.
4. Explain capabilities and settings worth understanding, what each option does,
what the plugin reads or sends and where, permissions, and known limits.
Length follows complexity; a theme needs one paragraph. Choose headings only
when they help. Use sentence case and plain factual language, with no em dashes.
Omit installation commands, badges, changelog, contributing or license sections,
marketing, and unsupported claims. Avoid implementation filler such as empty
cleanup functions, theme-token field lists, or lists of absent features.
-->
`;
const TSCONFIG = {
compilerOptions: {
target: "ES2020",
@@ -155,6 +183,7 @@ export async function scaffoldPluginDirectory(
version: "0.0.0",
files: [
"paseo-plugin.json",
"OVERVIEW.md",
"index.client.ts",
"index.client.tsx",
"index.server.ts",
@@ -179,6 +208,7 @@ export async function scaffoldPluginDirectory(
"paseo-plugin.json",
`${JSON.stringify({ id, requirements: { paseo: `>=${version}` } }, null, 2)}\n`,
],
["OVERVIEW.md", OVERVIEW],
["package.json", `${JSON.stringify(packageJson, null, 2)}\n`],
["tsconfig.json", `${JSON.stringify(TSCONFIG, null, 2)}\n`],
["index.client.tsx", CLIENT_ENTRY],
+41
View File
@@ -56,6 +56,7 @@ The scaffold includes this `files` list in `package.json`:
{
"files": [
"paseo-plugin.json",
"OVERVIEW.md",
"index.client.ts",
"index.client.tsx",
"index.server.ts",
@@ -136,6 +137,46 @@ In the app, enter only the source identifier.
:::
## Your listing page
Write `OVERVIEW.md` beside `paseo-plugin.json` for the plugin page inside Paseo.
It helps someone decide whether to install your plugin; the install command is already at
the top of that page. A README assumes a GitHub audience and carries installation
instructions, technical details, and badges. Long, AI-generated READMEs make people read
past that material to understand what a plugin does.
`OVERVIEW.md` is required to list your plugin in the registry. Commit it beside
`paseo-plugin.json` in the source repository at the pinned commit. The registry resolves it
relative to the manifest, under `pluginPath` for monorepos. Include it in the published npm
package too; the scaffold's `files` list includes it. Replace the scaffold's guidance comment
with useful facts before publishing.
The repository overview takes precedence over a registry import stopgap. Online validation
fails when the pinned commit has no `OVERVIEW.md`, except for unchanged imported records
that already carry `plugins/<owner>/<slug>.md` in the registry repository. Every version bump
requires a repository overview and removes the stopgap in the same PR. A bump without the
repository overview fails validation. Neither README files nor a `paseo-listing.json`
`readme` override supplies the overview.
Author overviews and registry stopgaps follow the same content contract, in this order.
Choose headings only when they help; length follows complexity. A theme needs one paragraph.
1. Explain what the plugin is and does in plain terms first.
2. Explain how it works only when that is not obvious.
3. Explain setup when needed: settings, accounts, tokens, providers, external tools, or other
plugins. Include applicable daemon version and operating system requirements. Setup
guidance is allowed; installation instructions are not.
4. Explain capabilities and settings worth understanding, what each option does, what the
plugin reads or sends and where, permissions, and known limits.
Use sentence case and plain factual language, with no em dashes. Omit installation commands,
badges, changelog, contributing or license sections, marketing, and unsupported claims.
Avoid implementation filler such as empty cleanup functions, catalogs of theme-token fields,
or lists of absent features. Keep only what helps someone choose the plugin.
If your plugin is an unchanged imported record, you can propose an author-written overview
to replace the registry stopgap. Do not add import credit to your own `OVERVIEW.md`.
## Share through GitHub or Git
Push the plugin project to a repository. Users can install it with:
+43
View File
@@ -64,6 +64,7 @@ The generated project contains:
```text
my-plugin/
paseo-plugin.json
OVERVIEW.md
package.json
tsconfig.json
index.client.tsx
@@ -638,6 +639,48 @@ Do not edit a local config when the target is a remote daemon. Perform the edit
When the same screen or sidebar item exists on several connected hosts, Paseo shows it once. The screen header has a host picker; a sidebar item uses the host of the screen on display, else the host last picked in one of the plugin's screens. The selected host owns the bundle, SDK calls, RPCs, and query cache. An offline selected host does not fall through to another host. Attachment sources stay scoped to the composer's host.
## Publishing a listing
Write `OVERVIEW.md` beside `paseo-plugin.json` for the plugin page inside Paseo.
It helps someone decide whether to install your plugin; the install command is already at
the top of that page. A README assumes a GitHub audience and carries installation
instructions, technical details, and badges. Long, AI-generated READMEs make people read
past that material to understand what a plugin does.
`OVERVIEW.md` is required to list your plugin in the registry. Commit it beside
`paseo-plugin.json` in the source repository at the pinned commit. The registry resolves it
relative to the manifest, under `pluginPath` for monorepos. Include it in the published npm
package too; the scaffold's `files` list includes it. Replace the scaffold's guidance comment
with useful facts before publishing.
The repository overview takes precedence over a registry import stopgap. Online validation
fails when the pinned commit has no `OVERVIEW.md`, except for unchanged imported records
that already carry `plugins/<owner>/<slug>.md` in the registry repository. Every version bump
requires a repository overview and removes the stopgap in the same PR. A bump without the
repository overview fails validation. Neither README files nor a `paseo-listing.json`
`readme` override supplies the overview.
Author overviews and registry stopgaps follow the same content contract, in this order.
Choose headings only when they help; length follows complexity. A theme needs one paragraph.
1. Explain what the plugin is and does in plain terms first.
2. Explain how it works only when that is not obvious.
3. Explain setup when needed: settings, accounts, tokens, providers, external tools, or other
plugins. Include applicable daemon version and operating system requirements. Setup
guidance is allowed; installation instructions are not.
4. Explain capabilities and settings worth understanding, what each option does, what the
plugin reads or sends and where, permissions, and known limits.
Use sentence case and plain factual language, with no em dashes. Omit installation commands,
badges, changelog, contributing or license sections, marketing, and unsupported claims.
Avoid implementation filler such as empty cleanup functions, catalogs of theme-token fields,
or lists of absent features. Keep only what helps someone choose the plugin.
If your plugin is an unchanged imported record, you can propose an author-written overview
to replace the registry stopgap. Do not add import credit to your own `OVERVIEW.md`.
See [Your listing page](https://paseo.sh/docs/plugins/publishing.md#your-listing-page).
## Typecheck and manage
When editing a plugin, typecheck its source before install or reload: