@majkapp/plugin-kit 1.0.0
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.
- package/README.md +636 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/plugin-kit.d.ts +39 -0
- package/dist/plugin-kit.d.ts.map +1 -0
- package/dist/plugin-kit.js +695 -0
- package/dist/types.d.ts +170 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
# @majk/plugin-kit
|
|
2
|
+
|
|
3
|
+
**Fluent builder framework for creating robust MAJK plugins**
|
|
4
|
+
|
|
5
|
+
Build type-safe, production-ready MAJK plugins with excellent developer experience, comprehensive validation, and clear error messages.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
โจ **Fluent API** - Chainable builder pattern with full TypeScript support
|
|
10
|
+
๐ก๏ธ **Type Safety** - Compile-time checks for routes, IDs, and descriptions
|
|
11
|
+
โ
**Build-Time Validation** - Catches errors before runtime
|
|
12
|
+
๐ **Clear Error Messages** - Actionable suggestions when things go wrong
|
|
13
|
+
๐ **Auto HTTP Server** - Built-in routing, CORS, error handling
|
|
14
|
+
โ๏ธ **React & HTML Screens** - Support for both SPA and simple HTML UIs
|
|
15
|
+
๐ง **Tool Management** - Declare tools with schema validation
|
|
16
|
+
๐พ **Storage Integration** - Direct access to plugin storage
|
|
17
|
+
๐ก **Event Bus** - Subscribe to system events
|
|
18
|
+
๐งน **Auto Cleanup** - Managed lifecycle with automatic resource cleanup
|
|
19
|
+
โค๏ธ **Health Checks** - Built-in health monitoring
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @majk/plugin-kit
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { definePlugin } from '@majk/plugin-kit';
|
|
31
|
+
|
|
32
|
+
export default definePlugin('my-plugin', 'My Plugin', '1.0.0')
|
|
33
|
+
.ui({ appDir: 'ui/dist' })
|
|
34
|
+
|
|
35
|
+
.topbar('/plugin-screens/my-plugin/dashboard', {
|
|
36
|
+
icon: '๐'
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
.screenReact({
|
|
40
|
+
id: 'dashboard',
|
|
41
|
+
name: 'Dashboard',
|
|
42
|
+
description: 'Main dashboard for my plugin. Shows key metrics and actions.',
|
|
43
|
+
route: '/plugin-screens/my-plugin/dashboard',
|
|
44
|
+
reactPath: '/'
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
.apiRoute({
|
|
48
|
+
method: 'GET',
|
|
49
|
+
path: '/api/data',
|
|
50
|
+
name: 'Get Data',
|
|
51
|
+
description: 'Retrieves plugin data. Returns formatted response with metadata.',
|
|
52
|
+
handler: async (req, res, { majk, storage }) => {
|
|
53
|
+
const data = await storage.get('data') || [];
|
|
54
|
+
return { data, count: data.length };
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
.tool('global', {
|
|
59
|
+
name: 'myTool',
|
|
60
|
+
description: 'Does something useful. Processes input and returns results.',
|
|
61
|
+
inputSchema: {
|
|
62
|
+
type: 'object',
|
|
63
|
+
properties: {
|
|
64
|
+
param: { type: 'string' }
|
|
65
|
+
},
|
|
66
|
+
required: ['param']
|
|
67
|
+
}
|
|
68
|
+
}, async (input, { logger }) => {
|
|
69
|
+
logger.info(`Tool called with: ${input.param}`);
|
|
70
|
+
return { success: true, result: input.param.toUpperCase() };
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
.build();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Core Concepts
|
|
77
|
+
|
|
78
|
+
### Plugin Definition
|
|
79
|
+
|
|
80
|
+
Every plugin starts with `definePlugin(id, name, version)`:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
definePlugin('system-explorer', 'System Explorer', '1.0.0')
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Rules:**
|
|
87
|
+
- `id` must be unique and URL-safe (kebab-case recommended)
|
|
88
|
+
- `name` is the display name
|
|
89
|
+
- `version` follows semver
|
|
90
|
+
|
|
91
|
+
### Screens
|
|
92
|
+
|
|
93
|
+
#### React Screens
|
|
94
|
+
|
|
95
|
+
For React SPAs, configure UI first:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
.ui({
|
|
99
|
+
appDir: 'ui/dist', // Where your built React app is
|
|
100
|
+
base: '/', // Base URL for the SPA
|
|
101
|
+
history: 'browser' // 'browser' or 'hash' routing
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
.screenReact({
|
|
105
|
+
id: 'dashboard',
|
|
106
|
+
name: 'Dashboard',
|
|
107
|
+
description: 'Main dashboard view. Shows metrics and controls.', // 2-3 sentences
|
|
108
|
+
route: '/plugin-screens/my-plugin/dashboard', // Must start with /plugin-screens/{id}/
|
|
109
|
+
reactPath: '/' // Path within your React app
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**The React app receives:**
|
|
114
|
+
- `window.__MAJK_BASE_URL__` - Host base URL
|
|
115
|
+
- `window.__MAJK_IFRAME_BASE__` - Plugin base path
|
|
116
|
+
- `window.__MAJK_PLUGIN_ID__` - Your plugin ID
|
|
117
|
+
|
|
118
|
+
#### HTML Screens
|
|
119
|
+
|
|
120
|
+
For simple HTML pages:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
.screenHtml({
|
|
124
|
+
id: 'about',
|
|
125
|
+
name: 'About',
|
|
126
|
+
description: 'Information about the plugin. Shows version and author.',
|
|
127
|
+
route: '/plugin-screens/my-plugin/about',
|
|
128
|
+
html: '<html>...' // OR htmlFile: 'about.html'
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### API Routes
|
|
133
|
+
|
|
134
|
+
Define REST endpoints:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
.apiRoute({
|
|
138
|
+
method: 'POST',
|
|
139
|
+
path: '/api/tasks/:id/complete',
|
|
140
|
+
name: 'Complete Task',
|
|
141
|
+
description: 'Marks a task as complete. Updates task status and triggers notifications.',
|
|
142
|
+
handler: async (req, res, { majk, storage, logger }) => {
|
|
143
|
+
const { id } = req.params; // Path parameters
|
|
144
|
+
const { note } = req.body; // Request body
|
|
145
|
+
const status = req.query.get('status'); // Query params
|
|
146
|
+
|
|
147
|
+
logger.info(`Completing task ${id}`);
|
|
148
|
+
|
|
149
|
+
// Access MAJK APIs
|
|
150
|
+
const todos = await majk.todos.list();
|
|
151
|
+
|
|
152
|
+
// Use plugin storage
|
|
153
|
+
await storage.set(`task:${id}`, { completed: true });
|
|
154
|
+
|
|
155
|
+
return { success: true, taskId: id };
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Available Methods:** `GET`, `POST`, `PUT`, `PATCH`, `DELETE`
|
|
161
|
+
|
|
162
|
+
**Context Provided:**
|
|
163
|
+
- `majk` - Full MAJK API interface
|
|
164
|
+
- `storage` - Plugin-scoped key-value storage
|
|
165
|
+
- `logger` - Scoped logger (debug, info, warn, error)
|
|
166
|
+
- `http` - HTTP configuration (port, baseUrl, secret)
|
|
167
|
+
|
|
168
|
+
### Tools
|
|
169
|
+
|
|
170
|
+
Tools are functions that agents can invoke:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
.tool(
|
|
174
|
+
'conversation', // Scope: 'global' | 'conversation' | 'teammate' | 'project'
|
|
175
|
+
{
|
|
176
|
+
name: 'analyzeSentiment',
|
|
177
|
+
description: 'Analyzes text sentiment. Returns positive, negative, or neutral classification.',
|
|
178
|
+
inputSchema: {
|
|
179
|
+
type: 'object',
|
|
180
|
+
properties: {
|
|
181
|
+
text: { type: 'string' }
|
|
182
|
+
},
|
|
183
|
+
required: ['text']
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
async (input, { majk, logger }) => {
|
|
187
|
+
logger.info('Analyzing sentiment');
|
|
188
|
+
|
|
189
|
+
// Your implementation
|
|
190
|
+
const sentiment = analyzeSentiment(input.text);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
data: { sentiment, confidence: 0.95 }
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Tool Scopes:**
|
|
201
|
+
- `global` - Available everywhere
|
|
202
|
+
- `conversation` - Scoped to conversations
|
|
203
|
+
- `teammate` - Scoped to teammates
|
|
204
|
+
- `project` - Scoped to projects
|
|
205
|
+
|
|
206
|
+
### Entities
|
|
207
|
+
|
|
208
|
+
Declare entities your plugin provides:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
.entity('teammate', [
|
|
212
|
+
{
|
|
213
|
+
id: 'bot-assistant',
|
|
214
|
+
name: 'Bot Assistant',
|
|
215
|
+
role: 'bot',
|
|
216
|
+
capabilities: ['analysis', 'reporting']
|
|
217
|
+
}
|
|
218
|
+
])
|
|
219
|
+
|
|
220
|
+
.entity('mcpServer', [
|
|
221
|
+
{
|
|
222
|
+
id: 'custom-server',
|
|
223
|
+
name: 'Custom MCP Server',
|
|
224
|
+
transport: { type: 'stdio', command: 'node', args: ['server.js'] }
|
|
225
|
+
}
|
|
226
|
+
])
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Supported Entity Types:**
|
|
230
|
+
- `mcpServer` - MCP servers
|
|
231
|
+
- `teammate` - Team members/bots
|
|
232
|
+
- `conversation` - Conversations
|
|
233
|
+
- `todo` - Tasks
|
|
234
|
+
- `project` - Projects
|
|
235
|
+
- `agent` - AI agents
|
|
236
|
+
|
|
237
|
+
### Config Wizard & Settings
|
|
238
|
+
|
|
239
|
+
#### Config Wizard
|
|
240
|
+
|
|
241
|
+
Show a wizard on first run:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
.configWizard({
|
|
245
|
+
path: '/setup',
|
|
246
|
+
title: 'Initial Setup',
|
|
247
|
+
width: 600,
|
|
248
|
+
height: 400,
|
|
249
|
+
description: 'Configure plugin settings. Set up API keys and preferences.',
|
|
250
|
+
shouldShow: async (ctx) => {
|
|
251
|
+
const config = await ctx.storage.get('config');
|
|
252
|
+
return !config; // Show if no config exists
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
#### Settings Screen
|
|
258
|
+
|
|
259
|
+
Ongoing settings management:
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
.settings({
|
|
263
|
+
path: '/settings',
|
|
264
|
+
title: 'Plugin Settings',
|
|
265
|
+
description: 'Manage plugin configuration. Adjust behavior and display options.'
|
|
266
|
+
})
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Lifecycle Hooks
|
|
270
|
+
|
|
271
|
+
#### onReady
|
|
272
|
+
|
|
273
|
+
Called after server starts, before `onLoad` completes:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
.onReady(async (ctx, cleanup) => {
|
|
277
|
+
// Subscribe to events
|
|
278
|
+
const sub = ctx.majk.eventBus.conversations().subscribe((event) => {
|
|
279
|
+
ctx.logger.info(`Conversation event: ${event.type}`);
|
|
280
|
+
});
|
|
281
|
+
cleanup(() => sub.unsubscribe());
|
|
282
|
+
|
|
283
|
+
// Set up timers
|
|
284
|
+
const timer = setInterval(() => {
|
|
285
|
+
ctx.logger.debug('Periodic check');
|
|
286
|
+
}, 60000);
|
|
287
|
+
cleanup(() => clearInterval(timer));
|
|
288
|
+
|
|
289
|
+
// Any other setup
|
|
290
|
+
await loadData(ctx.storage);
|
|
291
|
+
})
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Cleanup Registration:**
|
|
295
|
+
All cleanup functions are automatically called on `onUnload()`.
|
|
296
|
+
|
|
297
|
+
#### Health Checks
|
|
298
|
+
|
|
299
|
+
Define custom health monitoring:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
.health(async ({ majk, storage, logger }) => {
|
|
303
|
+
try {
|
|
304
|
+
// Check dependencies
|
|
305
|
+
await majk.conversations.list();
|
|
306
|
+
await storage.get('health-check');
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
healthy: true,
|
|
310
|
+
details: { api: 'ok', storage: 'ok' }
|
|
311
|
+
};
|
|
312
|
+
} catch (error) {
|
|
313
|
+
logger.error(`Health check failed: ${error.message}`);
|
|
314
|
+
return {
|
|
315
|
+
healthy: false,
|
|
316
|
+
details: { error: error.message }
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## API Reference
|
|
323
|
+
|
|
324
|
+
### PluginContext
|
|
325
|
+
|
|
326
|
+
Provided to all handlers and hooks:
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
interface PluginContext {
|
|
330
|
+
pluginId: string; // Your plugin ID
|
|
331
|
+
pluginRoot: string; // Plugin directory path
|
|
332
|
+
dataDir: string; // Plugin data directory
|
|
333
|
+
|
|
334
|
+
app: {
|
|
335
|
+
version: string; // MAJK version
|
|
336
|
+
name: string; // App name
|
|
337
|
+
appDataDir: string; // App data directory
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
http: {
|
|
341
|
+
port: number; // Assigned HTTP port
|
|
342
|
+
secret: string; // Security secret
|
|
343
|
+
baseUrl: string; // Base URL for iframe
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
majk: MajkInterface; // Full MAJK API
|
|
347
|
+
storage: PluginStorage; // Key-value storage
|
|
348
|
+
logger: PluginLogger; // Scoped logger
|
|
349
|
+
timers?: ScopedTimers; // Managed timers
|
|
350
|
+
ipc?: ScopedIpcRegistry; // Electron IPC
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### MajkInterface
|
|
355
|
+
|
|
356
|
+
The main MAJK API:
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
interface MajkInterface {
|
|
360
|
+
conversations: ConversationAPI;
|
|
361
|
+
todos: TodoAPI;
|
|
362
|
+
projects: ProjectAPI;
|
|
363
|
+
teammates: TeammateAPI;
|
|
364
|
+
mcpServers: MCPServerAPI;
|
|
365
|
+
knowledge: KnowledgeAPI;
|
|
366
|
+
tasks: TaskAPI;
|
|
367
|
+
eventBus: EventBusAPI;
|
|
368
|
+
auth: AuthAPI;
|
|
369
|
+
secrets: SecretsAPI;
|
|
370
|
+
plugins: PluginManagementAPI;
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### PluginStorage
|
|
375
|
+
|
|
376
|
+
Simple key-value storage scoped to your plugin:
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
interface PluginStorage {
|
|
380
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
381
|
+
set<T>(key: string, value: T): Promise<void>;
|
|
382
|
+
delete(key: string): Promise<void>;
|
|
383
|
+
clear(): Promise<void>;
|
|
384
|
+
keys(): Promise<string[]>;
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Example:**
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
// Save data
|
|
392
|
+
await storage.set('user-preferences', {
|
|
393
|
+
theme: 'dark',
|
|
394
|
+
notifications: true
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// Load data
|
|
398
|
+
const prefs = await storage.get<Preferences>('user-preferences');
|
|
399
|
+
|
|
400
|
+
// List all keys
|
|
401
|
+
const keys = await storage.keys();
|
|
402
|
+
|
|
403
|
+
// Delete specific key
|
|
404
|
+
await storage.delete('old-data');
|
|
405
|
+
|
|
406
|
+
// Clear everything
|
|
407
|
+
await storage.clear();
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### EventBus
|
|
411
|
+
|
|
412
|
+
Subscribe to system events:
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
// Listen to conversation events
|
|
416
|
+
const sub = majk.eventBus.conversations().subscribe((event) => {
|
|
417
|
+
console.log(`Event: ${event.type}`, event.entity);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// Unsubscribe
|
|
421
|
+
sub.unsubscribe();
|
|
422
|
+
|
|
423
|
+
// Specific event types
|
|
424
|
+
majk.eventBus.conversations().created().subscribe(...);
|
|
425
|
+
majk.eventBus.conversations().updated().subscribe(...);
|
|
426
|
+
majk.eventBus.conversations().deleted().subscribe(...);
|
|
427
|
+
|
|
428
|
+
// Custom channels
|
|
429
|
+
majk.eventBus.channel('my-events').subscribe(...);
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
## Validation & Error Handling
|
|
433
|
+
|
|
434
|
+
### Build-Time Validation
|
|
435
|
+
|
|
436
|
+
The kit validates at build time:
|
|
437
|
+
|
|
438
|
+
โ
**Route Prefixes** - Screen routes must match plugin ID
|
|
439
|
+
โ
**File Existence** - React dist and HTML files must exist
|
|
440
|
+
โ
**Uniqueness** - No duplicate routes, tools, or API endpoints
|
|
441
|
+
โ
**Dependencies** - UI must be configured for React screens
|
|
442
|
+
โ
**Descriptions** - Must be 2-3 sentences ending with period
|
|
443
|
+
|
|
444
|
+
**Example Error:**
|
|
445
|
+
|
|
446
|
+
```
|
|
447
|
+
โ Plugin Build Failed: React screen route must start with "/plugin-screens/my-plugin/"
|
|
448
|
+
๐ก Suggestion: Change route from "/screens/dashboard" to "/plugin-screens/my-plugin/dashboard"
|
|
449
|
+
๐ Context: {
|
|
450
|
+
"screen": "dashboard",
|
|
451
|
+
"route": "/screens/dashboard"
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Runtime Error Handling
|
|
456
|
+
|
|
457
|
+
All API route errors are automatically caught and logged:
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
.apiRoute({
|
|
461
|
+
method: 'POST',
|
|
462
|
+
path: '/api/process',
|
|
463
|
+
name: 'Process Data',
|
|
464
|
+
description: 'Processes input data. Validates and transforms the payload.',
|
|
465
|
+
handler: async (req, res, { logger }) => {
|
|
466
|
+
// Errors are automatically caught and returned as 500 responses
|
|
467
|
+
throw new Error('Processing failed');
|
|
468
|
+
|
|
469
|
+
// Returns:
|
|
470
|
+
// {
|
|
471
|
+
// "error": "Processing failed",
|
|
472
|
+
// "route": "Process Data",
|
|
473
|
+
// "path": "/api/process"
|
|
474
|
+
// }
|
|
475
|
+
}
|
|
476
|
+
})
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Logs show:
|
|
480
|
+
```
|
|
481
|
+
โ POST /api/process - Error: Processing failed
|
|
482
|
+
[stack trace]
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
## Best Practices
|
|
486
|
+
|
|
487
|
+
### 1. Use Storage for State
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
// โ Don't use in-memory state
|
|
491
|
+
let cache = {};
|
|
492
|
+
|
|
493
|
+
// โ
Use storage
|
|
494
|
+
await ctx.storage.set('cache', data);
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### 2. Register Cleanups
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
.onReady(async (ctx, cleanup) => {
|
|
501
|
+
// โ Don't forget to cleanup
|
|
502
|
+
const timer = setInterval(...);
|
|
503
|
+
|
|
504
|
+
// โ
Register cleanup
|
|
505
|
+
const timer = setInterval(...);
|
|
506
|
+
cleanup(() => clearInterval(timer));
|
|
507
|
+
|
|
508
|
+
// โ
Event subscriptions
|
|
509
|
+
const sub = ctx.majk.eventBus.conversations().subscribe(...);
|
|
510
|
+
cleanup(() => sub.unsubscribe());
|
|
511
|
+
})
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### 3. Validate Input
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
.apiRoute({
|
|
518
|
+
method: 'POST',
|
|
519
|
+
path: '/api/create',
|
|
520
|
+
name: 'Create Item',
|
|
521
|
+
description: 'Creates a new item. Validates input before processing.',
|
|
522
|
+
handler: async (req, res) => {
|
|
523
|
+
// โ
Validate input
|
|
524
|
+
if (!req.body?.name) {
|
|
525
|
+
res.status(400).json({ error: 'Name is required' });
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Process...
|
|
530
|
+
}
|
|
531
|
+
})
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### 4. Use Structured Logging
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
// โ Basic logging
|
|
538
|
+
logger.info('User action');
|
|
539
|
+
|
|
540
|
+
// โ
Structured logging
|
|
541
|
+
logger.info('User action', { userId, action: 'create', resourceId });
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### 5. Handle Errors Gracefully
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
.tool('global', spec, async (input, { logger }) => {
|
|
548
|
+
try {
|
|
549
|
+
const result = await processData(input);
|
|
550
|
+
return { success: true, data: result };
|
|
551
|
+
} catch (error) {
|
|
552
|
+
logger.error(`Tool failed: ${error.message}`);
|
|
553
|
+
return {
|
|
554
|
+
success: false,
|
|
555
|
+
error: error.message,
|
|
556
|
+
code: 'PROCESSING_ERROR'
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
})
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## Examples
|
|
563
|
+
|
|
564
|
+
See `example.ts` for a comprehensive example showing:
|
|
565
|
+
- React and HTML screens
|
|
566
|
+
- API routes with parameters
|
|
567
|
+
- Tools in different scopes
|
|
568
|
+
- Entity declarations
|
|
569
|
+
- Config wizard
|
|
570
|
+
- Event subscriptions
|
|
571
|
+
- Storage usage
|
|
572
|
+
- Health checks
|
|
573
|
+
|
|
574
|
+
## TypeScript
|
|
575
|
+
|
|
576
|
+
Full TypeScript support with:
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
import {
|
|
580
|
+
definePlugin,
|
|
581
|
+
FluentBuilder,
|
|
582
|
+
PluginContext,
|
|
583
|
+
RequestLike,
|
|
584
|
+
ResponseLike
|
|
585
|
+
} from '@majk/plugin-kit';
|
|
586
|
+
|
|
587
|
+
// Type-safe plugin ID
|
|
588
|
+
const plugin = definePlugin('my-plugin', 'My Plugin', '1.0.0');
|
|
589
|
+
// ^ Enforces route prefixes
|
|
590
|
+
|
|
591
|
+
// Type-safe routes
|
|
592
|
+
.screenReact({
|
|
593
|
+
route: '/plugin-screens/my-plugin/dashboard'
|
|
594
|
+
// ^^^^^^^^^ Must match plugin ID
|
|
595
|
+
})
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
## Troubleshooting
|
|
599
|
+
|
|
600
|
+
### "React app not built"
|
|
601
|
+
|
|
602
|
+
```
|
|
603
|
+
โ Plugin Build Failed: React app not built: /path/to/ui/dist/index.html does not exist
|
|
604
|
+
๐ก Suggestion: Run "npm run build" in your UI directory to build the React app
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
**Fix:** Build your React app before building the plugin.
|
|
608
|
+
|
|
609
|
+
### "Duplicate API route"
|
|
610
|
+
|
|
611
|
+
```
|
|
612
|
+
โ Plugin Build Failed: Duplicate API route: POST /api/data
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
**Fix:** Each route (method + path) must be unique.
|
|
616
|
+
|
|
617
|
+
### "Description must be 2-3 sentences"
|
|
618
|
+
|
|
619
|
+
```
|
|
620
|
+
โ Plugin Build Failed: Description for "My Screen" must be 2-3 sentences, found 1 sentences
|
|
621
|
+
๐ก Suggestion: Rewrite the description to have 2-3 clear sentences.
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
**Fix:** Write 2-3 complete sentences ending with periods.
|
|
625
|
+
|
|
626
|
+
### "Tool names must be unique"
|
|
627
|
+
|
|
628
|
+
```
|
|
629
|
+
โ Plugin Build Failed: Duplicate tool name: "analyze"
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
**Fix:** Each tool name must be unique within the plugin.
|
|
633
|
+
|
|
634
|
+
## License
|
|
635
|
+
|
|
636
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @majk/plugin-kit
|
|
3
|
+
*
|
|
4
|
+
* Fluent builder framework for creating robust MAJK plugins with:
|
|
5
|
+
* - Type-safe route and tool definitions
|
|
6
|
+
* - Compile-time and build-time validation
|
|
7
|
+
* - Clear, actionable error messages
|
|
8
|
+
* - Automatic HTTP server management
|
|
9
|
+
* - Built-in CORS, error handling, and logging
|
|
10
|
+
* - React SPA and HTML screen support
|
|
11
|
+
* - Entity, tool, and API route declarations
|
|
12
|
+
* - Lifecycle hooks with cleanup management
|
|
13
|
+
*/
|
|
14
|
+
export { definePlugin, FluentBuilder } from './plugin-kit';
|
|
15
|
+
export type { PluginContext, PluginLogger, PluginStorage, ScopedTimers, ScopedIpcRegistry, PluginCapabilities, PluginCapability, ToolImplementation, InProcessPlugin, PluginHealthStatus, ToolSpec, ToolHandler, ApiMethod, ApiRouteDef, RouteHandler, RequestLike, ResponseLike, UiConfig, HistoryMode, ScreenBase, ReactScreen, HtmlScreen, ConfigWizardDef, SettingsDef, Scope, EntityType, CleanupFn, HealthCheckFn } from './types';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE3D,YAAY,EACV,aAAa,EACb,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,QAAQ,EACR,WAAW,EACX,SAAS,EACT,WAAW,EACX,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,UAAU,EACV,WAAW,EACX,UAAU,EACV,eAAe,EACf,WAAW,EACX,KAAK,EACL,UAAU,EACV,SAAS,EACT,aAAa,EACd,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @majk/plugin-kit
|
|
4
|
+
*
|
|
5
|
+
* Fluent builder framework for creating robust MAJK plugins with:
|
|
6
|
+
* - Type-safe route and tool definitions
|
|
7
|
+
* - Compile-time and build-time validation
|
|
8
|
+
* - Clear, actionable error messages
|
|
9
|
+
* - Automatic HTTP server management
|
|
10
|
+
* - Built-in CORS, error handling, and logging
|
|
11
|
+
* - React SPA and HTML screen support
|
|
12
|
+
* - Entity, tool, and API route declarations
|
|
13
|
+
* - Lifecycle hooks with cleanup management
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.definePlugin = void 0;
|
|
17
|
+
var plugin_kit_1 = require("./plugin-kit");
|
|
18
|
+
Object.defineProperty(exports, "definePlugin", { enumerable: true, get: function () { return plugin_kit_1.definePlugin; } });
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { PluginContext, InProcessPlugin, ToolSpec, ToolHandler, ApiRouteDef, UiConfig, ReactScreen, HtmlScreen, ConfigWizardDef, SettingsDef, Scope, EntityType, CleanupFn, HealthCheckFn } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Fluent Builder Interface
|
|
4
|
+
*/
|
|
5
|
+
export interface FluentBuilder<Id extends string> {
|
|
6
|
+
/** Add a topbar item that navigates to a screen */
|
|
7
|
+
topbar(route: `/plugin-screens/${Id}/${string}`, opts?: {
|
|
8
|
+
icon?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
}): this;
|
|
12
|
+
/** Configure UI settings for React SPA */
|
|
13
|
+
ui(config?: UiConfig): this;
|
|
14
|
+
/** Add a React screen */
|
|
15
|
+
screenReact(screen: ReactScreen<Id>): this;
|
|
16
|
+
/** Add an HTML screen */
|
|
17
|
+
screenHtml(screen: HtmlScreen<Id>): this;
|
|
18
|
+
/** Add an API route */
|
|
19
|
+
apiRoute(route: ApiRouteDef): this;
|
|
20
|
+
/** Add a tool */
|
|
21
|
+
tool(scope: Scope, spec: ToolSpec, handler: ToolHandler): this;
|
|
22
|
+
/** Declare entities that this plugin provides */
|
|
23
|
+
entity(entityType: EntityType, entities: any[]): this;
|
|
24
|
+
/** Add config wizard */
|
|
25
|
+
configWizard(def: ConfigWizardDef): this;
|
|
26
|
+
/** Add settings screen */
|
|
27
|
+
settings(def: SettingsDef): this;
|
|
28
|
+
/** Hook called after server starts */
|
|
29
|
+
onReady(fn: (ctx: PluginContext, cleanup: (fn: CleanupFn) => void) => void | Promise<void>): this;
|
|
30
|
+
/** Custom health check */
|
|
31
|
+
health(fn: HealthCheckFn): this;
|
|
32
|
+
/** Build the plugin */
|
|
33
|
+
build(): InProcessPlugin;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create a plugin with fluent builder API
|
|
37
|
+
*/
|
|
38
|
+
export declare function definePlugin<const Id extends string>(id: Id, name: string, version: string): FluentBuilder<Id>;
|
|
39
|
+
//# sourceMappingURL=plugin-kit.d.ts.map
|