@gobing-ai/spur-plugin-sdk 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +296 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,296 @@
1
+ # @gobing-ai/spur-plugin-sdk
2
+
3
+ Spur Plugin SDK — types, schemas, and runtime for building Spur plugins with trust levels, capability registries, and event hooks.
4
+
5
+ ## Architecture
6
+
7
+ ```mermaid
8
+ classDiagram
9
+ direction TB
10
+
11
+ class SpurPlugin {
12
+ <<interface>>
13
+ +name: string
14
+ +version: string
15
+ +trust: TrustLevel
16
+ +onLoad(host: PluginHost)
17
+ +onUnload?(host: PluginHost)
18
+ +onServerStart?(host: PluginHost)
19
+ +onServerStop?(host: PluginHost)
20
+ }
21
+
22
+ class PluginHost {
23
+ +commands: CommandRegistry
24
+ +api: ApiRegistry
25
+ +ui: UiRegistry
26
+ +events: EventRegistry
27
+ +harnesses: HarnessRegistry
28
+ +providers: ProviderRegistry
29
+ +rules: RuleRegistry
30
+ +skills: SkillRegistry
31
+ +workers: WorkerRegistry
32
+ +eventRegistry: EventRegistry
33
+ +logger: Logger
34
+ +trust: TrustEngine
35
+ +loadPlugin(plugin, ctx, overrides?, env?) Promise~Config~
36
+ +unloadPlugin(name) Promise~void~
37
+ +isLoaded(name) boolean
38
+ +startServerHooks() Promise~void~
39
+ +stopServerHooks() Promise~void~
40
+ }
41
+
42
+ class TrustEngine {
43
+ +check(manifest, capability, name, ctx) void
44
+ +enforce(capability, level, ctx) void
45
+ +declares(manifest, capability, name) boolean
46
+ }
47
+
48
+ class TrustLevel {
49
+ <<enumeration>>
50
+ bundled
51
+ curated
52
+ local
53
+ untrusted
54
+ }
55
+
56
+ class Capability {
57
+ <<type>>
58
+ commands | api | ui | events
59
+ harnesses | providers | rules
60
+ skills | workers
61
+ }
62
+
63
+ class EventRegistry {
64
+ +subscribe(pattern, handler) void
65
+ +unsubscribe(pattern) void
66
+ +unsubscribeAll() void
67
+ }
68
+
69
+ class PluginManifest {
70
+ +name: string
71
+ +version: string
72
+ +trust: TrustLevel
73
+ +capabilities: CapabilitiesManifest
74
+ +config?: Record~string,unknown~
75
+ }
76
+
77
+ class RegistrationContext {
78
+ +source: PluginSource
79
+ +pluginName: string
80
+ +trustLevel: TrustLevel
81
+ }
82
+
83
+ class PluginManifestError~Zod~ {
84
+ +issues: ZodIssue[]
85
+ }
86
+ class PluginCollisionError
87
+ class PluginTrustError
88
+ class PluginNotDeclaredError
89
+
90
+ SpurPlugin --> PluginHost : onLoad(host)
91
+ PluginHost *-- TrustEngine
92
+ PluginHost *-- EventRegistry
93
+ PluginHost *-- "9" Registry : typed registries
94
+ PluginHost --> RegistrationContext
95
+ TrustEngine --> TrustLevel
96
+ TrustEngine --> Capability
97
+ TrustEngine ..> PluginTrustError : throws
98
+ TrustEngine ..> PluginNotDeclaredError : throws
99
+ PluginHost ..> PluginManifest : validates via
100
+ PluginManifestError --|> Error
101
+ PluginCollisionError --|> Error
102
+ PluginTrustError --|> Error
103
+ PluginNotDeclaredError --|> Error
104
+ ```
105
+
106
+ ## Quick Start
107
+
108
+ ### Installation
109
+
110
+ ```bash
111
+ bun add @gobing-ai/spur-plugin-sdk
112
+ ```
113
+
114
+ ### 1. Define a plugin
115
+
116
+ Plugins implement the `SpurPlugin` interface. The entry point is `onLoad(host)`, which receives a `PluginHost` providing access to all capability registries.
117
+
118
+ ```typescript
119
+ import type { SpurPlugin, PluginHost } from '@gobing-ai/spur-plugin-sdk';
120
+
121
+ export const myPlugin: SpurPlugin = {
122
+ name: 'my-plugin',
123
+ version: '1.0.0',
124
+ trust: 'local',
125
+
126
+ onLoad(host: PluginHost) {
127
+ // Register a slash command
128
+ host.commands.register({ name: 'my-cmd' }, {
129
+ name: 'my-cmd',
130
+ execute(args: string[]) {
131
+ host.logger.info(`my-cmd called with: ${args}`);
132
+ },
133
+ });
134
+
135
+ // Subscribe to agent lifecycle events
136
+ host.eventRegistry.subscribe('agent.*', (event, detail) => {
137
+ host.logger.info(`Agent event: ${event}`, detail);
138
+ });
139
+ },
140
+
141
+ onUnload(host: PluginHost) {
142
+ host.eventRegistry.unsubscribeAll();
143
+ },
144
+ };
145
+ ```
146
+
147
+ ### 2. Write the manifest
148
+
149
+ Every plugin needs a `plugin.yaml` declaring its trust level and capabilities:
150
+
151
+ ```yaml
152
+ # plugin.yaml
153
+ name: my-plugin
154
+ version: 1.0.0
155
+ description: Example Spur plugin
156
+ author: your-name
157
+ trust: local
158
+ capabilities:
159
+ commands:
160
+ - my-cmd
161
+ events:
162
+ - agent.run.start
163
+ - agent.run.complete
164
+ config:
165
+ greeting: hello
166
+ ```
167
+
168
+ Validate the manifest programmatically:
169
+
170
+ ```typescript
171
+ import { validateManifest, PluginManifestError } from '@gobing-ai/spur-plugin-sdk';
172
+
173
+ try {
174
+ const manifest = validateManifest(parsedYaml);
175
+ // Use manifest as a typed PluginManifest
176
+ } catch (err) {
177
+ if (err instanceof PluginManifestError) {
178
+ console.error('Invalid manifest:', err.issues);
179
+ }
180
+ }
181
+ ```
182
+
183
+ ### 3. Configuration merging
184
+
185
+ Plugin configuration merges three layers (lowest to highest precedence):
186
+
187
+ 1. `plugin.yaml` `config:` defaults
188
+ 2. `.spur/plugins/<name>.yaml` file overrides
189
+ 3. `SPUR_PLUGIN_<NAME>_<KEY>` environment variables
190
+
191
+ ```typescript
192
+ import { mergePluginConfig } from '@gobing-ai/spur-plugin-sdk';
193
+
194
+ const config = mergePluginConfig(
195
+ { greeting: 'hello', timeout: 30 }, // defaults from plugin.yaml
196
+ { timeout: 60 }, // .spur/plugins/my-plugin.yaml overrides
197
+ process.env, // env vars (SPUR_PLUGIN_MY_PLUGIN_*)
198
+ 'my-plugin',
199
+ );
200
+ // => { greeting: 'hello', timeout: <from env or 60> }
201
+ ```
202
+
203
+ Environment variables override everything. `SPUR_PLUGIN_MY_PLUGIN_TIMEOUT=90` sets `config.timeout` to `90`. Values are JSON-parsed when possible (`30` becomes a number, `true` becomes boolean), falling back to raw strings.
204
+
205
+ ## Trust Levels & Policy
206
+
207
+ Every plugin declares a trust level in its manifest. The `TrustEngine` enforces capability access at registration time.
208
+
209
+ | Trust Level | Allowed Capabilities | Typical Use |
210
+ |---|---|---|
211
+ | `bundled` | Everything (unconditional) | Built-ins shipped with Spur |
212
+ | `curated` | Everything | Vetted, externally-reviewed plugins |
213
+ | `local` | `commands`, `api`, `ui`, `events`, `skills` | Project-local plugins |
214
+ | `untrusted` | `commands`, `api`, `ui`, `events`, `skills` | Downloaded plugins |
215
+
216
+ Capabilities denied to `local`/`untrusted`: `harnesses`, `providers`, `rules`, `workers`.
217
+
218
+ Attempting to register a capability beyond your trust level throws `PluginTrustError`. Attempting to register a capability not declared in the manifest throws `PluginNotDeclaredError`.
219
+
220
+ ## Capability Registries
221
+
222
+ Each registry is typed — `register()` accepts a capability-specific `TImpl`:
223
+
224
+ | Registry | Purpose | TImpl |
225
+ |---|---|---|
226
+ | `CommandRegistry` | Slash commands | `{ name, execute(args) }` |
227
+ | `ApiRegistry` | Server API routes | `{ name, handler(req), openApi? }` |
228
+ | `UiRegistry` | UI components | `{ name, component }` |
229
+ | `EventRegistry` (plugin) | Domains events | `{ name, handlers }` |
230
+ | `HarnessRegistry` | Agent harnesses | `{ name, harness }` |
231
+ | `ProviderRegistry` | LLM/model providers | `{ name, provider }` |
232
+ | `RuleRegistry` | Validation rules | `{ name, rule }` |
233
+ | `SkillRegistry` | Agent skills | `{ name, skill }` |
234
+ | `WorkerRegistry` | Background workers | `{ name, worker }` |
235
+
236
+ All registries share a common `Registry<T>` base with collision detection (`PluginCollisionError` on duplicate `capability:name` pairs) and trust enforcement via `TrustEngine.check()`.
237
+
238
+ ## Event System
239
+
240
+ The `EventRegistry` wraps `@gobing-ai/ts-infra`'s `EventBus` with glob-pattern subscriptions:
241
+
242
+ ```typescript
243
+ // Subscribe to all agent events
244
+ host.eventRegistry.subscribe('agent.*', (event, detail) => { ... });
245
+
246
+ // Subscribe to everything (use sparingly)
247
+ host.eventRegistry.subscribe('*', (event, detail) => { ... });
248
+ ```
249
+
250
+ **Known events** (from `SpurEventMap`):
251
+
252
+ | Event | Payload |
253
+ |---|---|
254
+ | `agent.run.start` | `{ agent, prompt, cwd? }` |
255
+ | `agent.run.complete` | `{ agent, exitCode, durationMs }` |
256
+ | `agent.run.error` | `{ agent, error }` |
257
+ | `workflow.transition` | `{ workflowId, from, to }` |
258
+ | `rule.evaluate` | `{ ruleId, result }` |
259
+ | `usage.record` | `{ tokens, model, timestamp }` |
260
+ | `history.import.start` | `{ source }` |
261
+ | `history.import.complete` | `{ source, count }` |
262
+ | `plugin.load` | `{ name, version }` |
263
+ | `plugin.unload` | `{ name }` |
264
+ | `plugin.error` | `{ name, error }` |
265
+
266
+ High-churn events (`usage.record`) have a built-in token-bucket throttle.
267
+
268
+ ## Within Spur (internal usage)
269
+
270
+ Spur's own rule engine and workflow engine register as plugins through this SDK:
271
+
272
+ ```typescript
273
+ // apps/server/src/plugins.ts
274
+ import { PluginHost } from '@gobing-ai/spur-plugin-sdk';
275
+
276
+ const host = new PluginHost(bus, { logger });
277
+ await host.loadPlugin(ruleEnginePlugin, { source: 'builtin', pluginName: 'spur-rule-engine', trustLevel: 'bundled' });
278
+ await host.loadPlugin(workflowEnginePlugin, { source: 'builtin', pluginName: 'spur-workflow-engine', trustLevel: 'bundled' });
279
+
280
+ // Built-in commands use seedBuiltin before loading any external plugins
281
+ host.commands.seedBuiltin('spur-rule-run', { name: 'spur-rule-run', execute: ruleCommandHandler });
282
+ ```
283
+
284
+ External plugins are discovered via `PluginLoader` (in `@gobing-ai/spur-app`) from three roots:
285
+ 1. **Bundled** — `packages/plugin-sdk/src/builtins/`
286
+ 2. **User-global** — `~/.spur/plugins/`
287
+ 3. **Project-local** — `.spur/plugins/`
288
+
289
+ ## Error Handling
290
+
291
+ | Error | When |
292
+ |---|---|
293
+ | `PluginManifestError` | `plugin.yaml` fails Zod validation |
294
+ | `PluginCollisionError` | Two plugins register the same `capability:name` pair |
295
+ | `PluginTrustError` | Capability registration denied by trust policy |
296
+ | `PluginNotDeclaredError` | Capability not declared in `plugin.yaml` manifest |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur-plugin-sdk",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Spur Plugin SDK — types, schemas, and runtime for building Spur plugins with trust levels, capability registries, and event hooks.",
5
5
  "keywords": [
6
6
  "spur",