@majkapp/plugin-kit 3.2.1 → 3.3.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.
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Promptable CLI for @majkapp/plugin-kit
5
+ * Makes the plugin kit documentation available in LLM-friendly formats
6
+ */
7
+
8
+ import { createPromptable } from '@juleswhite/promptable';
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname, join } from 'path';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+
15
+ const cli = createPromptable({
16
+ title: '@majkapp/plugin-kit',
17
+ description: 'Build MAJK plugins with functions, UI screens, and services',
18
+ docsDir: join(__dirname, '../docs'),
19
+ packageCommand: 'npx @majkapp/plugin-kit',
20
+ views: {
21
+ llm: 'INDEX.md',
22
+ functions: 'FUNCTIONS.md',
23
+ screens: 'SCREENS.md',
24
+ hooks: 'HOOKS.md',
25
+ context: 'CONTEXT.md',
26
+ services: 'SERVICES.md',
27
+ lifecycle: 'LIFECYCLE.md',
28
+ testing: 'TESTING.md',
29
+ config: 'CONFIG.md',
30
+ api: 'API.md',
31
+ full: 'FULL.md'
32
+ }
33
+ });
34
+
35
+ cli.parse(process.argv);
package/docs/API.md ADDED
@@ -0,0 +1,394 @@
1
+ # API Reference
2
+
3
+ Quick reference for @majkapp/plugin-kit API.
4
+
5
+ ## definePlugin()
6
+
7
+ ```typescript
8
+ import { definePlugin } from '@majkapp/plugin-kit';
9
+
10
+ const plugin = definePlugin(id, name, version)
11
+ .pluginRoot(__dirname) // REQUIRED FIRST
12
+ .function(/* ... */)
13
+ .screenReact(/* ... */)
14
+ .service(/* ... */)
15
+ .onReady(/* ... */)
16
+ .build();
17
+
18
+ export = plugin;
19
+ ```
20
+
21
+ ### Parameters
22
+ - `id` - Unique plugin ID (lowercase, hyphens)
23
+ - `name` - Display name
24
+ - `version` - Semantic version (e.g., '1.0.0')
25
+
26
+ ## .pluginRoot()
27
+
28
+ **REQUIRED.** Must be called first.
29
+
30
+ ```typescript
31
+ .pluginRoot(__dirname) // Enables resource loading
32
+ ```
33
+
34
+ ## .function()
35
+
36
+ Define a callable function.
37
+
38
+ ```typescript
39
+ .function(name, {
40
+ description: string, // REQUIRED: 2-3 sentences
41
+ input: JsonSchema, // REQUIRED: Input validation schema
42
+ output: JsonSchema, // REQUIRED: Output validation schema
43
+ handler: async (input, ctx) => { /* ... */ }, // REQUIRED
44
+ tags: string[], // Optional: ['monitoring', 'health']
45
+ deprecated: boolean // Optional: Mark as deprecated
46
+ })
47
+ ```
48
+
49
+ ### Handler Context (ctx)
50
+
51
+ ```typescript
52
+ handler: async (input, ctx) => {
53
+ // Logging
54
+ ctx.logger.debug(message, data);
55
+ ctx.logger.info(message, data);
56
+ ctx.logger.warn(message, data);
57
+ ctx.logger.error(message, data);
58
+
59
+ // Storage
60
+ await ctx.storage.get(key);
61
+ await ctx.storage.set(key, value);
62
+ await ctx.storage.has(key);
63
+ await ctx.storage.delete(key);
64
+ await ctx.storage.clear();
65
+
66
+ // MAJK APIs
67
+ await ctx.majk.conversations.list();
68
+ await ctx.majk.conversations.get(id);
69
+ await ctx.majk.todos.list();
70
+ await ctx.majk.todos.get(id);
71
+ await ctx.majk.projects.list();
72
+ await ctx.majk.projects.get(id);
73
+ await ctx.majk.teammates.list();
74
+ await ctx.majk.teammates.get(id);
75
+ await ctx.majk.mcpServers.list();
76
+ await ctx.majk.mcpServers.get(id);
77
+ await ctx.majk.eventBus.subscribeAll(handler); // Use in .onReady()
78
+
79
+ return { /* output matching schema */ };
80
+ }
81
+ ```
82
+
83
+ ## .screenReact()
84
+
85
+ Define a React screen.
86
+
87
+ ```typescript
88
+ .screenReact({
89
+ id: string, // Unique ID
90
+ name: string, // Display name
91
+ description: string, // 2-3 sentences
92
+ route: `/plugin-screens/${id}/path`, // MUST start with /plugin-screens/{id}/
93
+ pluginPath: string, // Path to built React app (e.g., '/index.html')
94
+ pluginPathHash: string // Optional: Hash route (e.g., '#/dashboard')
95
+ })
96
+ ```
97
+
98
+ ### Multiple Screens
99
+
100
+ ```typescript
101
+ .screenReact({
102
+ id: 'my-plugin-dashboard',
103
+ name: 'Dashboard',
104
+ description: 'Main dashboard',
105
+ route: '/plugin-screens/my-plugin/dashboard',
106
+ pluginPath: '/index.html',
107
+ pluginPathHash: '#/'
108
+ })
109
+
110
+ .screenReact({
111
+ id: 'my-plugin-settings',
112
+ name: 'Settings',
113
+ description: 'Plugin settings',
114
+ route: '/plugin-screens/my-plugin/settings',
115
+ pluginPath: '/index.html',
116
+ pluginPathHash: '#/settings'
117
+ })
118
+ ```
119
+
120
+ ## .service()
121
+
122
+ Group functions into a service.
123
+
124
+ ```typescript
125
+ .service(serviceName, {
126
+ type: string, // Service type identifier
127
+ metadata: {
128
+ name: string, // Display name
129
+ description: string, // Service description
130
+ version: string, // Service version
131
+ author?: string,
132
+ homepage?: string,
133
+ tags?: string[]
134
+ },
135
+ discoverable: boolean // Allow discovery by other plugins
136
+ })
137
+ .withFunction(functionName, {
138
+ examples?: string[], // Usage examples
139
+ tags?: string[], // Additional tags
140
+ metadata?: Record<string, any> // Custom metadata
141
+ })
142
+ .withFunction(/* ... */)
143
+ .endService()
144
+ ```
145
+
146
+ ### Service Naming
147
+
148
+ Format: `plugin:{plugin-id}:{service-name}`
149
+
150
+ Example: `plugin:my-plugin:monitoring`
151
+
152
+ ## .onReady()
153
+
154
+ Lifecycle hook called when plugin loads.
155
+
156
+ ```typescript
157
+ .onReady(async (ctx, cleanup) => {
158
+ // Initialization code
159
+ const subscription = await ctx.majk.eventBus.subscribeAll(handler);
160
+
161
+ // Register cleanup (called on unload)
162
+ cleanup(() => {
163
+ subscription.unsubscribe();
164
+ });
165
+ })
166
+ ```
167
+
168
+ ## Generated React Hooks
169
+
170
+ Auto-generated from functions in `ui/src/generated/hooks.ts`.
171
+
172
+ ### Query Hooks (Auto-Fetch)
173
+
174
+ ```typescript
175
+ const { data, error, loading, refetch } = useHealth(
176
+ input?, // Optional input parameters
177
+ options?: {
178
+ enabled?: boolean, // Only fetch if true (default: true)
179
+ refetchInterval?: number // Auto-refetch interval in ms
180
+ }
181
+ );
182
+ ```
183
+
184
+ ### Mutation Hooks (Manual)
185
+
186
+ ```typescript
187
+ const { mutate, data, error, loading } = useClearEvents();
188
+
189
+ // Call mutate manually
190
+ await mutate(input);
191
+ ```
192
+
193
+ ## Testing
194
+
195
+ ### Function Testing
196
+
197
+ ```typescript
198
+ import { test, invoke, mock } from '@majkapp/plugin-test';
199
+ import assert from 'assert';
200
+
201
+ test('test name', async () => {
202
+ const context = mock()
203
+ .storage({ key: 'value' })
204
+ .withMajkData({
205
+ conversations: [{ id: 'c1', title: 'Test' }],
206
+ todos: [{ id: 't1', status: 'pending' }],
207
+ projects: [{ id: 'p1', name: 'Project' }]
208
+ })
209
+ .build();
210
+
211
+ const result = await invoke('functionName', { input }, { context });
212
+
213
+ assert.strictEqual(result.expected, 'value');
214
+ });
215
+ ```
216
+
217
+ ### UI Testing
218
+
219
+ ```typescript
220
+ import { test, bot, mock } from '@majkapp/plugin-test';
221
+ import assert from 'assert';
222
+
223
+ test('test name', async () => {
224
+ const context = mock()
225
+ .withMockHandler('health', async () => ({
226
+ status: 'ok',
227
+ timestamp: new Date().toISOString()
228
+ }))
229
+ .build();
230
+
231
+ const b = await bot(context);
232
+ await b.goto('/plugin-screens/my-plugin/main');
233
+
234
+ await b.waitForSelector('[data-majk-id="element"][data-majk-state="populated"]');
235
+
236
+ const text = await b.getText('[data-majk-id="element"]');
237
+ assert.ok(text.includes('expected'));
238
+
239
+ await b.screenshot('test.png');
240
+ await b.close();
241
+ });
242
+ ```
243
+
244
+ ## data-majk-* Attributes
245
+
246
+ ### data-majk-id
247
+
248
+ Identifies elements for testing.
249
+
250
+ ```typescript
251
+ <button data-majk-id="saveButton">Save</button>
252
+ <div data-majk-id="totalCount">{count}</div>
253
+ ```
254
+
255
+ ### data-majk-state
256
+
257
+ Tracks UI state for testing.
258
+
259
+ ```typescript
260
+ <div
261
+ data-majk-id="container"
262
+ data-majk-state={loading ? 'loading' : data ? 'populated' : 'empty'}
263
+ >
264
+ {/* content */}
265
+ </div>
266
+ ```
267
+
268
+ Common states: `loading`, `empty`, `populated`, `error`, `idle`, `processing`
269
+
270
+ ## JSON Schema Types
271
+
272
+ ```typescript
273
+ // String
274
+ { type: 'string' }
275
+ { type: 'string', enum: ['value1', 'value2'] }
276
+ { type: 'string', format: 'date-time' }
277
+
278
+ // Number
279
+ { type: 'number' }
280
+ { type: 'number', minimum: 0, maximum: 100 }
281
+
282
+ // Boolean
283
+ { type: 'boolean' }
284
+
285
+ // Object
286
+ {
287
+ type: 'object',
288
+ properties: {
289
+ field1: { type: 'string' },
290
+ field2: { type: 'number' }
291
+ },
292
+ required: ['field1'],
293
+ additionalProperties: false
294
+ }
295
+
296
+ // Array
297
+ {
298
+ type: 'array',
299
+ items: { type: 'string' }
300
+ }
301
+
302
+ // Array of objects
303
+ {
304
+ type: 'array',
305
+ items: {
306
+ type: 'object',
307
+ properties: { /* ... */ }
308
+ }
309
+ }
310
+ ```
311
+
312
+ ## Common Patterns
313
+
314
+ ### uiLog Function
315
+
316
+ ```typescript
317
+ .function('uiLog', {
318
+ description: 'Log messages from UI for debugging',
319
+ input: {
320
+ type: 'object',
321
+ properties: {
322
+ level: { type: 'string', enum: ['debug', 'info', 'warn', 'error'] },
323
+ component: { type: 'string' },
324
+ message: { type: 'string' },
325
+ data: { type: 'object' }
326
+ },
327
+ required: ['level', 'component', 'message']
328
+ },
329
+ output: {
330
+ type: 'object',
331
+ properties: { success: { type: 'boolean' } }
332
+ },
333
+ handler: async (input, ctx) => {
334
+ ctx.logger[input.level](`[UI:${input.component}] ${input.message}`, input.data);
335
+ return { success: true };
336
+ }
337
+ })
338
+ ```
339
+
340
+ ### Health Check Function
341
+
342
+ ```typescript
343
+ .function('health', {
344
+ description: 'Check plugin health status',
345
+ input: {
346
+ type: 'object',
347
+ properties: {},
348
+ additionalProperties: false
349
+ },
350
+ output: {
351
+ type: 'object',
352
+ properties: {
353
+ status: { type: 'string', enum: ['ok', 'error'] },
354
+ timestamp: { type: 'string', format: 'date-time' }
355
+ },
356
+ required: ['status', 'timestamp']
357
+ },
358
+ handler: async (_, ctx) => ({
359
+ status: 'ok',
360
+ timestamp: new Date().toISOString()
361
+ })
362
+ })
363
+ ```
364
+
365
+ ### Event Bus Subscription
366
+
367
+ ```typescript
368
+ .onReady(async (ctx, cleanup) => {
369
+ const subscription = await ctx.majk.eventBus.subscribeAll((event) => {
370
+ ctx.logger.info('Event', {
371
+ entityType: event.entityType,
372
+ eventType: event.type,
373
+ entityId: event.entity?.id
374
+ });
375
+ });
376
+
377
+ cleanup(() => {
378
+ subscription.unsubscribe();
379
+ });
380
+ })
381
+ ```
382
+
383
+ ## Next Steps
384
+
385
+ Run `npx @majkapp/plugin-kit` - Quick start guide
386
+ Run `npx @majkapp/plugin-kit --functions` - Function patterns
387
+ Run `npx @majkapp/plugin-kit --screens` - Screen configuration
388
+ Run `npx @majkapp/plugin-kit --hooks` - Generated hooks
389
+ Run `npx @majkapp/plugin-kit --context` - Context API
390
+ Run `npx @majkapp/plugin-kit --services` - Service grouping
391
+ Run `npx @majkapp/plugin-kit --lifecycle` - Lifecycle hooks
392
+ Run `npx @majkapp/plugin-kit --testing` - Testing guide
393
+ Run `npx @majkapp/plugin-kit --config` - Project configuration
394
+ Run `npx @majkapp/plugin-kit --full` - Complete reference