@agentuity/cli 1.0.24 → 1.0.26
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/cache/index.d.ts +1 -0
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +1 -0
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/user-cache.d.ts +20 -0
- package/dist/cache/user-cache.d.ts.map +1 -0
- package/dist/cache/user-cache.js +79 -0
- package/dist/cache/user-cache.js.map +1 -0
- package/dist/cmd/auth/logout.d.ts.map +1 -1
- package/dist/cmd/auth/logout.js +3 -1
- package/dist/cmd/auth/logout.js.map +1 -1
- package/dist/cmd/build/entry-generator.d.ts +4 -0
- package/dist/cmd/build/entry-generator.d.ts.map +1 -1
- package/dist/cmd/build/entry-generator.js +18 -3
- package/dist/cmd/build/entry-generator.js.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.d.ts +1 -0
- package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.js +11 -9
- package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
- package/dist/cmd/cloud/db/stats.d.ts.map +1 -1
- package/dist/cmd/cloud/db/stats.js.map +1 -1
- package/dist/cmd/cloud/email/create.d.ts.map +1 -1
- package/dist/cmd/cloud/email/create.js +2 -7
- package/dist/cmd/cloud/email/create.js.map +1 -1
- package/dist/cmd/cloud/email/destination/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/email/destination/delete.js +5 -1
- package/dist/cmd/cloud/email/destination/delete.js.map +1 -1
- package/dist/cmd/cloud/email/get.d.ts.map +1 -1
- package/dist/cmd/cloud/email/get.js +30 -7
- package/dist/cmd/cloud/email/get.js.map +1 -1
- package/dist/cmd/cloud/email/list.d.ts.map +1 -1
- package/dist/cmd/cloud/email/list.js +0 -6
- package/dist/cmd/cloud/email/list.js.map +1 -1
- package/dist/cmd/cloud/email/send.d.ts.map +1 -1
- package/dist/cmd/cloud/email/send.js +1 -5
- package/dist/cmd/cloud/email/send.js.map +1 -1
- package/dist/cmd/cloud/email/stats.d.ts.map +1 -1
- package/dist/cmd/cloud/email/stats.js.map +1 -1
- package/dist/cmd/cloud/email/util.d.ts +0 -1
- package/dist/cmd/cloud/email/util.d.ts.map +1 -1
- package/dist/cmd/cloud/email/util.js +1 -3
- package/dist/cmd/cloud/email/util.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/build.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/build.js +4 -1
- package/dist/cmd/cloud/sandbox/snapshot/build.js.map +1 -1
- package/dist/cmd/cloud/sandbox/stats.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/stats.js.map +1 -1
- package/dist/cmd/cloud/schedule/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/schedule/delete.js +4 -1
- package/dist/cmd/cloud/schedule/delete.js.map +1 -1
- package/dist/cmd/cloud/schedule/delivery/list.d.ts.map +1 -1
- package/dist/cmd/cloud/schedule/delivery/list.js.map +1 -1
- package/dist/cmd/cloud/schedule/destination/create.d.ts.map +1 -1
- package/dist/cmd/cloud/schedule/destination/create.js +3 -1
- package/dist/cmd/cloud/schedule/destination/create.js.map +1 -1
- package/dist/cmd/cloud/schedule/destination/index.d.ts.map +1 -1
- package/dist/cmd/cloud/schedule/destination/index.js.map +1 -1
- package/dist/cmd/cloud/schedule/destination/list.d.ts.map +1 -1
- package/dist/cmd/cloud/schedule/destination/list.js.map +1 -1
- package/dist/cmd/cloud/schedule/get.d.ts.map +1 -1
- package/dist/cmd/cloud/schedule/get.js +4 -1
- package/dist/cmd/cloud/schedule/get.js.map +1 -1
- package/dist/cmd/cloud/schedule/index.d.ts.map +1 -1
- package/dist/cmd/cloud/schedule/index.js +4 -1
- package/dist/cmd/cloud/schedule/index.js.map +1 -1
- package/dist/cmd/cloud/schedule/list.d.ts.map +1 -1
- package/dist/cmd/cloud/schedule/list.js.map +1 -1
- package/dist/cmd/cloud/schedule/stats.d.ts.map +1 -1
- package/dist/cmd/cloud/schedule/stats.js.map +1 -1
- package/dist/cmd/cloud/schedule/util.d.ts.map +1 -1
- package/dist/cmd/cloud/schedule/util.js +1 -2
- package/dist/cmd/cloud/schedule/util.js.map +1 -1
- package/dist/cmd/cloud/services/stats.d.ts.map +1 -1
- package/dist/cmd/cloud/services/stats.js.map +1 -1
- package/dist/cmd/cloud/stream/index.d.ts.map +1 -1
- package/dist/cmd/cloud/stream/index.js +7 -1
- package/dist/cmd/cloud/stream/index.js.map +1 -1
- package/dist/cmd/cloud/stream/stats.d.ts.map +1 -1
- package/dist/cmd/cloud/stream/stats.js.map +1 -1
- package/dist/cmd/cloud/task/attachment.d.ts +2 -0
- package/dist/cmd/cloud/task/attachment.d.ts.map +1 -0
- package/dist/cmd/cloud/task/attachment.js +393 -0
- package/dist/cmd/cloud/task/attachment.js.map +1 -0
- package/dist/cmd/cloud/task/create.d.ts.map +1 -1
- package/dist/cmd/cloud/task/create.js +126 -5
- package/dist/cmd/cloud/task/create.js.map +1 -1
- package/dist/cmd/cloud/task/get.d.ts.map +1 -1
- package/dist/cmd/cloud/task/get.js +29 -11
- package/dist/cmd/cloud/task/get.js.map +1 -1
- package/dist/cmd/cloud/task/index.d.ts.map +1 -1
- package/dist/cmd/cloud/task/index.js +13 -1
- package/dist/cmd/cloud/task/index.js.map +1 -1
- package/dist/cmd/cloud/task/list.d.ts.map +1 -1
- package/dist/cmd/cloud/task/list.js +31 -15
- package/dist/cmd/cloud/task/list.js.map +1 -1
- package/dist/cmd/cloud/task/stats.js +2 -0
- package/dist/cmd/cloud/task/stats.js.map +1 -1
- package/dist/cmd/cloud/task/util.d.ts.map +1 -1
- package/dist/cmd/cloud/task/util.js +2 -4
- package/dist/cmd/cloud/task/util.js.map +1 -1
- package/dist/cmd/cloud/webhook/create.d.ts.map +1 -1
- package/dist/cmd/cloud/webhook/create.js +6 -1
- package/dist/cmd/cloud/webhook/create.js.map +1 -1
- package/dist/cmd/cloud/webhook/deliveries.d.ts.map +1 -1
- package/dist/cmd/cloud/webhook/deliveries.js +4 -1
- package/dist/cmd/cloud/webhook/deliveries.js.map +1 -1
- package/dist/cmd/cloud/webhook/destinations.d.ts.map +1 -1
- package/dist/cmd/cloud/webhook/destinations.js +4 -5
- package/dist/cmd/cloud/webhook/destinations.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +80 -34
- package/dist/cmd/dev/index.js.map +1 -1
- package/package.json +6 -6
- package/src/cache/index.ts +2 -0
- package/src/cache/user-cache.ts +93 -0
- package/src/cmd/auth/logout.ts +3 -1
- package/src/cmd/build/entry-generator.ts +34 -4
- package/src/cmd/build/vite/bun-dev-server.ts +21 -9
- package/src/cmd/cloud/db/stats.ts +4 -12
- package/src/cmd/cloud/email/create.ts +2 -7
- package/src/cmd/cloud/email/destination/delete.ts +5 -1
- package/src/cmd/cloud/email/get.ts +42 -7
- package/src/cmd/cloud/email/list.ts +0 -6
- package/src/cmd/cloud/email/send.ts +1 -5
- package/src/cmd/cloud/email/stats.ts +2 -6
- package/src/cmd/cloud/email/util.ts +1 -3
- package/src/cmd/cloud/sandbox/snapshot/build.ts +25 -6
- package/src/cmd/cloud/sandbox/stats.ts +2 -6
- package/src/cmd/cloud/schedule/delete.ts +4 -1
- package/src/cmd/cloud/schedule/delivery/list.ts +15 -13
- package/src/cmd/cloud/schedule/destination/create.ts +11 -3
- package/src/cmd/cloud/schedule/destination/index.ts +3 -1
- package/src/cmd/cloud/schedule/destination/list.ts +19 -17
- package/src/cmd/cloud/schedule/get.ts +25 -20
- package/src/cmd/cloud/schedule/index.ts +4 -1
- package/src/cmd/cloud/schedule/list.ts +18 -16
- package/src/cmd/cloud/schedule/stats.ts +1 -3
- package/src/cmd/cloud/schedule/util.ts +1 -2
- package/src/cmd/cloud/services/stats.ts +13 -39
- package/src/cmd/cloud/stream/index.ts +7 -1
- package/src/cmd/cloud/stream/stats.ts +2 -6
- package/src/cmd/cloud/task/attachment.ts +432 -0
- package/src/cmd/cloud/task/create.ts +131 -5
- package/src/cmd/cloud/task/get.ts +30 -12
- package/src/cmd/cloud/task/index.ts +13 -1
- package/src/cmd/cloud/task/list.ts +31 -15
- package/src/cmd/cloud/task/stats.ts +3 -3
- package/src/cmd/cloud/task/util.ts +2 -4
- package/src/cmd/cloud/webhook/create.ts +6 -1
- package/src/cmd/cloud/webhook/deliveries.ts +4 -5
- package/src/cmd/cloud/webhook/destinations.ts +4 -5
- package/src/cmd/dev/index.ts +91 -48
|
@@ -49,22 +49,24 @@ export const listSubcommand = createCommand({
|
|
|
49
49
|
tui.info('No schedules found');
|
|
50
50
|
} else {
|
|
51
51
|
tui.table(
|
|
52
|
-
result.schedules.map(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
52
|
+
result.schedules.map(
|
|
53
|
+
(item: {
|
|
54
|
+
id: string;
|
|
55
|
+
created_at: string;
|
|
56
|
+
updated_at: string;
|
|
57
|
+
created_by: string;
|
|
58
|
+
name: string;
|
|
59
|
+
description: string | null;
|
|
60
|
+
expression: string;
|
|
61
|
+
due_date: string;
|
|
62
|
+
}) => ({
|
|
63
|
+
Name: item.name,
|
|
64
|
+
ID: item.id,
|
|
65
|
+
Expression: item.expression,
|
|
66
|
+
'Next Due': item.due_date,
|
|
67
|
+
Created: new Date(item.created_at).toLocaleString(),
|
|
68
|
+
})
|
|
69
|
+
),
|
|
68
70
|
['Name', 'ID', 'Expression', 'Next Due', 'Created']
|
|
69
71
|
);
|
|
70
72
|
}
|
|
@@ -19,9 +19,7 @@ function displayStats(data: ServiceStatsData): void {
|
|
|
19
19
|
}
|
|
20
20
|
tui.header('Schedule Statistics');
|
|
21
21
|
tui.newline();
|
|
22
|
-
console.log(
|
|
23
|
-
` ${tui.muted('Schedules:')} ${formatNumber(svc.scheduleCount)}`
|
|
24
|
-
);
|
|
22
|
+
console.log(` ${tui.muted('Schedules:')} ${formatNumber(svc.scheduleCount)}`);
|
|
25
23
|
console.log(
|
|
26
24
|
` ${tui.muted('Deliveries:')} ${formatNumber(svc.totalDeliveries)} (${svc.successDeliveries} ok, ${svc.failedDeliveries} failed)`
|
|
27
25
|
);
|
|
@@ -14,8 +14,7 @@ export interface ScheduleContext {
|
|
|
14
14
|
|
|
15
15
|
export async function createScheduleAdapter(ctx: ScheduleContext) {
|
|
16
16
|
const orgId =
|
|
17
|
-
ctx.options.orgId ??
|
|
18
|
-
(process.env.AGENTUITY_CLOUD_ORG_ID || ctx.config?.preferences?.orgId);
|
|
17
|
+
ctx.options.orgId ?? (process.env.AGENTUITY_CLOUD_ORG_ID || ctx.config?.preferences?.orgId);
|
|
19
18
|
if (!orgId) {
|
|
20
19
|
tui.fatal('Organization ID is required. Use --org-id flag or set AGENTUITY_CLOUD_ORG_ID.');
|
|
21
20
|
}
|
|
@@ -34,9 +34,7 @@ function displayServiceStats(data: ServiceStatsData): void {
|
|
|
34
34
|
console.log(
|
|
35
35
|
` ${tui.muted('Namespaces:')} ${formatNumber(services.keyvalue.namespaceCount)}`
|
|
36
36
|
);
|
|
37
|
-
console.log(
|
|
38
|
-
` ${tui.muted('Keys:')} ${formatNumber(services.keyvalue.keyCount)}`
|
|
39
|
-
);
|
|
37
|
+
console.log(` ${tui.muted('Keys:')} ${formatNumber(services.keyvalue.keyCount)}`);
|
|
40
38
|
console.log(
|
|
41
39
|
` ${tui.muted('Total Size:')} ${tui.formatBytes(services.keyvalue.totalSizeBytes)}`
|
|
42
40
|
);
|
|
@@ -61,15 +59,11 @@ function displayServiceStats(data: ServiceStatsData): void {
|
|
|
61
59
|
hasData = true;
|
|
62
60
|
tui.newline();
|
|
63
61
|
console.log(tui.colorPrimary('Queue:'));
|
|
64
|
-
console.log(
|
|
65
|
-
` ${tui.muted('Queues:')} ${formatNumber(services.queue.queueCount)}`
|
|
66
|
-
);
|
|
62
|
+
console.log(` ${tui.muted('Queues:')} ${formatNumber(services.queue.queueCount)}`);
|
|
67
63
|
console.log(
|
|
68
64
|
` ${tui.muted('Total Messages:')} ${formatNumber(services.queue.totalMessages)}`
|
|
69
65
|
);
|
|
70
|
-
console.log(
|
|
71
|
-
` ${tui.muted('DLQ Messages:')} ${formatNumber(services.queue.totalDlq)}`
|
|
72
|
-
);
|
|
66
|
+
console.log(` ${tui.muted('DLQ Messages:')} ${formatNumber(services.queue.totalDlq)}`);
|
|
73
67
|
}
|
|
74
68
|
|
|
75
69
|
if (services.stream) {
|
|
@@ -92,13 +86,9 @@ function displayServiceStats(data: ServiceStatsData): void {
|
|
|
92
86
|
console.log(
|
|
93
87
|
` ${tui.muted('Active:')} ${formatNumber(sb.totalActive)} (${sb.running} running, ${sb.idle} idle, ${sb.creating} creating)`
|
|
94
88
|
);
|
|
95
|
-
console.log(
|
|
96
|
-
` ${tui.muted('Executions:')} ${formatNumber(sb.totalExecutions)}`
|
|
97
|
-
);
|
|
89
|
+
console.log(` ${tui.muted('Executions:')} ${formatNumber(sb.totalExecutions)}`);
|
|
98
90
|
console.log(` ${tui.muted('CPU Time:')} ${formatLatency(sb.totalCpuTimeMs)}`);
|
|
99
|
-
console.log(
|
|
100
|
-
` ${tui.muted('Memory:')} ${tui.formatBytes(sb.totalMemoryByteSec)}`
|
|
101
|
-
);
|
|
91
|
+
console.log(` ${tui.muted('Memory:')} ${tui.formatBytes(sb.totalMemoryByteSec)}`);
|
|
102
92
|
console.log(
|
|
103
93
|
` ${tui.muted('Network Out:')} ${tui.formatBytes(sb.totalNetworkEgressBytes)}`
|
|
104
94
|
);
|
|
@@ -109,12 +99,8 @@ function displayServiceStats(data: ServiceStatsData): void {
|
|
|
109
99
|
const em = services.email;
|
|
110
100
|
tui.newline();
|
|
111
101
|
console.log(tui.colorPrimary('Email:'));
|
|
112
|
-
console.log(
|
|
113
|
-
|
|
114
|
-
);
|
|
115
|
-
console.log(
|
|
116
|
-
` ${tui.muted('Inbound:')} ${formatNumber(em.inboundCount)}`
|
|
117
|
-
);
|
|
102
|
+
console.log(` ${tui.muted('Addresses:')} ${formatNumber(em.addressCount)}`);
|
|
103
|
+
console.log(` ${tui.muted('Inbound:')} ${formatNumber(em.inboundCount)}`);
|
|
118
104
|
console.log(
|
|
119
105
|
` ${tui.muted('Outbound:')} ${formatNumber(em.outboundCount)} (${em.outboundSuccess} ok, ${em.outboundFailed} failed)`
|
|
120
106
|
);
|
|
@@ -127,9 +113,7 @@ function displayServiceStats(data: ServiceStatsData): void {
|
|
|
127
113
|
console.log(tui.colorPrimary('Task:'));
|
|
128
114
|
console.log(` ${tui.muted('Total:')} ${formatNumber(tk.total)}`);
|
|
129
115
|
console.log(` ${tui.muted('Open:')} ${formatNumber(tk.open)}`);
|
|
130
|
-
console.log(
|
|
131
|
-
` ${tui.muted('In Progress:')} ${formatNumber(tk.inProgress)}`
|
|
132
|
-
);
|
|
116
|
+
console.log(` ${tui.muted('In Progress:')} ${formatNumber(tk.inProgress)}`);
|
|
133
117
|
console.log(` ${tui.muted('Closed:')} ${formatNumber(tk.closed)}`);
|
|
134
118
|
}
|
|
135
119
|
|
|
@@ -138,9 +122,7 @@ function displayServiceStats(data: ServiceStatsData): void {
|
|
|
138
122
|
const sc = services.schedule;
|
|
139
123
|
tui.newline();
|
|
140
124
|
console.log(tui.colorPrimary('Schedule:'));
|
|
141
|
-
console.log(
|
|
142
|
-
` ${tui.muted('Schedules:')} ${formatNumber(sc.scheduleCount)}`
|
|
143
|
-
);
|
|
125
|
+
console.log(` ${tui.muted('Schedules:')} ${formatNumber(sc.scheduleCount)}`);
|
|
144
126
|
console.log(
|
|
145
127
|
` ${tui.muted('Deliveries:')} ${formatNumber(sc.totalDeliveries)} (${sc.successDeliveries} ok, ${sc.failedDeliveries} failed)`
|
|
146
128
|
);
|
|
@@ -151,18 +133,10 @@ function displayServiceStats(data: ServiceStatsData): void {
|
|
|
151
133
|
const db = services.database;
|
|
152
134
|
tui.newline();
|
|
153
135
|
console.log(tui.colorPrimary('Database:'));
|
|
154
|
-
console.log(
|
|
155
|
-
|
|
156
|
-
);
|
|
157
|
-
console.log(
|
|
158
|
-
` ${tui.muted('Tables:')} ${formatNumber(db.totalTableCount)}`
|
|
159
|
-
);
|
|
160
|
-
console.log(
|
|
161
|
-
` ${tui.muted('Records:')} ${formatNumber(db.totalRecordCount)}`
|
|
162
|
-
);
|
|
163
|
-
console.log(
|
|
164
|
-
` ${tui.muted('Total Size:')} ${tui.formatBytes(db.totalSizeBytes)}`
|
|
165
|
-
);
|
|
136
|
+
console.log(` ${tui.muted('Databases:')} ${formatNumber(db.databaseCount)}`);
|
|
137
|
+
console.log(` ${tui.muted('Tables:')} ${formatNumber(db.totalTableCount)}`);
|
|
138
|
+
console.log(` ${tui.muted('Records:')} ${formatNumber(db.totalRecordCount)}`);
|
|
139
|
+
console.log(` ${tui.muted('Total Size:')} ${tui.formatBytes(db.totalSizeBytes)}`);
|
|
166
140
|
}
|
|
167
141
|
|
|
168
142
|
if (!hasData) {
|
|
@@ -23,7 +23,13 @@ export const streamCommand = createCommand({
|
|
|
23
23
|
{ command: getCommand('cloud stream list'), description: 'List all streams' },
|
|
24
24
|
{ command: getCommand('cloud stream get <id>'), description: 'Get stream details' },
|
|
25
25
|
],
|
|
26
|
-
subcommands: [
|
|
26
|
+
subcommands: [
|
|
27
|
+
createSubcommand,
|
|
28
|
+
listSubcommand,
|
|
29
|
+
getSubcommand,
|
|
30
|
+
deleteSubcommand,
|
|
31
|
+
statsSubcommand,
|
|
32
|
+
],
|
|
27
33
|
});
|
|
28
34
|
|
|
29
35
|
export default streamCommand;
|
|
@@ -19,12 +19,8 @@ function displayStats(data: ServiceStatsData): void {
|
|
|
19
19
|
}
|
|
20
20
|
tui.header('Stream Statistics');
|
|
21
21
|
tui.newline();
|
|
22
|
-
console.log(
|
|
23
|
-
|
|
24
|
-
);
|
|
25
|
-
console.log(
|
|
26
|
-
` ${tui.muted('Total Size:')} ${tui.formatBytes(svc.totalSizeBytes)}`
|
|
27
|
-
);
|
|
22
|
+
console.log(` ${tui.muted('Streams:')} ${formatNumber(svc.streamCount)}`);
|
|
23
|
+
console.log(` ${tui.muted('Total Size:')} ${tui.formatBytes(svc.totalSizeBytes)}`);
|
|
28
24
|
}
|
|
29
25
|
|
|
30
26
|
export const statsSubcommand = createCommand({
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { basename, join } from 'path';
|
|
2
|
+
import { stat as fsStat } from 'node:fs/promises';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { createCommand } from '../../../types';
|
|
5
|
+
import * as tui from '../../../tui';
|
|
6
|
+
import { createStorageAdapter } from './util';
|
|
7
|
+
import { getCommand } from '../../../command-prefix';
|
|
8
|
+
import type { Attachment } from '@agentuity/core';
|
|
9
|
+
|
|
10
|
+
function formatBytes(bytes: number | undefined): string {
|
|
11
|
+
if (bytes === undefined || bytes === null) return '—';
|
|
12
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
13
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
14
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function truncate(s: string, max: number): string {
|
|
18
|
+
if (s.length <= max) return s;
|
|
19
|
+
return `${s.slice(0, max - 1)}…`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ── Upload ──────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const uploadSubcommand = createCommand({
|
|
25
|
+
name: 'upload',
|
|
26
|
+
aliases: ['up', 'put'],
|
|
27
|
+
description: 'Upload a file attachment to a task',
|
|
28
|
+
tags: ['mutating', 'slow', 'requires-auth'],
|
|
29
|
+
requires: { auth: true },
|
|
30
|
+
examples: [
|
|
31
|
+
{
|
|
32
|
+
command: getCommand('cloud task attachment upload task_abc123 ./report.pdf'),
|
|
33
|
+
description: 'Upload a file to a task',
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
schema: {
|
|
37
|
+
args: z.object({
|
|
38
|
+
taskId: z.string().min(1).describe('the task ID to attach the file to'),
|
|
39
|
+
file: z.string().min(1).describe('local file path to upload'),
|
|
40
|
+
}),
|
|
41
|
+
response: z.object({
|
|
42
|
+
success: z.boolean().describe('Whether the operation succeeded'),
|
|
43
|
+
attachment: z.object({
|
|
44
|
+
id: z.string().describe('Attachment ID'),
|
|
45
|
+
filename: z.string().describe('Filename'),
|
|
46
|
+
content_type: z.string().optional().describe('Content type'),
|
|
47
|
+
size: z.number().optional().describe('File size in bytes'),
|
|
48
|
+
}),
|
|
49
|
+
durationMs: z.number().describe('Operation duration in milliseconds'),
|
|
50
|
+
}),
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async handler(ctx) {
|
|
54
|
+
const { args, options } = ctx;
|
|
55
|
+
const started = Date.now();
|
|
56
|
+
const storage = await createStorageAdapter(ctx);
|
|
57
|
+
|
|
58
|
+
const file = Bun.file(args.file);
|
|
59
|
+
if (!(await file.exists())) {
|
|
60
|
+
tui.fatal(`File not found: ${args.file}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const filename = basename(args.file);
|
|
64
|
+
const contentType = file.type || 'application/octet-stream';
|
|
65
|
+
const size = file.size;
|
|
66
|
+
|
|
67
|
+
// Step 1: Get presigned upload URL
|
|
68
|
+
const presign = await tui.spinner({
|
|
69
|
+
message: 'Requesting upload URL',
|
|
70
|
+
clearOnSuccess: true,
|
|
71
|
+
callback: async () => {
|
|
72
|
+
return storage.uploadAttachment(args.taskId, {
|
|
73
|
+
filename,
|
|
74
|
+
content_type: contentType,
|
|
75
|
+
size,
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Step 2: Upload file to presigned URL
|
|
81
|
+
await tui.spinner({
|
|
82
|
+
message: `Uploading ${filename}`,
|
|
83
|
+
clearOnSuccess: true,
|
|
84
|
+
callback: async () => {
|
|
85
|
+
const response = await fetch(presign.presigned_url, {
|
|
86
|
+
method: 'PUT',
|
|
87
|
+
body: file.stream(),
|
|
88
|
+
headers: {
|
|
89
|
+
'Content-Type': contentType,
|
|
90
|
+
},
|
|
91
|
+
duplex: 'half',
|
|
92
|
+
});
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
tui.fatal(`Upload failed: ${response.statusText}`);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Step 3: Confirm the upload
|
|
100
|
+
const attachment = await tui.spinner({
|
|
101
|
+
message: 'Confirming upload',
|
|
102
|
+
clearOnSuccess: true,
|
|
103
|
+
callback: async () => {
|
|
104
|
+
return storage.confirmAttachment(presign.attachment.id);
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const durationMs = Date.now() - started;
|
|
109
|
+
|
|
110
|
+
if (!options.json) {
|
|
111
|
+
tui.success(`Attachment uploaded: ${tui.bold(attachment.id)}`);
|
|
112
|
+
|
|
113
|
+
const tableData: Record<string, string> = {
|
|
114
|
+
ID: attachment.id,
|
|
115
|
+
Filename: attachment.filename,
|
|
116
|
+
'Content Type': attachment.content_type ?? '—',
|
|
117
|
+
Size: formatBytes(attachment.size),
|
|
118
|
+
Task: attachment.task_id,
|
|
119
|
+
Created: new Date(attachment.created_at).toLocaleString(),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
tui.table([tableData], Object.keys(tableData), { layout: 'vertical', padStart: ' ' });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
attachment: {
|
|
128
|
+
id: attachment.id,
|
|
129
|
+
filename: attachment.filename,
|
|
130
|
+
content_type: attachment.content_type,
|
|
131
|
+
size: attachment.size,
|
|
132
|
+
},
|
|
133
|
+
durationMs,
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ── List ────────────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
const listAttachmentsSubcommand = createCommand({
|
|
141
|
+
name: 'list',
|
|
142
|
+
aliases: ['ls'],
|
|
143
|
+
description: 'List attachments for a task',
|
|
144
|
+
tags: ['read-only', 'slow', 'requires-auth'],
|
|
145
|
+
idempotent: true,
|
|
146
|
+
requires: { auth: true },
|
|
147
|
+
examples: [
|
|
148
|
+
{
|
|
149
|
+
command: getCommand('cloud task attachment list task_abc123'),
|
|
150
|
+
description: 'List all attachments for a task',
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
schema: {
|
|
154
|
+
args: z.object({
|
|
155
|
+
taskId: z.string().min(1).describe('the task ID to list attachments for'),
|
|
156
|
+
}),
|
|
157
|
+
response: z.object({
|
|
158
|
+
success: z.boolean().describe('Whether the operation succeeded'),
|
|
159
|
+
attachments: z.array(
|
|
160
|
+
z.object({
|
|
161
|
+
id: z.string(),
|
|
162
|
+
filename: z.string(),
|
|
163
|
+
content_type: z.string().optional(),
|
|
164
|
+
size: z.number().optional(),
|
|
165
|
+
created_at: z.string(),
|
|
166
|
+
})
|
|
167
|
+
),
|
|
168
|
+
total: z.number().describe('Total number of attachments'),
|
|
169
|
+
durationMs: z.number().describe('Operation duration in milliseconds'),
|
|
170
|
+
}),
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
async handler(ctx) {
|
|
174
|
+
const { args, options } = ctx;
|
|
175
|
+
const started = Date.now();
|
|
176
|
+
const storage = await createStorageAdapter(ctx);
|
|
177
|
+
|
|
178
|
+
const result = await storage.listAttachments(args.taskId);
|
|
179
|
+
const durationMs = Date.now() - started;
|
|
180
|
+
|
|
181
|
+
if (!options.json) {
|
|
182
|
+
if (result.attachments.length === 0) {
|
|
183
|
+
tui.info('No attachments found');
|
|
184
|
+
} else {
|
|
185
|
+
const tableData = result.attachments.map((att: Attachment) => ({
|
|
186
|
+
ID: tui.muted(truncate(att.id, 28)),
|
|
187
|
+
Filename: truncate(att.filename, 40),
|
|
188
|
+
'Content Type': att.content_type ?? tui.muted('—'),
|
|
189
|
+
Size: formatBytes(att.size),
|
|
190
|
+
Created: new Date(att.created_at).toLocaleDateString(),
|
|
191
|
+
}));
|
|
192
|
+
|
|
193
|
+
tui.table(tableData, [
|
|
194
|
+
{ name: 'ID', alignment: 'left' },
|
|
195
|
+
{ name: 'Filename', alignment: 'left' },
|
|
196
|
+
{ name: 'Content Type', alignment: 'left' },
|
|
197
|
+
{ name: 'Size', alignment: 'right' },
|
|
198
|
+
{ name: 'Created', alignment: 'left' },
|
|
199
|
+
]);
|
|
200
|
+
|
|
201
|
+
tui.info(
|
|
202
|
+
`${result.total} ${tui.plural(result.total, 'attachment', 'attachments')} (${durationMs.toFixed(1)}ms)`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
success: true,
|
|
209
|
+
attachments: result.attachments.map((att: Attachment) => ({
|
|
210
|
+
id: att.id,
|
|
211
|
+
filename: att.filename,
|
|
212
|
+
content_type: att.content_type,
|
|
213
|
+
size: att.size,
|
|
214
|
+
created_at: att.created_at,
|
|
215
|
+
})),
|
|
216
|
+
total: result.total,
|
|
217
|
+
durationMs,
|
|
218
|
+
};
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// ── Download ────────────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
const downloadSubcommand = createCommand({
|
|
225
|
+
name: 'download',
|
|
226
|
+
aliases: ['dl', 'get'],
|
|
227
|
+
description: 'Download a task attachment',
|
|
228
|
+
tags: ['read-only', 'slow', 'requires-auth'],
|
|
229
|
+
requires: { auth: true },
|
|
230
|
+
examples: [
|
|
231
|
+
{
|
|
232
|
+
command: getCommand('cloud task attachment download att_abc123'),
|
|
233
|
+
description: 'Download an attachment to the current directory',
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
command: getCommand('cloud task attachment download att_abc123 --output ./downloads/'),
|
|
237
|
+
description: 'Download an attachment to a specific directory',
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
schema: {
|
|
241
|
+
args: z.object({
|
|
242
|
+
attachmentId: z.string().min(1).describe('the attachment ID to download'),
|
|
243
|
+
}),
|
|
244
|
+
options: z.object({
|
|
245
|
+
output: z
|
|
246
|
+
.string()
|
|
247
|
+
.optional()
|
|
248
|
+
.describe('output file path or directory (defaults to current directory)'),
|
|
249
|
+
}),
|
|
250
|
+
response: z.object({
|
|
251
|
+
success: z.boolean().describe('Whether the operation succeeded'),
|
|
252
|
+
path: z.string().describe('Path where the file was saved'),
|
|
253
|
+
size: z.number().describe('Downloaded file size in bytes'),
|
|
254
|
+
durationMs: z.number().describe('Operation duration in milliseconds'),
|
|
255
|
+
}),
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
async handler(ctx) {
|
|
259
|
+
const { args, opts, options } = ctx;
|
|
260
|
+
const started = Date.now();
|
|
261
|
+
const storage = await createStorageAdapter(ctx);
|
|
262
|
+
|
|
263
|
+
// Step 1: Get presigned download URL
|
|
264
|
+
const presign = await tui.spinner({
|
|
265
|
+
message: 'Requesting download URL',
|
|
266
|
+
clearOnSuccess: true,
|
|
267
|
+
callback: async () => {
|
|
268
|
+
return storage.downloadAttachment(args.attachmentId);
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Step 2: Download the file
|
|
273
|
+
const response = await tui.spinner({
|
|
274
|
+
message: 'Downloading',
|
|
275
|
+
clearOnSuccess: true,
|
|
276
|
+
callback: async () => {
|
|
277
|
+
const res = await fetch(presign.presigned_url);
|
|
278
|
+
if (!res.ok) {
|
|
279
|
+
tui.fatal(`Download failed: ${res.statusText}`);
|
|
280
|
+
}
|
|
281
|
+
return res;
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Determine output path
|
|
286
|
+
// Extract filename from Content-Disposition header or URL
|
|
287
|
+
let filename = 'attachment';
|
|
288
|
+
const disposition = response.headers.get('content-disposition');
|
|
289
|
+
if (disposition) {
|
|
290
|
+
const match = disposition.match(/filename[*]?=(?:UTF-8''|"?)([^";]+)/i);
|
|
291
|
+
if (match?.[1]) {
|
|
292
|
+
filename = decodeURIComponent(match[1].replace(/"/g, ''));
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
// Try to extract filename from the presigned URL path
|
|
296
|
+
const urlPath = new URL(presign.presigned_url).pathname;
|
|
297
|
+
const urlFilename = basename(urlPath);
|
|
298
|
+
if (urlFilename && urlFilename !== '/') {
|
|
299
|
+
filename = decodeURIComponent(urlFilename);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Sanitize filename against path traversal
|
|
304
|
+
filename = filename.replace(/\0/g, ''); // strip null bytes
|
|
305
|
+
filename = filename.replace(/[/\\]/g, '_'); // replace path separators
|
|
306
|
+
filename = filename.replace(/^\.+/, ''); // strip leading dots
|
|
307
|
+
if (!filename || filename === '.' || filename === '..') {
|
|
308
|
+
filename = 'attachment';
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let outputPath: string;
|
|
312
|
+
if (opts.output) {
|
|
313
|
+
try {
|
|
314
|
+
const stats = await fsStat(opts.output);
|
|
315
|
+
if (stats.isDirectory()) {
|
|
316
|
+
outputPath = join(opts.output, filename);
|
|
317
|
+
} else {
|
|
318
|
+
// It's an existing file — use it directly
|
|
319
|
+
outputPath = opts.output;
|
|
320
|
+
}
|
|
321
|
+
} catch {
|
|
322
|
+
// Path doesn't exist — treat as target file path
|
|
323
|
+
outputPath = opts.output;
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
outputPath = join(process.cwd(), filename);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Step 3: Write file to disk
|
|
330
|
+
const size = await tui.spinner({
|
|
331
|
+
message: `Saving to ${outputPath}`,
|
|
332
|
+
clearOnSuccess: true,
|
|
333
|
+
callback: async () => {
|
|
334
|
+
const bytes = await Bun.write(outputPath, response);
|
|
335
|
+
return bytes;
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const durationMs = Date.now() - started;
|
|
340
|
+
|
|
341
|
+
if (!options.json) {
|
|
342
|
+
tui.success(`Downloaded to ${tui.bold(outputPath)} (${formatBytes(size)})`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
success: true,
|
|
347
|
+
path: outputPath,
|
|
348
|
+
size,
|
|
349
|
+
durationMs,
|
|
350
|
+
};
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// ── Delete ──────────────────────────────────────────────────────────────
|
|
355
|
+
|
|
356
|
+
const deleteAttachmentSubcommand = createCommand({
|
|
357
|
+
name: 'delete',
|
|
358
|
+
aliases: ['rm', 'remove'],
|
|
359
|
+
description: 'Delete a task attachment',
|
|
360
|
+
tags: ['mutating', 'slow', 'requires-auth'],
|
|
361
|
+
requires: { auth: true },
|
|
362
|
+
examples: [
|
|
363
|
+
{
|
|
364
|
+
command: getCommand('cloud task attachment delete att_abc123'),
|
|
365
|
+
description: 'Delete an attachment',
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
schema: {
|
|
369
|
+
args: z.object({
|
|
370
|
+
attachmentId: z.string().min(1).describe('the attachment ID to delete'),
|
|
371
|
+
}),
|
|
372
|
+
response: z.object({
|
|
373
|
+
success: z.boolean().describe('Whether the operation succeeded'),
|
|
374
|
+
attachmentId: z.string().describe('Deleted attachment ID'),
|
|
375
|
+
durationMs: z.number().describe('Operation duration in milliseconds'),
|
|
376
|
+
}),
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
async handler(ctx) {
|
|
380
|
+
const { args, options } = ctx;
|
|
381
|
+
const started = Date.now();
|
|
382
|
+
const storage = await createStorageAdapter(ctx);
|
|
383
|
+
|
|
384
|
+
await storage.deleteAttachment(args.attachmentId);
|
|
385
|
+
|
|
386
|
+
const durationMs = Date.now() - started;
|
|
387
|
+
|
|
388
|
+
if (!options.json) {
|
|
389
|
+
tui.success(`Attachment deleted: ${tui.bold(args.attachmentId)}`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
success: true,
|
|
394
|
+
attachmentId: args.attachmentId,
|
|
395
|
+
durationMs,
|
|
396
|
+
};
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// ── Parent command ──────────────────────────────────────────────────────
|
|
401
|
+
|
|
402
|
+
export const attachmentSubcommand = createCommand({
|
|
403
|
+
name: 'attachment',
|
|
404
|
+
aliases: ['attach', 'att'],
|
|
405
|
+
description: 'Manage task attachments',
|
|
406
|
+
tags: ['requires-auth'],
|
|
407
|
+
requires: { auth: true },
|
|
408
|
+
examples: [
|
|
409
|
+
{
|
|
410
|
+
command: getCommand('cloud task attachment upload task_abc123 ./report.pdf'),
|
|
411
|
+
description: 'Upload a file to a task',
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
command: getCommand('cloud task attachment list task_abc123'),
|
|
415
|
+
description: 'List task attachments',
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
command: getCommand('cloud task attachment download att_abc123'),
|
|
419
|
+
description: 'Download an attachment',
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
command: getCommand('cloud task attachment delete att_abc123'),
|
|
423
|
+
description: 'Delete an attachment',
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
subcommands: [
|
|
427
|
+
uploadSubcommand,
|
|
428
|
+
listAttachmentsSubcommand,
|
|
429
|
+
downloadSubcommand,
|
|
430
|
+
deleteAttachmentSubcommand,
|
|
431
|
+
],
|
|
432
|
+
});
|