Getting started
Build your first Decisive plugin — a tool the AI can call — and run it in your workspace.
Prerequisites
- A Decisive workspace connected to a GitHub repo (Settings → GitHub).
- Admin access to that workspace (only admins enable plugins and fill secrets).
1. Add the SDK
In your connected repo:
pnpm add -D @decisive/sdk
The SDK is types + tiny define*() helpers — no runtime weight. Your plugin code
is bundled by Decisive at sync time.
2. Create the plugin
Decisive looks for plugins in .decisive/plugins/<name>/plugin.ts, each with a
default export from definePlugin.
// .decisive/plugins/hello/plugin.ts
import { definePlugin, defineTool } from '@decisive/sdk';
export default definePlugin({
name: 'hello',
title: 'Hello',
apiVersion: 1,
actions: [
defineTool<{ name: string }>({
name: 'greet',
description: 'Greet someone by name and post it to chat.',
input: {
type: 'object',
properties: { name: { type: 'string', description: 'Who to greet.' } },
required: ['name']
},
async run(input, ctx) {
await ctx.chat.send(`👋 Hello, ${input.name}!`);
return { data: { greeted: input.name } };
}
})
]
});
Two things every action returns from run:
data— what the AI/caller receives (becomes the tool result).- optionally
blocks— what humans see (rendered natively). See Block Kit.
3. Push and sync
git add .decisive && git commit -m "feat: hello plugin" && git push
Decisive picks up the change (push webhook, or Settings → Extensions → Reload). The plugin appears as Discovered.
4. Review, enable, and fill secrets
In Settings → Extensions, an admin:
- reviews the plugin's declared capabilities and the commit it was built from,
- fills any declared
secrets, - clicks Enable.
Until enabled, nothing runs — this gate is deliberate (see Concepts → Lifecycle).
5. Use it
Mention @AI in chat: "greet Sam". The AI now has a greet tool and will call
it. You can also expose the same action as a slash command or panel — see
Surfaces.
Add a secret + an external call
defineTool<{ id: string }>({
name: 'lookup_order',
description: 'Look up an order by id.',
input: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
needs: ['SHOP_API_KEY'], // must match a declared secret on the plugin
async run(input, ctx) {
const res = await ctx.http(`https://api.shop.com/orders/${input.id}`, {
headers: { authorization: `Bearer ${ctx.secrets.SHOP_API_KEY}` }
});
if (!res.ok) return { error: `Shop API ${res.status}` };
return { data: await res.json() };
}
});
Declare the secret on the plugin so Decisive prompts the admin for it:
export default definePlugin({
name: 'shop',
apiVersion: 1,
secrets: [{ key: 'SHOP_API_KEY', label: 'Shop API key', help: 'Read-only token.', required: true }],
actions: [/* ... */]
});
Never commit secrets to the repo. Declare them; an admin fills them in Decisive; they're injected as
ctx.secrets.*at runtime.
Develop faster
- Run
tsc --noEmitin your repo — the SDK is fully typed, so most mistakes surface before you push. - Use
ctx.log(...)for output that shows up in the extension's run log. - Keep one capability per action; compose several actions in one plugin.
Next: Concepts for the full model, or jump to the Host API reference.