From zero to a working plugin
The end-to-end reference is dsh-quicknote, a maintained plugin built from this template: composer capture, a JSON API, four Agent tools and a sidebar page. This page is the short version of that path.
1. Generate the project
pnpm create dsh-kit dsh-quicknote
cd dsh-quicknote
pnpm installYou get a host half, a client half, tests, a build, AGENTS.md, llms.txt and one verification command.
2. Host half
Keep the store separate from transport. In src/host/store.ts, one JSON file, atomic writes:
await mkdir(dirname(file), { recursive: true })
await writeFile(tmp, JSON.stringify({ notes }), { mode: 0o600 })
await rename(tmp, file)Register the routes on the Host connection service; the client only sees JSON:
ctx.inject(['connection'], (scope) => {
const registry = scope.get('connection').fetch
registry.register({ path: '/api/quicknote/notes', methods: ['GET'], requestBody: 'buffered', fetch })
})Expose the same store as tools for the model: quicknote_add, quicknote_list, quicknote_done, quicknote_delete.
3. Client half
The composer button reads the active draft from the conversation service and opens a card:
const shell = ctx.get('conversation').input.shell(sessionId)
const draft = shell.state.getSnapshot().draftThe card saves through fetch('/api/quicknote/notes/create'). Saving never clears the draft unless the user chooses the explicit Save and clear draft button; that is the difference between a convenience and losing someone's text.
Register the pieces as slots:
ctx.slots.inject('conversation.input.left', () => ctx.slots.register({ ... }, ComposerButton))
ctx.slots.inject('shell.overlay', () => ctx.slots.register({ ... }, Overlay))
ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({ ... }, Icon))
ctx.slots.inject('main', () => ctx.slots.register({ ... }, Page))4. Prove it
pnpm verify
dsh plugin --profile web add link:$PWDThen in DSH: click Note in the composer, save, open the sidebar page, mark the note done and delete it. The real plugin's repository also contains the tests that cover the store and the client registration.
5. What the template deliberately leaves to you
- The product UI and copy.
- Whether notes are local-only or synced.
- The API shape and any additional tools.
- Publishing: see PUBLISHING.md in the reference plugin.
The point of the template is that layout, build, types, tests and the copy-safe capture flow are already there; the product is yours.
6. Debug loop
pnpm test # one suite
pnpm verify # the full gate
pnpm build # regenerate lib/After a client change, rebuild and restart DSH; the web shell caches the bundle per boot. Use dsh --profile web --dump-config to confirm the row and the client entry. Browser DevTools shows the plugin's console errors; the Host log shows route/tool registration failures.
7. Publish
pnpm verify
git tag v0.1.0Check the artifact with npm pack --dry-run before tagging.
Follow PUBLISHING.md: the GitHub repository owns review and history, npm owns the artifact, and CI publishes with provenance after the tag.
8. Host it, document it, and apply for curation
- Push the plugin to its own GitHub repository.
- Give it its own documentation site (install, usage, reference), not a page on the dsh-kit site. The reference implementation builds one with VitePress: https://github.com/dsh-kit/dsh-quicknote/tree/main/docs.
- To join the curated list, read CURATION.md and open a Plugin application issue. The list is curated, not a marketplace.