@agentuity/cli 0.1.15 → 0.1.17
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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +18 -2
- package/dist/cli.js.map +1 -1
- package/dist/cmd/ai/opencode/install.js +1 -1
- package/dist/cmd/ai/opencode/install.js.map +1 -1
- package/dist/cmd/build/ast.d.ts.map +1 -1
- package/dist/cmd/build/ast.js +68 -2
- package/dist/cmd/build/ast.js.map +1 -1
- package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/registry-generator.js +112 -23
- package/dist/cmd/build/vite/registry-generator.js.map +1 -1
- package/dist/cmd/build/vite/route-discovery.d.ts +4 -0
- package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/route-discovery.js +4 -0
- package/dist/cmd/build/vite/route-discovery.js.map +1 -1
- package/dist/cmd/cloud/env/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/env/delete.js +93 -34
- package/dist/cmd/cloud/env/delete.js.map +1 -1
- package/dist/cmd/cloud/env/get.d.ts.map +1 -1
- package/dist/cmd/cloud/env/get.js +53 -16
- package/dist/cmd/cloud/env/get.js.map +1 -1
- package/dist/cmd/cloud/env/import.d.ts.map +1 -1
- package/dist/cmd/cloud/env/import.js +80 -39
- package/dist/cmd/cloud/env/import.js.map +1 -1
- package/dist/cmd/cloud/env/index.d.ts.map +1 -1
- package/dist/cmd/cloud/env/index.js +6 -2
- package/dist/cmd/cloud/env/index.js.map +1 -1
- package/dist/cmd/cloud/env/list.d.ts.map +1 -1
- package/dist/cmd/cloud/env/list.js +99 -23
- package/dist/cmd/cloud/env/list.js.map +1 -1
- package/dist/cmd/cloud/env/org-util.d.ts +16 -0
- package/dist/cmd/cloud/env/org-util.d.ts.map +1 -0
- package/dist/cmd/cloud/env/org-util.js +28 -0
- package/dist/cmd/cloud/env/org-util.js.map +1 -0
- package/dist/cmd/cloud/env/pull.d.ts.map +1 -1
- package/dist/cmd/cloud/env/pull.js +61 -29
- package/dist/cmd/cloud/env/pull.js.map +1 -1
- package/dist/cmd/cloud/env/push.d.ts.map +1 -1
- package/dist/cmd/cloud/env/push.js +70 -30
- package/dist/cmd/cloud/env/push.js.map +1 -1
- package/dist/cmd/cloud/env/set.d.ts.map +1 -1
- package/dist/cmd/cloud/env/set.js +72 -26
- package/dist/cmd/cloud/env/set.js.map +1 -1
- package/dist/cmd/cloud/index.d.ts.map +1 -1
- package/dist/cmd/cloud/index.js +2 -0
- package/dist/cmd/cloud/index.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/create-namespace.js +1 -1
- package/dist/cmd/cloud/keyvalue/create-namespace.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/delete-namespace.js +2 -2
- package/dist/cmd/cloud/keyvalue/delete-namespace.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/delete.js +1 -1
- package/dist/cmd/cloud/keyvalue/delete.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/get.js +1 -1
- package/dist/cmd/cloud/keyvalue/get.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/index.js +1 -1
- package/dist/cmd/cloud/keyvalue/index.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/keys.js +1 -1
- package/dist/cmd/cloud/keyvalue/keys.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/list-namespaces.js +1 -1
- package/dist/cmd/cloud/keyvalue/list-namespaces.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/repl.d.ts.map +1 -1
- package/dist/cmd/cloud/keyvalue/repl.js +8 -5
- package/dist/cmd/cloud/keyvalue/repl.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/search.js +1 -1
- package/dist/cmd/cloud/keyvalue/search.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/set.js +1 -1
- package/dist/cmd/cloud/keyvalue/set.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/stats.js +1 -1
- package/dist/cmd/cloud/keyvalue/stats.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/util.d.ts +4 -4
- package/dist/cmd/cloud/keyvalue/util.d.ts.map +1 -1
- package/dist/cmd/cloud/keyvalue/util.js +4 -9
- package/dist/cmd/cloud/keyvalue/util.js.map +1 -1
- package/dist/cmd/cloud/queue/ack.d.ts +3 -0
- package/dist/cmd/cloud/queue/ack.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/ack.js +45 -0
- package/dist/cmd/cloud/queue/ack.js.map +1 -0
- package/dist/cmd/cloud/queue/create.d.ts +3 -0
- package/dist/cmd/cloud/queue/create.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/create.js +80 -0
- package/dist/cmd/cloud/queue/create.js.map +1 -0
- package/dist/cmd/cloud/queue/delete.d.ts +3 -0
- package/dist/cmd/cloud/queue/delete.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/delete.js +50 -0
- package/dist/cmd/cloud/queue/delete.js.map +1 -0
- package/dist/cmd/cloud/queue/destinations.d.ts +3 -0
- package/dist/cmd/cloud/queue/destinations.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/destinations.js +232 -0
- package/dist/cmd/cloud/queue/destinations.js.map +1 -0
- package/dist/cmd/cloud/queue/dlq.d.ts +3 -0
- package/dist/cmd/cloud/queue/dlq.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/dlq.js +168 -0
- package/dist/cmd/cloud/queue/dlq.js.map +1 -0
- package/dist/cmd/cloud/queue/get.d.ts +3 -0
- package/dist/cmd/cloud/queue/get.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/get.js +130 -0
- package/dist/cmd/cloud/queue/get.js.map +1 -0
- package/dist/cmd/cloud/queue/index.d.ts +3 -0
- package/dist/cmd/cloud/queue/index.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/index.js +65 -0
- package/dist/cmd/cloud/queue/index.js.map +1 -0
- package/dist/cmd/cloud/queue/list.d.ts +3 -0
- package/dist/cmd/cloud/queue/list.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/list.js +71 -0
- package/dist/cmd/cloud/queue/list.js.map +1 -0
- package/dist/cmd/cloud/queue/messages.d.ts +3 -0
- package/dist/cmd/cloud/queue/messages.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/messages.js +137 -0
- package/dist/cmd/cloud/queue/messages.js.map +1 -0
- package/dist/cmd/cloud/queue/nack.d.ts +3 -0
- package/dist/cmd/cloud/queue/nack.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/nack.js +45 -0
- package/dist/cmd/cloud/queue/nack.js.map +1 -0
- package/dist/cmd/cloud/queue/pause.d.ts +3 -0
- package/dist/cmd/cloud/queue/pause.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/pause.js +36 -0
- package/dist/cmd/cloud/queue/pause.js.map +1 -0
- package/dist/cmd/cloud/queue/publish.d.ts +3 -0
- package/dist/cmd/cloud/queue/publish.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/publish.js +76 -0
- package/dist/cmd/cloud/queue/publish.js.map +1 -0
- package/dist/cmd/cloud/queue/receive.d.ts +3 -0
- package/dist/cmd/cloud/queue/receive.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/receive.js +67 -0
- package/dist/cmd/cloud/queue/receive.js.map +1 -0
- package/dist/cmd/cloud/queue/resume.d.ts +3 -0
- package/dist/cmd/cloud/queue/resume.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/resume.js +35 -0
- package/dist/cmd/cloud/queue/resume.js.map +1 -0
- package/dist/cmd/cloud/queue/sources.d.ts +3 -0
- package/dist/cmd/cloud/queue/sources.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/sources.js +290 -0
- package/dist/cmd/cloud/queue/sources.js.map +1 -0
- package/dist/cmd/cloud/queue/stats.d.ts +3 -0
- package/dist/cmd/cloud/queue/stats.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/stats.js +239 -0
- package/dist/cmd/cloud/queue/stats.js.map +1 -0
- package/dist/cmd/cloud/queue/util.d.ts +26 -0
- package/dist/cmd/cloud/queue/util.d.ts.map +1 -0
- package/dist/cmd/cloud/queue/util.js +19 -0
- package/dist/cmd/cloud/queue/util.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/build.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/build.js +122 -28
- package/dist/cmd/cloud/sandbox/snapshot/build.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/create.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/create.js +19 -7
- package/dist/cmd/cloud/sandbox/snapshot/create.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/get.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/get.js +16 -0
- package/dist/cmd/cloud/sandbox/snapshot/get.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/list.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/list.js +4 -0
- package/dist/cmd/cloud/sandbox/snapshot/list.js.map +1 -1
- package/dist/cmd/cloud/vector/stats.d.ts.map +1 -1
- package/dist/cmd/cloud/vector/stats.js +8 -0
- package/dist/cmd/cloud/vector/stats.js.map +1 -1
- package/dist/cmd/project/create.d.ts.map +1 -1
- package/dist/cmd/project/create.js +12 -0
- package/dist/cmd/project/create.js.map +1 -1
- package/dist/cmd/project/template-flow.d.ts +3 -0
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.js +157 -68
- package/dist/cmd/project/template-flow.js.map +1 -1
- package/dist/cmd/setup/index.d.ts.map +1 -1
- package/dist/cmd/setup/index.js +2 -1
- package/dist/cmd/setup/index.js.map +1 -1
- package/dist/env-util.d.ts +6 -1
- package/dist/env-util.d.ts.map +1 -1
- package/dist/env-util.js +16 -2
- package/dist/env-util.js.map +1 -1
- package/dist/errors.d.ts +4 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +6 -0
- package/dist/errors.js.map +1 -1
- package/dist/onboarding/agentPrompt.d.ts +8 -0
- package/dist/onboarding/agentPrompt.d.ts.map +1 -0
- package/dist/onboarding/agentPrompt.js +263 -0
- package/dist/onboarding/agentPrompt.js.map +1 -0
- package/dist/schema-generator.d.ts +1 -1
- package/dist/schema-generator.d.ts.map +1 -1
- package/dist/schema-parser.d.ts +1 -1
- package/dist/schema-parser.d.ts.map +1 -1
- package/dist/schema-parser.js +36 -1
- package/dist/schema-parser.js.map +1 -1
- package/dist/tui/box.d.ts +4 -0
- package/dist/tui/box.d.ts.map +1 -1
- package/dist/tui/box.js +39 -0
- package/dist/tui/box.js.map +1 -1
- package/dist/tui.d.ts +11 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +33 -15
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +6 -6
- package/src/cli.ts +19 -2
- package/src/cmd/ai/opencode/install.ts +1 -1
- package/src/cmd/build/ast.ts +88 -2
- package/src/cmd/build/vite/registry-generator.ts +120 -24
- package/src/cmd/build/vite/route-discovery.ts +16 -0
- package/src/cmd/cloud/env/delete.ts +113 -41
- package/src/cmd/cloud/env/get.ts +60 -16
- package/src/cmd/cloud/env/import.ts +92 -44
- package/src/cmd/cloud/env/index.ts +6 -2
- package/src/cmd/cloud/env/list.ts +112 -27
- package/src/cmd/cloud/env/org-util.ts +37 -0
- package/src/cmd/cloud/env/pull.ts +72 -31
- package/src/cmd/cloud/env/push.ts +84 -35
- package/src/cmd/cloud/env/set.ts +89 -33
- package/src/cmd/cloud/index.ts +2 -0
- package/src/cmd/cloud/keyvalue/create-namespace.ts +1 -1
- package/src/cmd/cloud/keyvalue/delete-namespace.ts +2 -2
- package/src/cmd/cloud/keyvalue/delete.ts +1 -1
- package/src/cmd/cloud/keyvalue/get.ts +1 -1
- package/src/cmd/cloud/keyvalue/index.ts +1 -1
- package/src/cmd/cloud/keyvalue/keys.ts +1 -1
- package/src/cmd/cloud/keyvalue/list-namespaces.ts +1 -1
- package/src/cmd/cloud/keyvalue/repl.ts +8 -5
- package/src/cmd/cloud/keyvalue/search.ts +1 -1
- package/src/cmd/cloud/keyvalue/set.ts +1 -1
- package/src/cmd/cloud/keyvalue/stats.ts +1 -1
- package/src/cmd/cloud/keyvalue/util.ts +8 -17
- package/src/cmd/cloud/queue/ack.ts +50 -0
- package/src/cmd/cloud/queue/create.ts +91 -0
- package/src/cmd/cloud/queue/delete.ts +57 -0
- package/src/cmd/cloud/queue/destinations.ts +287 -0
- package/src/cmd/cloud/queue/dlq.ts +203 -0
- package/src/cmd/cloud/queue/get.ts +158 -0
- package/src/cmd/cloud/queue/index.ts +66 -0
- package/src/cmd/cloud/queue/list.ts +81 -0
- package/src/cmd/cloud/queue/messages.ts +160 -0
- package/src/cmd/cloud/queue/nack.ts +50 -0
- package/src/cmd/cloud/queue/pause.ts +41 -0
- package/src/cmd/cloud/queue/publish.ts +88 -0
- package/src/cmd/cloud/queue/receive.ts +76 -0
- package/src/cmd/cloud/queue/resume.ts +40 -0
- package/src/cmd/cloud/queue/sources.ts +352 -0
- package/src/cmd/cloud/queue/stats.ts +297 -0
- package/src/cmd/cloud/queue/util.ts +34 -0
- package/src/cmd/cloud/sandbox/snapshot/build.ts +146 -29
- package/src/cmd/cloud/sandbox/snapshot/create.ts +24 -7
- package/src/cmd/cloud/sandbox/snapshot/get.ts +16 -0
- package/src/cmd/cloud/sandbox/snapshot/list.ts +4 -0
- package/src/cmd/cloud/vector/stats.ts +9 -0
- package/src/cmd/project/create.ts +12 -0
- package/src/cmd/project/template-flow.ts +181 -69
- package/src/cmd/setup/index.ts +2 -1
- package/src/env-util.ts +17 -2
- package/src/errors.ts +8 -0
- package/src/onboarding/agentPrompt.ts +263 -0
- package/src/schema-generator.ts +1 -1
- package/src/schema-parser.ts +45 -3
- package/src/tui/box.ts +52 -0
- package/src/tui.ts +47 -17
- package/src/types.ts +0 -1
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createCommand } from '../../../types';
|
|
3
|
+
import * as tui from '../../../tui';
|
|
4
|
+
import { createQueueAPIClient, getQueueApiOptions } from './util';
|
|
5
|
+
import { getCommand } from '../../../command-prefix';
|
|
6
|
+
import {
|
|
7
|
+
getOrgAnalytics,
|
|
8
|
+
getQueueAnalytics,
|
|
9
|
+
streamOrgAnalytics,
|
|
10
|
+
streamQueueAnalytics,
|
|
11
|
+
type OrgAnalytics,
|
|
12
|
+
type QueueAnalytics,
|
|
13
|
+
type SSEStatsEvent,
|
|
14
|
+
} from '@agentuity/server';
|
|
15
|
+
|
|
16
|
+
const StatsResponseSchema = z.union([
|
|
17
|
+
z.object({ type: z.literal('org'), analytics: z.unknown() }),
|
|
18
|
+
z.object({ type: z.literal('queue'), analytics: z.unknown() }),
|
|
19
|
+
z.object({ type: z.literal('stream'), events: z.array(z.unknown()) }),
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
function formatNumber(n: number): string {
|
|
23
|
+
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
24
|
+
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
|
|
25
|
+
return String(n);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function formatPercent(n: number): string {
|
|
29
|
+
return `${n.toFixed(2)}%`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatLatency(ms: number): string {
|
|
33
|
+
if (ms >= 1000) return `${(ms / 1000).toFixed(1)}s`;
|
|
34
|
+
return `${Math.round(ms)}ms`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatDuration(start: string, end: string): string {
|
|
38
|
+
const startDate = new Date(start);
|
|
39
|
+
const endDate = new Date(end);
|
|
40
|
+
const hours = Math.round((endDate.getTime() - startDate.getTime()) / 3600000);
|
|
41
|
+
if (hours >= 24) return `${Math.round(hours / 24)}d`;
|
|
42
|
+
return `${hours}h`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function displayOrgAnalytics(analytics: OrgAnalytics): void {
|
|
46
|
+
const { summary, queues, period } = analytics;
|
|
47
|
+
|
|
48
|
+
tui.info(`Organization Analytics (${formatDuration(period.start, period.end)})`);
|
|
49
|
+
tui.newline();
|
|
50
|
+
console.log(tui.colorPrimary('Summary:'));
|
|
51
|
+
console.log(` ${tui.muted('Total Queues:')} ${summary.total_queues}`);
|
|
52
|
+
console.log(
|
|
53
|
+
` ${tui.muted('Published:')} ${formatNumber(summary.total_messages_published)}`
|
|
54
|
+
);
|
|
55
|
+
console.log(
|
|
56
|
+
` ${tui.muted('Delivered:')} ${formatNumber(summary.total_messages_delivered)}`
|
|
57
|
+
);
|
|
58
|
+
console.log(
|
|
59
|
+
` ${tui.muted('Acknowledged:')} ${formatNumber(summary.total_messages_acknowledged)}`
|
|
60
|
+
);
|
|
61
|
+
console.log(` ${tui.muted('DLQ Messages:')} ${formatNumber(summary.total_dlq_messages)}`);
|
|
62
|
+
console.log(` ${tui.muted('Avg Latency:')} ${formatLatency(summary.avg_latency_ms)}`);
|
|
63
|
+
console.log(` ${tui.muted('P95 Latency:')} ${formatLatency(summary.p95_latency_ms)}`);
|
|
64
|
+
console.log(` ${tui.muted('Error Rate:')} ${formatPercent(summary.error_rate_percent)}`);
|
|
65
|
+
|
|
66
|
+
if (queues.length > 0) {
|
|
67
|
+
tui.newline();
|
|
68
|
+
console.log(tui.colorPrimary('Queues:'));
|
|
69
|
+
const tableData = queues.map((q) => ({
|
|
70
|
+
Name: q.name,
|
|
71
|
+
Type: q.queue_type,
|
|
72
|
+
Published: formatNumber(q.messages_published),
|
|
73
|
+
Delivered: formatNumber(q.messages_delivered),
|
|
74
|
+
Backlog: formatNumber(q.backlog),
|
|
75
|
+
DLQ: formatNumber(q.dlq_count),
|
|
76
|
+
'Avg Latency': formatLatency(q.avg_latency_ms),
|
|
77
|
+
'Error %': formatPercent(q.error_rate_percent),
|
|
78
|
+
}));
|
|
79
|
+
tui.table(tableData);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function displayQueueAnalytics(analytics: QueueAnalytics): void {
|
|
84
|
+
const { queue_name, queue_type, period, current, period_stats, latency, consumer_latency } =
|
|
85
|
+
analytics;
|
|
86
|
+
|
|
87
|
+
tui.info(`Queue: ${queue_name} (${queue_type})`);
|
|
88
|
+
console.log(tui.colorWarning(`Period: ${formatDuration(period.start, period.end)}`));
|
|
89
|
+
tui.newline();
|
|
90
|
+
|
|
91
|
+
console.log(tui.colorPrimary('Current State:'));
|
|
92
|
+
console.log(` ${tui.muted('Backlog:')} ${formatNumber(current.backlog)}`);
|
|
93
|
+
console.log(` ${tui.muted('In-Flight:')} ${formatNumber(current.messages_in_flight)}`);
|
|
94
|
+
console.log(` ${tui.muted('DLQ:')} ${formatNumber(current.dlq_count)}`);
|
|
95
|
+
console.log(` ${tui.muted('Consumers:')} ${current.active_consumers}`);
|
|
96
|
+
if (current.oldest_message_age_seconds != null) {
|
|
97
|
+
console.log(` ${tui.muted('Oldest Msg Age:')} ${current.oldest_message_age_seconds}s`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
tui.newline();
|
|
101
|
+
console.log(tui.colorPrimary('Period Stats:'));
|
|
102
|
+
console.log(
|
|
103
|
+
` ${tui.muted('Published:')} ${formatNumber(period_stats.messages_published)}`
|
|
104
|
+
);
|
|
105
|
+
console.log(
|
|
106
|
+
` ${tui.muted('Delivered:')} ${formatNumber(period_stats.messages_delivered)}`
|
|
107
|
+
);
|
|
108
|
+
console.log(
|
|
109
|
+
` ${tui.muted('Acknowledged:')} ${formatNumber(period_stats.messages_acknowledged)}`
|
|
110
|
+
);
|
|
111
|
+
console.log(
|
|
112
|
+
` ${tui.muted('Failed:')} ${formatNumber(period_stats.messages_failed)}`
|
|
113
|
+
);
|
|
114
|
+
console.log(
|
|
115
|
+
` ${tui.muted('Replayed:')} ${formatNumber(period_stats.messages_replayed)}`
|
|
116
|
+
);
|
|
117
|
+
console.log(
|
|
118
|
+
` ${tui.muted('Bytes Published:')} ${tui.formatBytes(period_stats.bytes_published)}`
|
|
119
|
+
);
|
|
120
|
+
console.log(
|
|
121
|
+
` ${tui.muted('Delivery Attempts:')} ${formatNumber(period_stats.delivery_attempts)}`
|
|
122
|
+
);
|
|
123
|
+
console.log(` ${tui.muted('Retries:')} ${formatNumber(period_stats.retry_count)}`);
|
|
124
|
+
|
|
125
|
+
tui.newline();
|
|
126
|
+
console.log(tui.colorPrimary('Delivery Latency:'));
|
|
127
|
+
console.log(` ${tui.muted('Average:')} ${formatLatency(latency.avg_ms)}`);
|
|
128
|
+
if (latency.p50_ms != null)
|
|
129
|
+
console.log(` ${tui.muted('Median (P50):')} ${formatLatency(latency.p50_ms)}`);
|
|
130
|
+
if (latency.p95_ms != null)
|
|
131
|
+
console.log(` ${tui.muted('P95:')} ${formatLatency(latency.p95_ms)}`);
|
|
132
|
+
if (latency.p99_ms != null)
|
|
133
|
+
console.log(` ${tui.muted('P99:')} ${formatLatency(latency.p99_ms)}`);
|
|
134
|
+
if (latency.max_ms != null)
|
|
135
|
+
console.log(` ${tui.muted('Max:')} ${formatLatency(latency.max_ms)}`);
|
|
136
|
+
|
|
137
|
+
tui.newline();
|
|
138
|
+
console.log(tui.colorPrimary('Consumer Latency:'));
|
|
139
|
+
console.log(` ${tui.muted('Average:')} ${formatLatency(consumer_latency.avg_ms)}`);
|
|
140
|
+
if (consumer_latency.p50_ms != null)
|
|
141
|
+
console.log(` ${tui.muted('Median (P50):')} ${formatLatency(consumer_latency.p50_ms)}`);
|
|
142
|
+
if (consumer_latency.p95_ms != null)
|
|
143
|
+
console.log(` ${tui.muted('P95:')} ${formatLatency(consumer_latency.p95_ms)}`);
|
|
144
|
+
if (consumer_latency.p99_ms != null)
|
|
145
|
+
console.log(` ${tui.muted('P99:')} ${formatLatency(consumer_latency.p99_ms)}`);
|
|
146
|
+
|
|
147
|
+
if (analytics.destinations && analytics.destinations.length > 0) {
|
|
148
|
+
tui.newline();
|
|
149
|
+
tui.info('Destinations:');
|
|
150
|
+
const destData = analytics.destinations.map((d) => {
|
|
151
|
+
const total = d.success_count + d.failure_count;
|
|
152
|
+
const errorRate = total > 0 ? (d.failure_count / total) * 100 : 0;
|
|
153
|
+
return {
|
|
154
|
+
ID: d.id.slice(0, 12) + '...',
|
|
155
|
+
URL: d.url.length > 40 ? d.url.slice(0, 37) + '...' : d.url,
|
|
156
|
+
Success: formatNumber(d.success_count),
|
|
157
|
+
Failed: formatNumber(d.failure_count),
|
|
158
|
+
'Avg Response': d.avg_response_time_ms ? formatLatency(d.avg_response_time_ms) : '-',
|
|
159
|
+
'Error %': formatPercent(errorRate),
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
tui.table(destData);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function displayStreamEvent(event: SSEStatsEvent, queueName?: string): void {
|
|
167
|
+
const time = new Date(event.timestamp).toLocaleTimeString();
|
|
168
|
+
const prefix = queueName ? `[${queueName}]` : '[org]';
|
|
169
|
+
|
|
170
|
+
process.stdout.write('\x1b[2K\r');
|
|
171
|
+
process.stdout.write(
|
|
172
|
+
`${tui.colorMuted(time)} ${prefix} ` +
|
|
173
|
+
`Backlog: ${tui.colorInfo(formatNumber(event.backlog))} | ` +
|
|
174
|
+
`In-Flight: ${tui.colorInfo(formatNumber(event.messages_in_flight))} | ` +
|
|
175
|
+
`Throughput: ${tui.colorSuccess(formatNumber(event.throughput_1m))}/min | ` +
|
|
176
|
+
`Latency: ${formatLatency(event.avg_latency_ms)} | ` +
|
|
177
|
+
`Errors: ${event.error_rate_1m > 0 ? tui.colorError(String(event.error_rate_1m)) : '0'}/min`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const statsSubcommand = createCommand({
|
|
182
|
+
name: 'stats',
|
|
183
|
+
description: 'View queue analytics and statistics',
|
|
184
|
+
tags: ['read-only', 'requires-auth'],
|
|
185
|
+
requires: { auth: true, org: true },
|
|
186
|
+
examples: [
|
|
187
|
+
{
|
|
188
|
+
command: getCommand('cloud queue stats'),
|
|
189
|
+
description: 'View org-level analytics for all queues',
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
command: getCommand('cloud queue stats --name my-queue'),
|
|
193
|
+
description: 'View detailed analytics for a specific queue',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
command: getCommand('cloud queue stats --live'),
|
|
197
|
+
description: 'Stream real-time stats (Ctrl+C to exit)',
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
command: getCommand('cloud queue stats --name my-queue --live --interval 10'),
|
|
201
|
+
description: 'Stream queue stats every 10 seconds',
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
schema: {
|
|
205
|
+
args: z.object({
|
|
206
|
+
name: z.string().optional().describe('Queue name (omit for org-level stats)'),
|
|
207
|
+
}),
|
|
208
|
+
options: z.object({
|
|
209
|
+
live: z.boolean().default(false).describe('Stream real-time stats'),
|
|
210
|
+
interval: z.number().default(5).describe('Refresh interval in seconds (for --live)'),
|
|
211
|
+
start: z.string().optional().describe('Start time (ISO 8601)'),
|
|
212
|
+
end: z.string().optional().describe('End time (ISO 8601)'),
|
|
213
|
+
granularity: z.enum(['minute', 'hour', 'day']).optional().describe('Time granularity'),
|
|
214
|
+
}),
|
|
215
|
+
response: StatsResponseSchema,
|
|
216
|
+
},
|
|
217
|
+
idempotent: true,
|
|
218
|
+
|
|
219
|
+
async handler(ctx) {
|
|
220
|
+
const { args, opts, options } = ctx;
|
|
221
|
+
const client = await createQueueAPIClient(ctx);
|
|
222
|
+
const apiOptions = getQueueApiOptions(ctx);
|
|
223
|
+
|
|
224
|
+
const analyticsOptions = {
|
|
225
|
+
...apiOptions,
|
|
226
|
+
start: opts.start,
|
|
227
|
+
end: opts.end,
|
|
228
|
+
granularity: opts.granularity,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
if (opts.live) {
|
|
232
|
+
const events: SSEStatsEvent[] = [];
|
|
233
|
+
|
|
234
|
+
tui.info(
|
|
235
|
+
`Streaming stats every ${opts.interval}s... ${tui.colorMuted('(Ctrl+C to exit)')}`
|
|
236
|
+
);
|
|
237
|
+
tui.info('');
|
|
238
|
+
|
|
239
|
+
const handleInterrupt = () => {
|
|
240
|
+
tui.info('');
|
|
241
|
+
tui.info('Stream stopped.');
|
|
242
|
+
process.exit(0);
|
|
243
|
+
};
|
|
244
|
+
process.on('SIGINT', handleInterrupt);
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
if (args.name) {
|
|
248
|
+
const stream = streamQueueAnalytics(client, args.name, {
|
|
249
|
+
interval: opts.interval,
|
|
250
|
+
orgId: apiOptions?.orgId,
|
|
251
|
+
});
|
|
252
|
+
for await (const event of stream) {
|
|
253
|
+
events.push(event);
|
|
254
|
+
if (!options.json) {
|
|
255
|
+
displayStreamEvent(event, args.name);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
const stream = streamOrgAnalytics(client, {
|
|
260
|
+
interval: opts.interval,
|
|
261
|
+
orgId: apiOptions?.orgId,
|
|
262
|
+
});
|
|
263
|
+
for await (const event of stream) {
|
|
264
|
+
events.push(event);
|
|
265
|
+
if (!options.json) {
|
|
266
|
+
displayStreamEvent(event);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} finally {
|
|
271
|
+
process.off('SIGINT', handleInterrupt);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return { type: 'stream' as const, events };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (args.name) {
|
|
278
|
+
const analytics = await getQueueAnalytics(client, args.name, analyticsOptions);
|
|
279
|
+
|
|
280
|
+
if (!options.json) {
|
|
281
|
+
displayQueueAnalytics(analytics);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return { type: 'queue' as const, analytics };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const analytics = await getOrgAnalytics(client, analyticsOptions);
|
|
288
|
+
|
|
289
|
+
if (!options.json) {
|
|
290
|
+
displayOrgAnalytics(analytics);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return { type: 'org' as const, analytics };
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
export default statsSubcommand;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Logger } from '@agentuity/core';
|
|
2
|
+
import { APIClient, type QueueApiOptions } from '@agentuity/server';
|
|
3
|
+
import { getGlobalCatalystAPIClient } from '../../../config';
|
|
4
|
+
import type { AuthData, Config, GlobalOptions } from '../../../types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Context required for queue API operations.
|
|
8
|
+
*/
|
|
9
|
+
export interface QueueContext {
|
|
10
|
+
logger: Logger;
|
|
11
|
+
auth: AuthData;
|
|
12
|
+
config: Config | null;
|
|
13
|
+
options: GlobalOptions;
|
|
14
|
+
orgId?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates an API client for queue operations.
|
|
19
|
+
*
|
|
20
|
+
* Queues are global resources that don't require a project context.
|
|
21
|
+
* Uses the global Catalyst API client with user authentication.
|
|
22
|
+
*/
|
|
23
|
+
export async function createQueueAPIClient(ctx: QueueContext): Promise<APIClient> {
|
|
24
|
+
return getGlobalCatalystAPIClient(ctx.logger, ctx.auth, ctx.config?.name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Creates QueueApiOptions from the CLI context.
|
|
29
|
+
* Prioritizes explicit orgId on context, then falls back to global --org-id option.
|
|
30
|
+
*/
|
|
31
|
+
export function getQueueApiOptions(ctx: QueueContext): QueueApiOptions | undefined {
|
|
32
|
+
const orgId = ctx.orgId ?? ctx.options.orgId;
|
|
33
|
+
return orgId ? { orgId } : undefined;
|
|
34
|
+
}
|
|
@@ -5,10 +5,12 @@ import { YAML } from 'bun';
|
|
|
5
5
|
import * as tar from 'tar';
|
|
6
6
|
import { createCommand } from '../../../../types';
|
|
7
7
|
import * as tui from '../../../../tui';
|
|
8
|
+
import { ErrorCode } from '../../../../errors';
|
|
8
9
|
import { getCommand } from '../../../../command-prefix';
|
|
9
10
|
import {
|
|
10
11
|
snapshotBuildInit,
|
|
11
12
|
snapshotBuildFinalize,
|
|
13
|
+
snapshotUpload,
|
|
12
14
|
SnapshotBuildFileSchema,
|
|
13
15
|
} from '@agentuity/server';
|
|
14
16
|
import type { SnapshotFileInfo } from '@agentuity/server';
|
|
@@ -35,8 +37,13 @@ const SnapshotBuildResponseSchema = z.object({
|
|
|
35
37
|
.record(z.string(), z.string())
|
|
36
38
|
.optional()
|
|
37
39
|
.describe('User-defined metadata key-value pairs'),
|
|
40
|
+
error: z.string().optional().describe('Error message if build failed'),
|
|
41
|
+
malwareDetected: z.boolean().optional().describe('True if malware was detected'),
|
|
42
|
+
virusName: z.string().optional().describe('Name of detected virus'),
|
|
38
43
|
});
|
|
39
44
|
|
|
45
|
+
const MALWARE_REGEX = /malware detected \(([^)]+)\)/i;
|
|
46
|
+
|
|
40
47
|
interface FileEntry {
|
|
41
48
|
path: string;
|
|
42
49
|
absolutePath: string;
|
|
@@ -229,6 +236,33 @@ async function createTarGzArchive(
|
|
|
229
236
|
);
|
|
230
237
|
}
|
|
231
238
|
|
|
239
|
+
function createProgressStream(
|
|
240
|
+
file: ReturnType<typeof Bun.file>,
|
|
241
|
+
totalSize: number,
|
|
242
|
+
onProgress: (percent: number) => void
|
|
243
|
+
): ReadableStream<Uint8Array> {
|
|
244
|
+
let bytesRead = 0;
|
|
245
|
+
const reader = file.stream().getReader();
|
|
246
|
+
|
|
247
|
+
return new ReadableStream<Uint8Array>({
|
|
248
|
+
async pull(controller) {
|
|
249
|
+
const { done, value } = await reader.read();
|
|
250
|
+
if (done) {
|
|
251
|
+
controller.close();
|
|
252
|
+
onProgress(100);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
bytesRead += value.byteLength;
|
|
256
|
+
const percent = Math.min(99, Math.floor((bytesRead / totalSize) * 100));
|
|
257
|
+
onProgress(percent);
|
|
258
|
+
controller.enqueue(value);
|
|
259
|
+
},
|
|
260
|
+
cancel() {
|
|
261
|
+
reader.cancel();
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
232
266
|
async function generateContentHash(params: {
|
|
233
267
|
runtime: string;
|
|
234
268
|
description?: string;
|
|
@@ -254,7 +288,8 @@ async function generateContentHash(params: {
|
|
|
254
288
|
const sortedFiles = [...params.files].sort((a, b) => a.path.localeCompare(b.path));
|
|
255
289
|
for (const file of sortedFiles) {
|
|
256
290
|
const contentHash = params.fileHashes.get(file.path) ?? '';
|
|
257
|
-
|
|
291
|
+
const mode = file.mode.toString(8).padStart(4, '0');
|
|
292
|
+
hash.update(`file:${file.path}:${file.size}:${contentHash}:${mode}:${file.contentType}\n`);
|
|
258
293
|
}
|
|
259
294
|
}
|
|
260
295
|
|
|
@@ -315,6 +350,10 @@ export const buildSubcommand = createCommand({
|
|
|
315
350
|
description: z.string().optional().describe('Snapshot description (overrides build file)'),
|
|
316
351
|
metadata: z.array(z.string()).optional().describe('Metadata key-value pairs (KEY=VALUE)'),
|
|
317
352
|
force: z.boolean().optional().describe('Force rebuild even if content is unchanged'),
|
|
353
|
+
public: z
|
|
354
|
+
.boolean()
|
|
355
|
+
.optional()
|
|
356
|
+
.describe('Make snapshot public (enables virus scanning, no encryption)'),
|
|
318
357
|
}),
|
|
319
358
|
response: SnapshotBuildResponseSchema,
|
|
320
359
|
},
|
|
@@ -457,21 +496,35 @@ export const buildSubcommand = createCommand({
|
|
|
457
496
|
files = await resolveFileGlobs(directory, buildConfig.files);
|
|
458
497
|
}
|
|
459
498
|
|
|
460
|
-
const
|
|
461
|
-
path: f.path,
|
|
462
|
-
size: f.size,
|
|
463
|
-
}));
|
|
464
|
-
const totalSize = fileList.reduce((sum, f) => sum + f.size, 0);
|
|
465
|
-
|
|
466
|
-
const fileHashes = new Map<string, string>();
|
|
499
|
+
const fileMetadata = new Map<string, { sha256: string; contentType: string; mode: number }>();
|
|
467
500
|
for (const file of files.values()) {
|
|
468
501
|
const fullPath = join(directory, file.path);
|
|
469
502
|
const bunFile = Bun.file(fullPath);
|
|
470
503
|
const content = await bunFile.arrayBuffer();
|
|
471
504
|
const hash = createHash('sha256').update(Buffer.from(content)).digest('hex');
|
|
472
|
-
|
|
505
|
+
const contentType = bunFile.type || 'application/octet-stream';
|
|
506
|
+
const stat = statSync(fullPath);
|
|
507
|
+
const mode = stat.mode & 0o7777; // Extract permission bits only
|
|
508
|
+
fileMetadata.set(file.path, { sha256: hash, contentType, mode });
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const fileHashes = new Map<string, string>();
|
|
512
|
+
for (const [path, meta] of fileMetadata) {
|
|
513
|
+
fileHashes.set(path, meta.sha256);
|
|
473
514
|
}
|
|
474
515
|
|
|
516
|
+
const fileList: SnapshotFileInfo[] = Array.from(files.values()).map((f) => {
|
|
517
|
+
const meta = fileMetadata.get(f.path);
|
|
518
|
+
return {
|
|
519
|
+
path: f.path,
|
|
520
|
+
size: f.size,
|
|
521
|
+
sha256: meta?.sha256 ?? '',
|
|
522
|
+
contentType: meta?.contentType ?? 'application/octet-stream',
|
|
523
|
+
mode: meta?.mode ?? 0o644,
|
|
524
|
+
};
|
|
525
|
+
});
|
|
526
|
+
const totalSize = fileList.reduce((sum, f) => sum + f.size, 0);
|
|
527
|
+
|
|
475
528
|
const contentHash = await generateContentHash({
|
|
476
529
|
runtime: buildConfig.runtime,
|
|
477
530
|
description: finalDescription,
|
|
@@ -565,6 +618,8 @@ export const buildSubcommand = createCommand({
|
|
|
565
618
|
|
|
566
619
|
const client = getCatalystAPIClient(logger, auth, region);
|
|
567
620
|
|
|
621
|
+
const isPublic = opts.public === true;
|
|
622
|
+
|
|
568
623
|
const initResult = await tui.spinner({
|
|
569
624
|
message: 'Initializing snapshot build...',
|
|
570
625
|
clearOnSuccess: true,
|
|
@@ -576,7 +631,8 @@ export const buildSubcommand = createCommand({
|
|
|
576
631
|
description: finalDescription,
|
|
577
632
|
contentHash,
|
|
578
633
|
force: opts.force,
|
|
579
|
-
encrypt:
|
|
634
|
+
encrypt: !isPublic,
|
|
635
|
+
public: isPublic,
|
|
580
636
|
orgId,
|
|
581
637
|
});
|
|
582
638
|
},
|
|
@@ -611,7 +667,7 @@ export const buildSubcommand = createCommand({
|
|
|
611
667
|
};
|
|
612
668
|
}
|
|
613
669
|
|
|
614
|
-
// Encrypt the archive if public key is provided
|
|
670
|
+
// Encrypt the archive if public key is provided (private snapshots only)
|
|
615
671
|
let uploadPath = archivePath;
|
|
616
672
|
let uploadSize = archiveSize;
|
|
617
673
|
|
|
@@ -646,28 +702,89 @@ export const buildSubcommand = createCommand({
|
|
|
646
702
|
uploadSize = Bun.file(encryptedPath).size;
|
|
647
703
|
}
|
|
648
704
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
705
|
+
if (initResult.uploadUrl) {
|
|
706
|
+
// Private snapshot: upload directly to S3
|
|
707
|
+
// Use Bun.file() directly as body - Bun sets Content-Length automatically from file size
|
|
708
|
+
await tui.spinner({
|
|
709
|
+
message: 'Uploading snapshot...',
|
|
710
|
+
clearOnSuccess: true,
|
|
711
|
+
callback: async () => {
|
|
712
|
+
const uploadFile = Bun.file(uploadPath);
|
|
713
|
+
const response = await fetch(initResult.uploadUrl!, {
|
|
714
|
+
method: 'PUT',
|
|
715
|
+
headers: {
|
|
716
|
+
'Content-Type': 'application/gzip',
|
|
717
|
+
},
|
|
718
|
+
body: uploadFile,
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
if (!response.ok) {
|
|
722
|
+
throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
});
|
|
726
|
+
} else {
|
|
727
|
+
// Public snapshot: upload via Catalyst (with virus scanning)
|
|
728
|
+
try {
|
|
729
|
+
await tui.spinner({
|
|
730
|
+
message: 'Uploading and scanning snapshot...',
|
|
731
|
+
type: 'progress',
|
|
732
|
+
clearOnSuccess: true,
|
|
733
|
+
clearOnError: true,
|
|
734
|
+
callback: async (updateProgress) => {
|
|
735
|
+
const uploadFile = Bun.file(uploadPath);
|
|
736
|
+
const progressStream = createProgressStream(uploadFile, uploadSize, updateProgress);
|
|
737
|
+
await snapshotUpload(client, {
|
|
738
|
+
snapshotId: initResult.snapshotId!,
|
|
739
|
+
body: progressStream,
|
|
740
|
+
contentLength: uploadSize,
|
|
741
|
+
orgId,
|
|
742
|
+
});
|
|
660
743
|
},
|
|
661
|
-
body: uploadFile,
|
|
662
744
|
});
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
745
|
+
} catch (err) {
|
|
746
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
747
|
+
const malwareMatch = MALWARE_REGEX.exec(errorMessage);
|
|
748
|
+
|
|
749
|
+
if (malwareMatch) {
|
|
750
|
+
const virusName = malwareMatch[1];
|
|
751
|
+
|
|
752
|
+
if (options.json) {
|
|
753
|
+
console.log(
|
|
754
|
+
JSON.stringify(
|
|
755
|
+
{
|
|
756
|
+
snapshotId: '',
|
|
757
|
+
name: finalName ?? '',
|
|
758
|
+
tag: opts.tag,
|
|
759
|
+
runtime: buildConfig.runtime,
|
|
760
|
+
sizeBytes: totalSize,
|
|
761
|
+
fileCount: fileList.length,
|
|
762
|
+
createdAt: new Date().toISOString(),
|
|
763
|
+
error: errorMessage,
|
|
764
|
+
malwareDetected: true,
|
|
765
|
+
virusName,
|
|
766
|
+
},
|
|
767
|
+
null,
|
|
768
|
+
2
|
|
769
|
+
)
|
|
770
|
+
);
|
|
771
|
+
process.exit(ErrorCode.MALWARE_DETECTED);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
console.log('');
|
|
775
|
+
tui.errorBox(
|
|
776
|
+
'Malware Detected',
|
|
777
|
+
`Your snapshot was rejected because it contains malware.\n\nVirus: ${virusName}\n\nPlease remove the infected files and try again.`
|
|
778
|
+
);
|
|
779
|
+
tui.fatal(
|
|
780
|
+
'Snapshot build failed due to malware detection',
|
|
781
|
+
ErrorCode.MALWARE_DETECTED
|
|
782
|
+
);
|
|
666
783
|
}
|
|
667
784
|
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
}
|
|
785
|
+
throw err;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
671
788
|
|
|
672
789
|
const snapshot = await tui.spinner({
|
|
673
790
|
message: 'Finalizing snapshot...',
|
|
@@ -39,6 +39,10 @@ export const createSubcommand = createCommand({
|
|
|
39
39
|
),
|
|
40
40
|
description: 'Create a named snapshot with description',
|
|
41
41
|
},
|
|
42
|
+
{
|
|
43
|
+
command: getCommand('cloud sandbox snapshot create sbx_abc123 --public'),
|
|
44
|
+
description: 'Create a public snapshot',
|
|
45
|
+
},
|
|
42
46
|
],
|
|
43
47
|
schema: {
|
|
44
48
|
args: z.object({
|
|
@@ -51,6 +55,7 @@ export const createSubcommand = createCommand({
|
|
|
51
55
|
.describe('Display name for the snapshot (letters, numbers, underscores, dashes only)'),
|
|
52
56
|
description: z.string().optional().describe('Description of the snapshot'),
|
|
53
57
|
tag: z.string().optional().describe('Tag for the snapshot (defaults to "latest")'),
|
|
58
|
+
public: z.boolean().optional().default(false).describe('Make the snapshot publicly accessible'),
|
|
54
59
|
}),
|
|
55
60
|
response: SnapshotCreateResponseSchema,
|
|
56
61
|
},
|
|
@@ -86,17 +91,29 @@ export const createSubcommand = createCommand({
|
|
|
86
91
|
name: opts.name,
|
|
87
92
|
description: opts.description,
|
|
88
93
|
tag: opts.tag,
|
|
94
|
+
public: opts.public,
|
|
89
95
|
orgId,
|
|
90
96
|
});
|
|
91
97
|
|
|
92
98
|
if (!options.json) {
|
|
93
|
-
tui.success(`
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
tui.success(`Created snapshot ${tui.bold(snapshot.snapshotId)}`);
|
|
100
|
+
console.log('');
|
|
101
|
+
|
|
102
|
+
tui.table(
|
|
103
|
+
[
|
|
104
|
+
{
|
|
105
|
+
Name: snapshot.name,
|
|
106
|
+
Description: snapshot.description ?? '-',
|
|
107
|
+
Tag: snapshot.tag ?? 'latest',
|
|
108
|
+
Size: tui.formatBytes(snapshot.sizeBytes),
|
|
109
|
+
Files: snapshot.fileCount.toFixed(),
|
|
110
|
+
Visibility: snapshot.public ? 'public' : 'private',
|
|
111
|
+
Created: snapshot.createdAt,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
['Name', 'Description', 'Tag', 'Size', 'Files', 'Visibility', 'Created'],
|
|
115
|
+
{ layout: 'vertical', padStart: ' ' }
|
|
116
|
+
);
|
|
100
117
|
}
|
|
101
118
|
|
|
102
119
|
return {
|
|
@@ -10,6 +10,9 @@ import { getGlobalCatalystAPIClient } from '../../../../config';
|
|
|
10
10
|
const SnapshotFileSchema = z.object({
|
|
11
11
|
path: z.string(),
|
|
12
12
|
size: z.number(),
|
|
13
|
+
sha256: z.string(),
|
|
14
|
+
contentType: z.string(),
|
|
15
|
+
mode: z.number(),
|
|
13
16
|
});
|
|
14
17
|
|
|
15
18
|
const SandboxInfoSchema = z.object({
|
|
@@ -22,10 +25,14 @@ const SandboxInfoSchema = z.object({
|
|
|
22
25
|
const SnapshotGetResponseSchema = z.object({
|
|
23
26
|
snapshotId: z.string().describe('Snapshot ID'),
|
|
24
27
|
name: z.string().describe('Snapshot name'),
|
|
28
|
+
fullName: z.string().optional().describe('Full name with org slug (@slug/name:tag)'),
|
|
25
29
|
tag: z.string().nullable().optional().describe('Snapshot tag'),
|
|
26
30
|
sizeBytes: z.number().describe('Snapshot size in bytes'),
|
|
27
31
|
fileCount: z.number().describe('Number of files'),
|
|
28
32
|
parentSnapshotId: z.string().nullable().optional().describe('Parent snapshot ID'),
|
|
33
|
+
public: z.boolean().optional().describe('Whether snapshot is publicly accessible'),
|
|
34
|
+
orgName: z.string().optional().describe('Organization name (for public snapshots)'),
|
|
35
|
+
orgSlug: z.string().optional().describe('Organization slug (for public snapshots)'),
|
|
29
36
|
createdAt: z.string().describe('Creation timestamp'),
|
|
30
37
|
downloadUrl: z.string().optional().describe('Presigned download URL'),
|
|
31
38
|
files: z.array(SnapshotFileSchema).nullable().optional().describe('Files in snapshot'),
|
|
@@ -87,6 +94,15 @@ export const getSubcommand = createCommand({
|
|
|
87
94
|
}
|
|
88
95
|
tableData['Size'] = tui.formatBytes(snapshot.sizeBytes);
|
|
89
96
|
tableData['Files'] = snapshot.fileCount;
|
|
97
|
+
if (snapshot.public) {
|
|
98
|
+
tableData['Public'] = 'Yes';
|
|
99
|
+
if (snapshot.fullName) {
|
|
100
|
+
tableData['Full Name'] = snapshot.fullName;
|
|
101
|
+
}
|
|
102
|
+
if (snapshot.orgName) {
|
|
103
|
+
tableData['Publisher'] = snapshot.orgName;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
90
106
|
tableData['Created'] = snapshot.createdAt;
|
|
91
107
|
if (snapshot.parentSnapshotId) {
|
|
92
108
|
tableData['Parent'] = snapshot.parentSnapshotId;
|