@danxbot/plugin-sdk
v0.2.22
Published
SDK for building 50 Arms card-type plugins (DX-5170): the card kit's type declarations, a Vite preset that builds a plugin against the app's shared runtime and writes its manifest, and the danx-plugin CLI.
Readme
@danxbot/plugin-sdk
Build a 50 Arms card-type plugin: a small React bundle that gives a card type its own card view (CardView) and/or card-list view (CardListView), loaded into 50 Arms' own page with import().
A plugin does not bundle React, @danxbot/ui or the card features. The app serves them under fixed import specifiers through an import map, so a plugin shares the app's single React and renders the app's own components:
| Specifier | What it is |
|---|---|
| react, react/jsx-runtime, react-dom | The app's React |
| @danxbot/ui | The app's design system |
| @danxbot/card-kit@1 | The card features: data hooks, card regions (StandardCardPage, EditableTitle, ChecklistsSection, ...), CardFace, CardBoard |
The live list and each library's major version is GET /api/plugin-runtime.
Set up a plugin
package.json: depend on @danxbot/plugin-sdk and install its peers: vite, react, @types/react, @danxbot/ui and react-router (types only: the app serves the real ones; the kit's declarations import them).
vite.config.ts:
import { defineConfig } from "vite";
import { danxPlugin } from "@danxbot/plugin-sdk/vite";
export default defineConfig({ plugins: [danxPlugin()] });tsconfig.json: add "types": ["@danxbot/plugin-sdk/card-kit"]: the kit's declarations. They are self-contained (they import only react, @danxbot/ui and react-router) and the specifier is not a package, so TypeScript reads them as ambient types.
src/index.tsx exports CardView and/or CardListView:
import { StandardCardPage, type CardViewProps } from "@danxbot/card-kit@1";
export function CardView({ card, mutation }: CardViewProps) {
return <StandardCardPage card={card} mutation={mutation} />;
}vite build writes the plugin to dist/: index.js (the entry), chunks/ for code splitting, index.css, source maps, and danx-plugin.json, the manifest listing every file with its size and sha256 and the runtime majors the plugin was built against.
The build refuses to bundle a copy of React, React DOM, @danxbot/ui or react-router: import only the specifiers above. For routing, use the kit's Link and useNavigate; StandardCardPage reads the plan the page was opened from out of the URL, so a view that wraps it passes only {card, mutation}.
The card kit is a contract
A runtime export of @danxbot/card-kit@1, and its parameters, is never removed or changed in place; the keys of the exported data types follow the Versioning section below. A breaking change is @danxbot/card-kit@2, served beside @1 until no approved plugin revision needs it. A plugin's manifest records the majors it was built against, and the app refuses to run a revision built for a major it no longer serves.
Publish a revision
DANXBOT_DASHBOARD_URL=https://<your dashboard> DANXBOT_API_KEY=<a key from your own account> npx danx-plugin publish --plugin <key> --note "what changed" [--dir <project>] [--name "Display name"]publish runs the project's own npm run build, archives the source (inside a git repo: the tracked files plus untracked ones git does not ignore; otherwise what the top-level .gitignore leaves, minus node_modules, .git and a top-level dist; a lockfile is required, so the download builds as is) and uploads both as a new revision of the plugin <key>, creating the plugin on its first publish. It prints the revision number and the Settings > Plugins link where a team admin approves it. .env* files are never archived, in or out of git. Inside git, a git failure (git missing, a dubious-ownership refusal) stops the publish with git's message; only "not a git repository" means outside git, where the top-level .gitignore is honoured (*, ?, **, anchoring and trailing /; a ! negation is refused). A blank note is refused first, before the build, the archive or any request. The dashboard validates the upload; a refusal prints its named reason.
The same flow is exported for tools as @danxbot/plugin-sdk/publish (buildPlugin, archiveSource, uploadRevision, fetchTransport): the dashboard MCP's plugin_publish tool runs it for agents.
The screenshot on a revision
An approver sees a screenshot of the plugin next to each revision. There is nothing to draw by hand: the dashboard MCP's plugin_publish tool (not danx-plugin publish, which uploads no screenshot) takes it from the plugin's own build. A headless Chromium (Playwright, resolved from your project or the MCP server's working directory) opens the dashboard's public /plugin-preview page, which imports your built CardView on the app's own React, @danxbot/ui and card kit and draws it with an invented sample card; the build is served from memory, so the revision need not be uploaded first. The capture waits until the page itself reports ready (or failed, with the reason), then saves a 1280x800 PNG as screenshot.png, which joins the revision's files and manifest.
So a plugin must export a CardView for a screenshot to exist. No browser, a dashboard that predates the page, or a plugin that fails to render all publish the revision WITHOUT a screenshot; plugin_publish answers screenshot: "none: <reason>" with the cause.
Versioning
The SDK stays 0.x until the shapes behind the card kit's data types settle: phase 1 of DX-4679 (card types as rows) changes IssueViewV2.type, and DX-5164 (designs on any card) changes the design fields. Until then the declarations may change between 0.x releases; the kit's runtime exports follow the @danxbot/card-kit@1 rule above.
