Writing your first plugin
Every surface in Blue is a Cordis plugin — your own enhancements stand level with the built-ins: the same seams, the same /theme hot-swap reload behavior, the same automatic rollback of every contribution on unload. This page walks you from zero to a working first plugin; for the full catalog of integration surfaces, see the Seam reference.
Preview-stage caveat
Seam signatures are planned to freeze in Phase 3; plugins integrating today may need adaptation across upgrades. This page is kept in sync with every release.
The plugin model
A Blue plugin is a Cordis plugin — export name (a stable string), an optional inject (the services it waits for), and apply(ctx):
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-plugin.hello'
export const inject = ['blueComponents']
export function apply(ctx: Context): void {
// …register your contributions
}Registration is an effect: wrap every registration (component mounts, commands, keys, status entries) in ctx.effect(() => ...) — unloading the plugin's fiber rolls everything back, and /theme hot-swap reloads of dependents leave no residue.
Your first plugin: a status-bar clock
Goal: add the current time to the status bar and register a /now command. The complete code:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-plugin.clock'
export const inject = ['blueStatus', 'commands']
export function apply(ctx: Context): void {
ctx.effect(() => ctx.blueStatus.register({
id: 'my-plugin.clock',
priority: 25, // built-ins occupy 0/5/10/20/30 — gaps are yours
render: (width) => new Date().toLocaleTimeString(),
}))
ctx.effect(() => ctx.commands.register({
name: 'now',
description: 'Print the current time',
handler: () => ({ kind: 'success', text: new Date().toLocaleString() }),
}))
}Notes:
injectdeclares dependencies — the plugin activates only when the services exist, no hand-rolled waiting;- both registrations return disposers and are effect-managed — everything vanishes on unload;
/nowappears in the editor's slash completion and/helpautomatically; no extra UI registration.
Packaging and assembly
- Export a subpath: expose the plugin entry in your package's
package.json(e.g."./clock": "./lib/clock.js"); the shape is identical to Blue's built-in plugins (see Built-in plugins). - Add a patch row to the profile's
cordis.patch.yml:
- id: my-plugin-clock
name: 'my-scope/my-pkg/clock'- Install:
dsh plugin --profile blue add link:/path/to/your/pkgduring development, or the marketplace's one-liner once it opens.
Rows add, delete, and reorder freely — zero-code customization: don't want a surface? Delete its row.
Next steps
- Seam reference — the full catalog of seams Blue opens: screen, keymap, component factory, themes, status bar, render intents, the shared editor, and what each enables;
- Built-in plugins — Blue's 21 built-ins are living examples of what plugins can do, each removable.
Design discipline
- Depend only on documented seams and contract packages (the
@dsh-blue/blue-*type exports) — never Blue package internals; - Every registration returns a disposer and lives inside
ctx.effect; - plain-first: your plugin stands level with Blue's built-in enhancements — there is no "internal channel";
- New seams open only when a first real consumer appears, and may adjust before the signature freeze.