@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.
|
|
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.
|
|
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
|
-
|
|
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;
|