For developers
Teach Diurna a new source.
A plugin is a small TypeScript module: one definePlugin
call, an honest manifest, a single-file bundle. If your tool knows
something about your workday, it can be a signal source.
Paste it into Claude — or any agent — and describe your source. It carries the full contract: signal shape, SDK hooks, manifest schema, bundling, submission.
1 Write the plugin
Install @diurna/plugin-sdk and default-export a
definePlugin. There are two optional hooks, and you
implement whichever fit:
-
provide(settings, day)— a Provider: a pure function that returns one day's signals from a durable source. The engine's cache-through wrapper handles storage — past days are served from the DB, today is always re-provided. -
watch(ctx, settings)— a Watcher: for signals that must be captured the moment they happen. Called on a cadence; acts only through the typedctx(readSignals,emitPrompt,getOAuthToken) — it never draws UI and never writes the DB.
import { definePlugin } from '@diurna/plugin-sdk'
export default definePlugin({
id: 'acme.standup',
settings: {
apiHost: { default: 'api.acme.dev', kind: 'str', help: 'Standup API host' },
},
// A Provider: "give me this day's signals." Pure — never touches the DB.
provide: async (settings, day) => {
const entries = await fetchStandups(settings.apiHost, day)
return entries.map((e) => ({
kind: 'standup',
tsStart: e.postedAt,
data: { text: e.text, channel: e.channel },
}))
},
})
Settings descriptors (default / kind /
help) drive a generated settings pane — plugins never ship
UI.
2 Declare the manifest
manifest.json is hand-authored, deliberately: it's the
security contract a reviewer reads as a static declaration, never
inferred from code. Declare only what you need — the capability sheet
is what users consent to, and an update asking for more is held for
re-consent.
{
"id": "acme.standup",
"name": "Acme Standup",
"version": "1.0.0",
"entry": "dist/bundle.cjs",
"author": "acme",
"license": "MIT",
"repo": "https://github.com/acme/diurna-standup",
"capabilities": {
"network": { "hosts": ["api.acme.dev"] }
},
"provides": [
{
"kind": "standup",
"point": true,
"description": "a standup note the user posted; data.text, data.channel."
}
]
}
The one duplication — manifest.provides vs the code's
signal descriptors — is checked at build time: the build fails on
drift, so the reviewed declaration and the shipped behavior can't
diverge.
3 Bundle to one file
Plugins ship as a single-file JS artifact with dependencies baked in at publish time — no dependency resolution ever runs on a user's machine. The bundle runs in an isolated subprocess, sandboxed to exactly the capabilities the manifest declares.
4 Submit by pull request
The registry is a git repo with no backend. Listing means opening a PR that adds your entry — repo, pinned version, bundle checksum, embedded manifest — and CI validates it with static analysis only (it never runs your code):
- Manifest matches the schema; entry key equals
manifest.id - License is OSI-approved — open source is required for listing
- The bundle at your release URL matches the pinned sha256
- The embedded manifest byte-matches the one in the pinned bundle
Pinning is the security property: every update goes through re-review and a new pinned checksum, so a compromised author account can't silently push code to users.