@donkeylabs/cli 0.6.4 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkeylabs/cli",
3
- "version": "0.6.4",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "description": "CLI for @donkeylabs/server - project scaffolding and code generation",
6
6
  "main": "./src/index.ts",
@@ -35,7 +35,7 @@
35
35
  "@types/prompts": "^2.4.9"
36
36
  },
37
37
  "peerDependencies": {
38
- "@donkeylabs/server": "^0.4.0"
38
+ "@donkeylabs/server": "^1.0.0"
39
39
  },
40
40
  "keywords": [
41
41
  "cli",
@@ -66,6 +66,26 @@
66
66
  let eventName = $state("demo.test");
67
67
  let eventData = $state('{"hello": "world"}');
68
68
 
69
+ // Audit state
70
+ let auditAction = $state("user.login");
71
+ let auditResource = $state("user");
72
+ let auditResourceId = $state("user-123");
73
+ let auditEntries = $state<Array<{
74
+ id: string;
75
+ timestamp: string;
76
+ action: string;
77
+ actor: string;
78
+ resource: string;
79
+ resourceId?: string;
80
+ metadata?: Record<string, any>;
81
+ }>>([]);
82
+ let lastAuditId = $state<string | null>(null);
83
+
84
+ // WebSocket state
85
+ let wsChannel = $state("demo");
86
+ let wsMessage = $state("Hello from browser!");
87
+ let wsClientCount = $state(0);
88
+
69
89
  // Counter actions - using typed client
70
90
  async function counterAction(
71
91
  action: "get" | "increment" | "decrement" | "reset",
@@ -170,6 +190,50 @@
170
190
  };
171
191
  }
172
192
 
193
+ // Audit actions - using typed client
194
+ async function auditLogEntry() {
195
+ const result = await client.api.audit.log({
196
+ action: auditAction,
197
+ resource: auditResource,
198
+ resourceId: auditResourceId,
199
+ metadata: { browser: true, timestamp: Date.now() },
200
+ }) as { id: string };
201
+ lastAuditId = result.id;
202
+ refreshAuditEntries();
203
+ }
204
+
205
+ async function refreshAuditEntries() {
206
+ const result = await client.api.audit.query({ limit: 10 }) as { entries: typeof auditEntries };
207
+ auditEntries = result.entries;
208
+ }
209
+
210
+ // WebSocket actions - using typed client
211
+ async function wsBroadcastMessage() {
212
+ await client.api.websocket.broadcast({
213
+ channel: wsChannel,
214
+ event: "chat",
215
+ data: {
216
+ message: wsMessage,
217
+ timestamp: new Date().toISOString(),
218
+ },
219
+ });
220
+ }
221
+
222
+ async function wsBroadcastAll() {
223
+ await client.api.websocket.broadcastAll({
224
+ event: "announcement",
225
+ data: {
226
+ message: wsMessage,
227
+ timestamp: new Date().toISOString(),
228
+ },
229
+ });
230
+ }
231
+
232
+ async function refreshWsClients() {
233
+ const result = await client.api.websocket.clientCount({}) as { count: number };
234
+ wsClientCount = result.count;
235
+ }
236
+
173
237
  onMount(() => {
174
238
  if (!browser) return;
175
239
 
@@ -178,6 +242,8 @@
178
242
  refreshJobStats();
179
243
  refreshCronTasks();
180
244
  refreshSSEClients();
245
+ refreshAuditEntries();
246
+ refreshWsClients();
181
247
 
182
248
  // SSE subscription using the typed client
183
249
  const unsubscribe = client.sse.subscribe(
@@ -483,6 +549,73 @@
483
549
  </p>
484
550
  </CardContent>
485
551
  </Card>
552
+
553
+ <!-- Audit Demo -->
554
+ <Card>
555
+ <CardHeader>
556
+ <CardTitle>Audit Trail</CardTitle>
557
+ <CardDescription>Compliance logging and tracking</CardDescription>
558
+ </CardHeader>
559
+ <CardContent class="space-y-3">
560
+ <div class="flex gap-2">
561
+ <Input bind:value={auditAction} placeholder="Action" class="flex-1" />
562
+ <Input bind:value={auditResource} placeholder="Resource" class="flex-1" />
563
+ </div>
564
+ <Input bind:value={auditResourceId} placeholder="Resource ID (optional)" />
565
+ <div class="flex gap-2">
566
+ <Button onclick={auditLogEntry} size="sm">Log Entry</Button>
567
+ <Button onclick={refreshAuditEntries} size="sm" variant="outline">Refresh</Button>
568
+ </div>
569
+ {#if lastAuditId}
570
+ <p class="text-xs text-muted-foreground">
571
+ Last: <code class="bg-muted px-1 rounded">{lastAuditId}</code>
572
+ </p>
573
+ {/if}
574
+ {#if auditEntries.length > 0}
575
+ <div class="max-h-32 overflow-y-auto space-y-1">
576
+ {#each auditEntries.slice(0, 5) as entry}
577
+ <div class="text-xs p-2 bg-muted rounded flex justify-between items-center">
578
+ <span class="font-medium">{entry.action}</span>
579
+ <span class="text-muted-foreground">{entry.resource}</span>
580
+ <span class="text-muted-foreground">{new Date(entry.timestamp).toLocaleTimeString()}</span>
581
+ </div>
582
+ {/each}
583
+ </div>
584
+ {:else}
585
+ <p class="text-xs text-muted-foreground italic">No audit entries yet</p>
586
+ {/if}
587
+ </CardContent>
588
+ </Card>
589
+
590
+ <!-- WebSocket Demo -->
591
+ <Card>
592
+ <CardHeader>
593
+ <CardTitle>WebSocket</CardTitle>
594
+ <CardDescription>Bidirectional real-time messaging</CardDescription>
595
+ </CardHeader>
596
+ <CardContent class="space-y-3">
597
+ <div class="flex gap-2">
598
+ <Input bind:value={wsChannel} placeholder="Channel" class="w-28" />
599
+ <Input bind:value={wsMessage} placeholder="Message" class="flex-1" />
600
+ </div>
601
+ <div class="flex gap-2">
602
+ <Button onclick={wsBroadcastMessage} size="sm">Broadcast</Button>
603
+ <Button onclick={wsBroadcastAll} size="sm" variant="secondary">Broadcast All</Button>
604
+ <Button onclick={refreshWsClients} size="sm" variant="outline">Refresh</Button>
605
+ </div>
606
+ <div class="flex items-center gap-2">
607
+ <span class="relative flex h-2 w-2">
608
+ <span class="relative inline-flex rounded-full h-2 w-2 {wsClientCount > 0 ? 'bg-green-500' : 'bg-gray-400'}"></span>
609
+ </span>
610
+ <span class="text-xs text-muted-foreground">
611
+ {wsClientCount} WebSocket client{wsClientCount !== 1 ? 's' : ''} connected
612
+ </span>
613
+ </div>
614
+ <p class="text-xs text-muted-foreground italic">
615
+ Messages logged to audit trail automatically
616
+ </p>
617
+ </CardContent>
618
+ </Card>
486
619
  </div>
487
620
 
488
621
  <!-- SSE Events Stream - Full Width -->
@@ -104,6 +104,68 @@ export const demoPlugin = createPlugin.define({
104
104
  total: ctx.core.sse.getClients().length,
105
105
  byChannel: ctx.core.sse.getClientsByChannel("events").length,
106
106
  }),
107
+
108
+ // Audit helpers - compliance and tracking
109
+ auditLog: async (action: string, resource: string, resourceId?: string, metadata?: Record<string, any>) => {
110
+ const id = await ctx.core.audit.log({
111
+ action,
112
+ actor: "demo-user", // In real apps, get from auth context
113
+ resource,
114
+ resourceId,
115
+ metadata,
116
+ });
117
+ return { id };
118
+ },
119
+ auditQuery: async (filters: { action?: string; resource?: string; limit?: number }) => {
120
+ const entries = await ctx.core.audit.query({
121
+ action: filters.action,
122
+ resource: filters.resource,
123
+ limit: filters.limit ?? 10,
124
+ });
125
+ return {
126
+ entries: entries.map(e => ({
127
+ id: e.id,
128
+ timestamp: e.timestamp.toISOString(),
129
+ action: e.action,
130
+ actor: e.actor,
131
+ resource: e.resource,
132
+ resourceId: e.resourceId,
133
+ metadata: e.metadata,
134
+ })),
135
+ };
136
+ },
137
+ auditGetByResource: async (resource: string, resourceId: string) => {
138
+ const entries = await ctx.core.audit.getByResource(resource, resourceId);
139
+ return {
140
+ entries: entries.map(e => ({
141
+ id: e.id,
142
+ timestamp: e.timestamp.toISOString(),
143
+ action: e.action,
144
+ actor: e.actor,
145
+ metadata: e.metadata,
146
+ })),
147
+ };
148
+ },
149
+
150
+ // WebSocket helpers - bidirectional real-time communication
151
+ wsBroadcast: (channel: string, event: string, data: any) => {
152
+ ctx.core.websocket.broadcast(channel, event, data);
153
+ return { success: true };
154
+ },
155
+ wsBroadcastAll: (event: string, data: any) => {
156
+ ctx.core.websocket.broadcastAll(event, data);
157
+ return { success: true };
158
+ },
159
+ wsGetClients: (channel?: string) => {
160
+ const clients = ctx.core.websocket.getClients(channel);
161
+ return {
162
+ count: clients.length,
163
+ clients,
164
+ };
165
+ },
166
+ wsGetClientCount: (channel?: string) => {
167
+ return { count: ctx.core.websocket.getClientCount(channel) };
168
+ },
107
169
  };
108
170
  },
109
171
  init: async (ctx) => {
@@ -139,6 +201,58 @@ export const demoPlugin = createPlugin.define({
139
201
  });
140
202
  });
141
203
 
142
- ctx.core.logger.info("Demo plugin initialized with all core services");
204
+ // WebSocket message handler - echo messages back and broadcast to channel
205
+ ctx.core.websocket.onMessage(async (clientId, event, data) => {
206
+ ctx.core.logger.info("WebSocket message received", { clientId, event, data });
207
+
208
+ // Echo the message back to the sender
209
+ if (event === "echo") {
210
+ ctx.core.websocket.send(clientId, "echo-reply", {
211
+ original: data,
212
+ timestamp: new Date().toISOString(),
213
+ });
214
+ }
215
+
216
+ // Broadcast to a channel if requested
217
+ if (event === "broadcast" && data?.channel) {
218
+ ctx.core.websocket.broadcast(data.channel, "ws-broadcast", {
219
+ from: clientId,
220
+ message: data.message,
221
+ timestamp: new Date().toISOString(),
222
+ });
223
+ }
224
+
225
+ // Log WebSocket activity to audit trail
226
+ await ctx.core.audit.log({
227
+ action: "websocket.message",
228
+ actor: clientId,
229
+ resource: "websocket",
230
+ resourceId: event,
231
+ metadata: { event, dataSize: JSON.stringify(data).length },
232
+ });
233
+ });
234
+
235
+ // Audit important events for compliance tracking
236
+ ctx.core.events.on("job.completed", async (data: any) => {
237
+ await ctx.core.audit.log({
238
+ action: "job.completed",
239
+ actor: "system",
240
+ resource: "job",
241
+ resourceId: data.jobId,
242
+ metadata: { name: data.name },
243
+ });
244
+ });
245
+
246
+ ctx.core.events.on("workflow.completed", async (data: any) => {
247
+ await ctx.core.audit.log({
248
+ action: "workflow.completed",
249
+ actor: "system",
250
+ resource: "workflow",
251
+ resourceId: data.instanceId,
252
+ metadata: { workflowName: data.workflowName },
253
+ });
254
+ });
255
+
256
+ ctx.core.logger.info("Demo plugin initialized with all core services (including audit & websocket)");
143
257
  },
144
258
  });
@@ -316,4 +316,143 @@ demo.route("workflow.cancel").typed(
316
316
  })
317
317
  );
318
318
 
319
+ // =============================================================================
320
+ // AUDIT - Compliance and tracking (via plugin service)
321
+ // =============================================================================
322
+
323
+ demo.route("audit.log").typed(
324
+ defineRoute({
325
+ input: z.object({
326
+ action: z.string(),
327
+ resource: z.string(),
328
+ resourceId: z.string().optional(),
329
+ metadata: z.record(z.any()).optional(),
330
+ }),
331
+ output: z.object({ id: z.string() }),
332
+ handle: async (input, ctx) => {
333
+ return ctx.plugins.demo.auditLog(
334
+ input.action,
335
+ input.resource,
336
+ input.resourceId,
337
+ input.metadata
338
+ );
339
+ },
340
+ })
341
+ );
342
+
343
+ demo.route("audit.query").typed(
344
+ defineRoute({
345
+ input: z.object({
346
+ action: z.string().optional(),
347
+ resource: z.string().optional(),
348
+ limit: z.number().optional().default(10),
349
+ }),
350
+ output: z.object({
351
+ entries: z.array(
352
+ z.object({
353
+ id: z.string(),
354
+ timestamp: z.string(),
355
+ action: z.string(),
356
+ actor: z.string(),
357
+ resource: z.string(),
358
+ resourceId: z.string().optional(),
359
+ metadata: z.record(z.any()).optional(),
360
+ })
361
+ ),
362
+ }),
363
+ handle: async (input, ctx) => {
364
+ return ctx.plugins.demo.auditQuery({
365
+ action: input.action,
366
+ resource: input.resource,
367
+ limit: input.limit ?? 10,
368
+ });
369
+ },
370
+ })
371
+ );
372
+
373
+ demo.route("audit.byResource").typed(
374
+ defineRoute({
375
+ input: z.object({
376
+ resource: z.string(),
377
+ resourceId: z.string(),
378
+ }),
379
+ output: z.object({
380
+ entries: z.array(
381
+ z.object({
382
+ id: z.string(),
383
+ timestamp: z.string(),
384
+ action: z.string(),
385
+ actor: z.string(),
386
+ metadata: z.record(z.any()).optional(),
387
+ })
388
+ ),
389
+ }),
390
+ handle: async (input, ctx) => {
391
+ return ctx.plugins.demo.auditGetByResource(input.resource, input.resourceId);
392
+ },
393
+ })
394
+ );
395
+
396
+ // =============================================================================
397
+ // WEBSOCKET - Bidirectional real-time communication (via plugin service)
398
+ // =============================================================================
399
+
400
+ demo.route("websocket.broadcast").typed(
401
+ defineRoute({
402
+ input: z.object({
403
+ channel: z.string(),
404
+ event: z.string().default("message"),
405
+ data: z.any(),
406
+ }),
407
+ output: z.object({ success: z.boolean() }),
408
+ handle: async (input, ctx) => {
409
+ return ctx.plugins.demo.wsBroadcast(
410
+ input.channel,
411
+ input.event ?? "message",
412
+ input.data
413
+ );
414
+ },
415
+ })
416
+ );
417
+
418
+ demo.route("websocket.broadcastAll").typed(
419
+ defineRoute({
420
+ input: z.object({
421
+ event: z.string().default("message"),
422
+ data: z.any(),
423
+ }),
424
+ output: z.object({ success: z.boolean() }),
425
+ handle: async (input, ctx) => {
426
+ return ctx.plugins.demo.wsBroadcastAll(input.event ?? "message", input.data);
427
+ },
428
+ })
429
+ );
430
+
431
+ demo.route("websocket.clients").typed(
432
+ defineRoute({
433
+ input: z.object({
434
+ channel: z.string().optional(),
435
+ }),
436
+ output: z.object({
437
+ count: z.number(),
438
+ clients: z.array(z.string()),
439
+ }),
440
+ handle: async (input, ctx) => {
441
+ return ctx.plugins.demo.wsGetClients(input.channel);
442
+ },
443
+ })
444
+ );
445
+
446
+ demo.route("websocket.clientCount").typed(
447
+ defineRoute({
448
+ input: z.object({
449
+ channel: z.string().optional(),
450
+ }),
451
+ output: z.object({ count: z.number() }),
452
+ handle: async (input, ctx) => {
453
+ return ctx.plugins.demo.wsGetClientCount(input.channel);
454
+ },
455
+ })
456
+ );
457
+
319
458
  export default demo;