@agentuity/cli 0.1.42 → 0.1.44
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/auth.d.ts +2 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +7 -5
- package/dist/auth.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +24 -12
- package/dist/cli.js.map +1 -1
- package/dist/cmd/build/entry-generator.d.ts.map +1 -1
- package/dist/cmd/build/entry-generator.js +163 -18
- package/dist/cmd/build/entry-generator.js.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.js +19 -9
- package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
- package/dist/cmd/build/vite/public-asset-path-plugin.d.ts +24 -15
- package/dist/cmd/build/vite/public-asset-path-plugin.d.ts.map +1 -1
- package/dist/cmd/build/vite/public-asset-path-plugin.js +92 -47
- package/dist/cmd/build/vite/public-asset-path-plugin.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server-config.js +9 -6
- package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-asset-server.js +1 -1
- package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +12 -11
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/cloud/env/org-util.d.ts +2 -1
- package/dist/cmd/cloud/env/org-util.d.ts.map +1 -1
- package/dist/cmd/cloud/env/org-util.js +4 -2
- package/dist/cmd/cloud/env/org-util.js.map +1 -1
- package/dist/cmd/cloud/stream/create.d.ts +3 -0
- package/dist/cmd/cloud/stream/create.d.ts.map +1 -0
- package/dist/cmd/cloud/stream/create.js +227 -0
- package/dist/cmd/cloud/stream/create.js.map +1 -0
- package/dist/cmd/cloud/stream/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/stream/delete.js +2 -1
- package/dist/cmd/cloud/stream/delete.js.map +1 -1
- package/dist/cmd/cloud/stream/get.d.ts.map +1 -1
- package/dist/cmd/cloud/stream/get.js +2 -1
- package/dist/cmd/cloud/stream/get.js.map +1 -1
- package/dist/cmd/cloud/stream/index.d.ts.map +1 -1
- package/dist/cmd/cloud/stream/index.js +10 -1
- package/dist/cmd/cloud/stream/index.js.map +1 -1
- package/dist/cmd/cloud/stream/list.d.ts.map +1 -1
- package/dist/cmd/cloud/stream/list.js +2 -1
- package/dist/cmd/cloud/stream/list.js.map +1 -1
- package/dist/cmd/cloud/stream/util.d.ts +6 -5
- package/dist/cmd/cloud/stream/util.d.ts.map +1 -1
- package/dist/cmd/cloud/stream/util.js +26 -5
- package/dist/cmd/cloud/stream/util.js.map +1 -1
- package/dist/cmd/support/report.d.ts.map +1 -1
- package/dist/cmd/support/report.js +58 -23
- package/dist/cmd/support/report.js.map +1 -1
- package/dist/cmd/upgrade/index.d.ts.map +1 -1
- package/dist/cmd/upgrade/index.js +23 -0
- package/dist/cmd/upgrade/index.js.map +1 -1
- package/dist/cmd/upgrade/npm-availability.d.ts +44 -0
- package/dist/cmd/upgrade/npm-availability.d.ts.map +1 -0
- package/dist/cmd/upgrade/npm-availability.js +73 -0
- package/dist/cmd/upgrade/npm-availability.js.map +1 -0
- package/dist/internal-logger.d.ts +7 -0
- package/dist/internal-logger.d.ts.map +1 -1
- package/dist/internal-logger.js +82 -28
- package/dist/internal-logger.js.map +1 -1
- package/dist/tui.d.ts +9 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +39 -14
- package/dist/tui.js.map +1 -1
- package/dist/version-check.d.ts.map +1 -1
- package/dist/version-check.js +13 -2
- package/dist/version-check.js.map +1 -1
- package/package.json +8 -7
- package/src/auth.ts +9 -5
- package/src/cli.ts +44 -12
- package/src/cmd/build/entry-generator.ts +163 -18
- package/src/cmd/build/vite/metadata-generator.ts +20 -9
- package/src/cmd/build/vite/public-asset-path-plugin.ts +105 -53
- package/src/cmd/build/vite/vite-asset-server-config.ts +9 -6
- package/src/cmd/build/vite/vite-asset-server.ts +3 -1
- package/src/cmd/build/vite/vite-builder.ts +21 -20
- package/src/cmd/cloud/env/org-util.ts +5 -2
- package/src/cmd/cloud/stream/create.ts +248 -0
- package/src/cmd/cloud/stream/delete.ts +2 -1
- package/src/cmd/cloud/stream/get.ts +2 -1
- package/src/cmd/cloud/stream/index.ts +10 -1
- package/src/cmd/cloud/stream/list.ts +2 -1
- package/src/cmd/cloud/stream/util.ts +39 -12
- package/src/cmd/support/report.ts +82 -28
- package/src/cmd/upgrade/index.ts +25 -0
- package/src/cmd/upgrade/npm-availability.ts +105 -0
- package/src/internal-logger.ts +91 -27
- package/src/tui.ts +42 -14
- package/src/version-check.ts +19 -3
|
@@ -68,7 +68,9 @@ export async function generateAssetServerConfig(
|
|
|
68
68
|
root: rootDir,
|
|
69
69
|
base: '/',
|
|
70
70
|
clearScreen: false,
|
|
71
|
-
|
|
71
|
+
// Serve public assets from src/web/public/ at root path (e.g., /favicon.png)
|
|
72
|
+
// The Bun server proxies /public/* requests to Vite, rewriting to root paths
|
|
73
|
+
publicDir: join(rootDir, 'src', 'web', 'public'),
|
|
72
74
|
|
|
73
75
|
resolve: {
|
|
74
76
|
alias,
|
|
@@ -99,12 +101,13 @@ export async function generateAssetServerConfig(
|
|
|
99
101
|
credentials: true,
|
|
100
102
|
},
|
|
101
103
|
|
|
102
|
-
// HMR configuration
|
|
103
|
-
// Do NOT set
|
|
104
|
-
//
|
|
104
|
+
// HMR configuration for development with tunnel support (*.agentuity.live)
|
|
105
|
+
// Do NOT set host/protocol - let Vite auto-detect from page origin
|
|
106
|
+
// This allows HMR to work both locally and through the Gravity tunnel
|
|
107
|
+
// The Bun server proxies /__vite_hmr WebSocket connections to Vite
|
|
105
108
|
hmr: {
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
// Use a dedicated path for HMR WebSocket to enable proxying
|
|
110
|
+
path: '/__vite_hmr',
|
|
108
111
|
},
|
|
109
112
|
|
|
110
113
|
// Don't open browser - Bun server will be the entry point
|
|
@@ -119,7 +119,9 @@ export async function startViteAssetServer(
|
|
|
119
119
|
);
|
|
120
120
|
}
|
|
121
121
|
logger.debug(`Asset server will handle: HMR, React transformation, source maps`);
|
|
122
|
-
logger.debug(
|
|
122
|
+
logger.debug(
|
|
123
|
+
`HMR WebSocket configured at /__vite_hmr (proxied through Bun server for tunnel support)`
|
|
124
|
+
);
|
|
123
125
|
|
|
124
126
|
return { server, port: actualPort };
|
|
125
127
|
}
|
|
@@ -149,13 +149,22 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
|
|
|
149
149
|
analyticsEnabled = false,
|
|
150
150
|
} = options;
|
|
151
151
|
|
|
152
|
+
// Determine CDN base URL for production builds
|
|
153
|
+
// Use CDN for all non-dev builds with a deploymentId (including local region)
|
|
154
|
+
const isLocalRegion = options.region === 'local';
|
|
155
|
+
const cdnDomain = isLocalRegion
|
|
156
|
+
? 'localstack-static-assets.t3.storage.dev'
|
|
157
|
+
: 'cdn.agentuity.com';
|
|
158
|
+
const cdnBaseUrl =
|
|
159
|
+
!dev && deploymentId ? `https://${cdnDomain}/${deploymentId}/client/` : undefined;
|
|
160
|
+
|
|
152
161
|
// Load custom user plugins from agentuity.config.ts if it exists
|
|
153
162
|
const clientOutDir = join(rootDir, '.agentuity/client');
|
|
154
163
|
const plugins = [
|
|
155
164
|
react(),
|
|
156
165
|
browserEnvPlugin(),
|
|
157
|
-
// Fix incorrect public asset paths
|
|
158
|
-
publicAssetPathPlugin(),
|
|
166
|
+
// Fix incorrect public asset paths and rewrite to CDN URLs
|
|
167
|
+
publicAssetPathPlugin({ cdnBaseUrl }),
|
|
159
168
|
flattenHtmlOutputPlugin(clientOutDir),
|
|
160
169
|
// Emit analytics beacon as hashed CDN asset (prod builds only)
|
|
161
170
|
beaconPlugin({ enabled: analyticsEnabled && !dev }),
|
|
@@ -177,15 +186,6 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
|
|
|
177
186
|
);
|
|
178
187
|
}
|
|
179
188
|
|
|
180
|
-
// Determine CDN base URL for production builds
|
|
181
|
-
// Use CDN for all non-dev builds with a deploymentId (including local region)
|
|
182
|
-
const isLocalRegion = options.region === 'local';
|
|
183
|
-
const cdnDomain = isLocalRegion
|
|
184
|
-
? 'localstack-static-assets.t3.storage.dev'
|
|
185
|
-
: 'cdn.agentuity.com';
|
|
186
|
-
const cdnBaseUrl =
|
|
187
|
-
!dev && deploymentId ? `https://${cdnDomain}/${deploymentId}/client/` : undefined;
|
|
188
|
-
|
|
189
189
|
viteConfig = {
|
|
190
190
|
// Use project root as Vite root so plugins (e.g., TanStack Router) resolve paths
|
|
191
191
|
// from the repo root, matching where agentuity.config.ts is located
|
|
@@ -203,16 +203,17 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
|
|
|
203
203
|
? JSON.stringify(workbenchRoute)
|
|
204
204
|
: 'undefined',
|
|
205
205
|
},
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
},
|
|
211
|
-
manifest: true,
|
|
212
|
-
emptyOutDir: true,
|
|
213
|
-
// Disable copying public files - Vite already includes them in assets with hashing
|
|
214
|
-
copyPublicDir: false,
|
|
206
|
+
build: {
|
|
207
|
+
outDir: clientOutDir,
|
|
208
|
+
rollupOptions: {
|
|
209
|
+
input: htmlPath,
|
|
215
210
|
},
|
|
211
|
+
manifest: true,
|
|
212
|
+
emptyOutDir: true,
|
|
213
|
+
// Copy public files to output for CDN upload (production builds only)
|
|
214
|
+
// In dev mode, Vite serves them directly from src/web/public/
|
|
215
|
+
copyPublicDir: !dev,
|
|
216
|
+
},
|
|
216
217
|
logLevel: 'warn',
|
|
217
218
|
};
|
|
218
219
|
} else if (mode === 'workbench') {
|
|
@@ -9,12 +9,14 @@ import { listOrganizations } from '@agentuity/server';
|
|
|
9
9
|
* @param apiClient - The API client
|
|
10
10
|
* @param config - The CLI config (may be null)
|
|
11
11
|
* @param orgOption - The --org option value (true for default/prompt, or explicit org ID)
|
|
12
|
+
* @param autoSelect - If true, auto-select preferred org without prompting (for --confirm)
|
|
12
13
|
* @returns The resolved organization ID
|
|
13
14
|
*/
|
|
14
15
|
export async function resolveOrgId(
|
|
15
16
|
apiClient: APIClient,
|
|
16
17
|
config: Config | null,
|
|
17
|
-
orgOption: boolean | string
|
|
18
|
+
orgOption: boolean | string,
|
|
19
|
+
autoSelect?: boolean
|
|
18
20
|
): Promise<string> {
|
|
19
21
|
// If an explicit org ID was provided (string), use it directly
|
|
20
22
|
if (typeof orgOption === 'string' && orgOption !== 'true') {
|
|
@@ -25,8 +27,9 @@ export async function resolveOrgId(
|
|
|
25
27
|
const orgs = await tui.spinner('Fetching organizations', () => listOrganizations(apiClient));
|
|
26
28
|
|
|
27
29
|
// Use preference if available, otherwise prompt
|
|
30
|
+
// Pass autoSelect to skip prompting when --confirm is used
|
|
28
31
|
const preferredOrgId = config?.preferences?.orgId;
|
|
29
|
-
return tui.selectOrganization(orgs, preferredOrgId);
|
|
32
|
+
return tui.selectOrganization(orgs, preferredOrgId, autoSelect);
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
/**
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { basename } from 'path';
|
|
3
|
+
import { createCommand } from '../../../types';
|
|
4
|
+
import * as tui from '../../../tui';
|
|
5
|
+
import { createStorageAdapter } from './util';
|
|
6
|
+
import { getCommand } from '../../../command-prefix';
|
|
7
|
+
|
|
8
|
+
const StreamCreateResponseSchema = z.object({
|
|
9
|
+
id: z.string().describe('Stream ID'),
|
|
10
|
+
namespace: z.string().describe('Stream namespace'),
|
|
11
|
+
url: z.string().describe('Public URL'),
|
|
12
|
+
sizeBytes: z.number().describe('Size in bytes'),
|
|
13
|
+
metadata: z.record(z.string(), z.string()).describe('Stream metadata'),
|
|
14
|
+
expiresAt: z.string().optional().describe('Expiration timestamp'),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const createSubcommand = createCommand({
|
|
18
|
+
name: 'create',
|
|
19
|
+
aliases: ['new'],
|
|
20
|
+
description: 'Create a new stream and upload content',
|
|
21
|
+
tags: ['mutating', 'creates-resource', 'slow', 'requires-auth', 'uses-stdin'],
|
|
22
|
+
requires: { auth: true, region: true },
|
|
23
|
+
optional: { project: true },
|
|
24
|
+
idempotent: false,
|
|
25
|
+
examples: [
|
|
26
|
+
{
|
|
27
|
+
command: getCommand('cloud stream create memory-share ./notes.md'),
|
|
28
|
+
description: 'Create stream from file',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
command: getCommand(
|
|
32
|
+
'cloud stream create memory-share ./data.json --content-type application/json'
|
|
33
|
+
),
|
|
34
|
+
description: 'Create stream with explicit content type',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
command: `cat summary.md | ${getCommand('cloud stream create memory-share -')}`,
|
|
38
|
+
description: 'Create stream from stdin',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
command: getCommand('cloud stream create memory-share ./notes.md --ttl 3600'),
|
|
42
|
+
description: 'Create stream with 1 hour TTL',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
command: getCommand(
|
|
46
|
+
'cloud stream create memory-share ./notes.md --metadata type=summary,source=session'
|
|
47
|
+
),
|
|
48
|
+
description: 'Create stream with metadata',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
command: getCommand('cloud stream create memory-share ./large.json --compress'),
|
|
52
|
+
description: 'Create compressed stream',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
schema: {
|
|
56
|
+
args: z.object({
|
|
57
|
+
namespace: z.string().min(1).max(254).describe('Stream namespace (1-254 characters)'),
|
|
58
|
+
filename: z.string().describe('File path to upload or "-" for STDIN'),
|
|
59
|
+
}),
|
|
60
|
+
options: z.object({
|
|
61
|
+
metadata: z
|
|
62
|
+
.string()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe('Metadata key=value pairs (comma-separated: key1=value1,key2=value2)'),
|
|
65
|
+
contentType: z
|
|
66
|
+
.string()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe('Content type (auto-detected from extension if not provided)'),
|
|
69
|
+
compress: z.boolean().optional().describe('Enable gzip compression'),
|
|
70
|
+
ttl: z.coerce
|
|
71
|
+
.number()
|
|
72
|
+
.optional()
|
|
73
|
+
.describe('TTL in seconds (60-7776000, or 0/null for never expires)'),
|
|
74
|
+
}),
|
|
75
|
+
response: StreamCreateResponseSchema,
|
|
76
|
+
},
|
|
77
|
+
webUrl: '/services/stream',
|
|
78
|
+
|
|
79
|
+
async handler(ctx) {
|
|
80
|
+
const { args, opts, options } = ctx;
|
|
81
|
+
const started = Date.now();
|
|
82
|
+
const storage = await createStorageAdapter(ctx);
|
|
83
|
+
|
|
84
|
+
// Parse metadata if provided
|
|
85
|
+
let metadata: Record<string, string> | undefined;
|
|
86
|
+
if (opts.metadata) {
|
|
87
|
+
const validPairs: Record<string, string> = {};
|
|
88
|
+
const malformed: string[] = [];
|
|
89
|
+
const pairs = opts.metadata.split(',');
|
|
90
|
+
|
|
91
|
+
for (const pair of pairs) {
|
|
92
|
+
const trimmedPair = pair.trim();
|
|
93
|
+
if (!trimmedPair) continue;
|
|
94
|
+
|
|
95
|
+
const firstEqualIdx = trimmedPair.indexOf('=');
|
|
96
|
+
if (firstEqualIdx === -1) {
|
|
97
|
+
malformed.push(trimmedPair);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const key = trimmedPair.substring(0, firstEqualIdx).trim();
|
|
102
|
+
const value = trimmedPair.substring(firstEqualIdx + 1).trim();
|
|
103
|
+
|
|
104
|
+
if (!key || !value) {
|
|
105
|
+
malformed.push(trimmedPair);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
validPairs[key] = value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (malformed.length > 0) {
|
|
113
|
+
ctx.logger.warn(`Skipping malformed metadata pairs: ${malformed.join(', ')}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (Object.keys(validPairs).length > 0) {
|
|
117
|
+
metadata = validPairs;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Determine content type
|
|
122
|
+
let contentType = opts.contentType;
|
|
123
|
+
if (!contentType) {
|
|
124
|
+
// Auto-detect from filename extension
|
|
125
|
+
const filename = args.filename === '-' ? 'stdin' : args.filename;
|
|
126
|
+
const dotIndex = filename.lastIndexOf('.');
|
|
127
|
+
const ext = dotIndex > 0 ? filename.substring(dotIndex + 1).toLowerCase() : undefined;
|
|
128
|
+
// Text-based types should include charset=utf-8 for proper browser rendering
|
|
129
|
+
const mimeTypes: Record<string, string> = {
|
|
130
|
+
txt: 'text/plain; charset=utf-8',
|
|
131
|
+
md: 'text/markdown; charset=utf-8',
|
|
132
|
+
html: 'text/html; charset=utf-8',
|
|
133
|
+
css: 'text/css; charset=utf-8',
|
|
134
|
+
yaml: 'application/x-yaml; charset=utf-8',
|
|
135
|
+
yml: 'application/x-yaml; charset=utf-8',
|
|
136
|
+
js: 'application/javascript; charset=utf-8',
|
|
137
|
+
ts: 'application/typescript; charset=utf-8',
|
|
138
|
+
json: 'application/json; charset=utf-8',
|
|
139
|
+
xml: 'application/xml; charset=utf-8',
|
|
140
|
+
pdf: 'application/pdf',
|
|
141
|
+
zip: 'application/zip',
|
|
142
|
+
jpg: 'image/jpeg',
|
|
143
|
+
jpeg: 'image/jpeg',
|
|
144
|
+
png: 'image/png',
|
|
145
|
+
gif: 'image/gif',
|
|
146
|
+
svg: 'image/svg+xml; charset=utf-8',
|
|
147
|
+
mp4: 'video/mp4',
|
|
148
|
+
mp3: 'audio/mpeg',
|
|
149
|
+
};
|
|
150
|
+
contentType = ext ? mimeTypes[ext] : 'application/octet-stream';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Create the stream
|
|
154
|
+
const stream = await storage.create(args.namespace, {
|
|
155
|
+
metadata,
|
|
156
|
+
contentType,
|
|
157
|
+
compress: opts.compress ? true : undefined,
|
|
158
|
+
ttl: opts.ttl,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Read and write content
|
|
162
|
+
let inputStream: ReadableStream<Uint8Array>;
|
|
163
|
+
|
|
164
|
+
if (args.filename === '-') {
|
|
165
|
+
// Stream from STDIN
|
|
166
|
+
inputStream = Bun.stdin.stream();
|
|
167
|
+
} else {
|
|
168
|
+
// Stream from file
|
|
169
|
+
const file = Bun.file(args.filename);
|
|
170
|
+
if (!(await file.exists())) {
|
|
171
|
+
tui.fatal(`File not found: ${args.filename}`);
|
|
172
|
+
}
|
|
173
|
+
inputStream = file.stream();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Write content to stream in chunks
|
|
177
|
+
const reader = inputStream.getReader();
|
|
178
|
+
const MAX_CHUNK_SIZE = 5 * 1024 * 1024; // 5MB max per write
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
while (true) {
|
|
182
|
+
const { done, value } = await reader.read();
|
|
183
|
+
if (done) break;
|
|
184
|
+
|
|
185
|
+
// If chunk is larger than 5MB, split it
|
|
186
|
+
if (value.length > MAX_CHUNK_SIZE) {
|
|
187
|
+
let offset = 0;
|
|
188
|
+
while (offset < value.length) {
|
|
189
|
+
const chunk = value.slice(offset, offset + MAX_CHUNK_SIZE);
|
|
190
|
+
await stream.write(chunk);
|
|
191
|
+
offset += MAX_CHUNK_SIZE;
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
await stream.write(value);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} finally {
|
|
198
|
+
reader.releaseLock();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Close the stream
|
|
202
|
+
await stream.close();
|
|
203
|
+
|
|
204
|
+
const durationMs = Date.now() - started;
|
|
205
|
+
const sizeBytes = stream.bytesWritten;
|
|
206
|
+
|
|
207
|
+
// Get stream info to retrieve expiresAt
|
|
208
|
+
let expiresAt: string | undefined;
|
|
209
|
+
try {
|
|
210
|
+
const info = await storage.get(stream.id);
|
|
211
|
+
expiresAt = info.expiresAt;
|
|
212
|
+
} catch {
|
|
213
|
+
// expiresAt is optional, ignore errors
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!options.json) {
|
|
217
|
+
const sourceLabel = args.filename === '-' ? 'stdin' : basename(args.filename);
|
|
218
|
+
console.log(`Namespace: ${tui.bold(args.namespace)}`);
|
|
219
|
+
console.log(`ID: ${stream.id}`);
|
|
220
|
+
console.log(`Size: ${tui.formatBytes(sizeBytes)}`);
|
|
221
|
+
console.log(`URL: ${tui.link(stream.url)}`);
|
|
222
|
+
if (expiresAt) {
|
|
223
|
+
console.log(`Expires: ${expiresAt}`);
|
|
224
|
+
}
|
|
225
|
+
if (metadata && Object.keys(metadata).length > 0) {
|
|
226
|
+
console.log(`Metadata:`);
|
|
227
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
228
|
+
console.log(` ${key}: ${value}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (opts.compress) {
|
|
232
|
+
console.log(`Compressed: yes`);
|
|
233
|
+
}
|
|
234
|
+
tui.success(`created stream from ${sourceLabel} in ${durationMs.toFixed(1)}ms`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
id: stream.id,
|
|
239
|
+
namespace: args.namespace,
|
|
240
|
+
url: stream.url,
|
|
241
|
+
sizeBytes,
|
|
242
|
+
metadata: metadata ?? {},
|
|
243
|
+
expiresAt,
|
|
244
|
+
};
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
export default createSubcommand;
|
|
@@ -13,7 +13,8 @@ export const deleteSubcommand = createCommand({
|
|
|
13
13
|
description: 'Delete a stream by ID (soft delete)',
|
|
14
14
|
tags: ['destructive', 'deletes-resource', 'slow', 'requires-auth'],
|
|
15
15
|
idempotent: true,
|
|
16
|
-
requires: { auth: true,
|
|
16
|
+
requires: { auth: true, region: true },
|
|
17
|
+
optional: { project: true },
|
|
17
18
|
examples: [
|
|
18
19
|
{ command: getCommand('stream delete stream-id-123'), description: 'Delete a stream' },
|
|
19
20
|
{
|
|
@@ -16,7 +16,8 @@ export const getSubcommand = createCommand({
|
|
|
16
16
|
name: 'get',
|
|
17
17
|
description: 'Get detailed information about a specific stream',
|
|
18
18
|
tags: ['read-only', 'slow', 'requires-auth'],
|
|
19
|
-
requires: { auth: true,
|
|
19
|
+
requires: { auth: true, region: true },
|
|
20
|
+
optional: { project: true },
|
|
20
21
|
idempotent: true,
|
|
21
22
|
examples: [
|
|
22
23
|
{ command: getCommand('stream get stream-id-123'), description: 'Get stream details' },
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createCommand } from '../../../types';
|
|
2
|
+
import createSubcommand from './create';
|
|
2
3
|
import listSubcommand from './list';
|
|
3
4
|
import getSubcommand from './get';
|
|
4
5
|
import deleteSubcommand from './delete';
|
|
@@ -10,10 +11,18 @@ export const streamCommand = createCommand({
|
|
|
10
11
|
description: 'Manage durable streams',
|
|
11
12
|
tags: ['slow', 'requires-auth'],
|
|
12
13
|
examples: [
|
|
14
|
+
{
|
|
15
|
+
command: getCommand('cloud stream create memory-share ./notes.md'),
|
|
16
|
+
description: 'Create stream from file',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
command: `cat data.json | ${getCommand('cloud stream create memory-share -')}`,
|
|
20
|
+
description: 'Create stream from stdin',
|
|
21
|
+
},
|
|
13
22
|
{ command: getCommand('cloud stream list'), description: 'List all streams' },
|
|
14
23
|
{ command: getCommand('cloud stream get <id>'), description: 'Get stream details' },
|
|
15
24
|
],
|
|
16
|
-
subcommands: [listSubcommand, getSubcommand, deleteSubcommand],
|
|
25
|
+
subcommands: [createSubcommand, listSubcommand, getSubcommand, deleteSubcommand],
|
|
17
26
|
});
|
|
18
27
|
|
|
19
28
|
export default streamCommand;
|
|
@@ -22,7 +22,8 @@ export const listSubcommand = createCommand({
|
|
|
22
22
|
aliases: ['ls'],
|
|
23
23
|
description: 'List recent streams with optional filtering',
|
|
24
24
|
tags: ['read-only', 'slow', 'requires-auth'],
|
|
25
|
-
requires: { auth: true,
|
|
25
|
+
requires: { auth: true, region: true },
|
|
26
|
+
optional: { project: true },
|
|
26
27
|
idempotent: true,
|
|
27
28
|
examples: [
|
|
28
29
|
{ command: getCommand('cloud stream list'), description: 'List all streams' },
|
|
@@ -1,35 +1,62 @@
|
|
|
1
|
-
import { StreamStorageService, Logger } from '@agentuity/core';
|
|
1
|
+
import { StreamStorageService, type Logger } from '@agentuity/core';
|
|
2
2
|
import { createServerFetchAdapter, getServiceUrls } from '@agentuity/server';
|
|
3
3
|
import { loadProjectSDKKey } from '../../../config';
|
|
4
|
-
import {
|
|
5
|
-
import type { Config } from '../../../types';
|
|
4
|
+
import type { AuthData, Config, GlobalOptions, ProjectConfig } from '../../../types';
|
|
6
5
|
import * as tui from '../../../tui';
|
|
7
6
|
|
|
8
7
|
export async function createStorageAdapter(ctx: {
|
|
9
8
|
logger: Logger;
|
|
10
9
|
projectDir: string;
|
|
10
|
+
auth: AuthData;
|
|
11
|
+
region: string;
|
|
12
|
+
project?: ProjectConfig;
|
|
11
13
|
config: Config | null;
|
|
12
|
-
|
|
14
|
+
options: GlobalOptions;
|
|
13
15
|
}) {
|
|
16
|
+
// Try to get SDK key from project context first (preferred for project-based auth)
|
|
14
17
|
const sdkKey = await loadProjectSDKKey(ctx.logger, ctx.projectDir);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
|
|
19
|
+
let authToken: string;
|
|
20
|
+
let queryParams: Record<string, string> | undefined;
|
|
21
|
+
|
|
22
|
+
if (sdkKey) {
|
|
23
|
+
// Use SDK key auth (project context available)
|
|
24
|
+
authToken = sdkKey;
|
|
25
|
+
ctx.logger.trace('using SDK key auth for stream');
|
|
26
|
+
} else {
|
|
27
|
+
// Use CLI key auth with orgId query param
|
|
28
|
+
// Pulse server expects orgId as query param for CLI tokens (ck_*)
|
|
29
|
+
// IMPORTANT: For CLI key auth, prefer user's org ID over project's org ID
|
|
30
|
+
// because the CLI key is validated against the user's orgs, not the project's org
|
|
31
|
+
const orgId =
|
|
32
|
+
ctx.options.orgId ??
|
|
33
|
+
process.env.AGENTUITY_CLOUD_ORG_ID ??
|
|
34
|
+
ctx.config?.preferences?.orgId ??
|
|
35
|
+
ctx.project?.orgId;
|
|
36
|
+
|
|
37
|
+
if (!orgId) {
|
|
38
|
+
tui.fatal(
|
|
39
|
+
'Organization ID is required. Either run from a project directory, use --org-id flag, or set AGENTUITY_CLOUD_ORG_ID environment variable.'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
authToken = ctx.auth.apiKey;
|
|
44
|
+
queryParams = { orgId };
|
|
45
|
+
ctx.logger.trace('using CLI key auth with orgId query param for stream');
|
|
20
46
|
}
|
|
21
47
|
|
|
48
|
+
const baseUrl = getServiceUrls(ctx.region).stream;
|
|
49
|
+
|
|
22
50
|
const adapter = createServerFetchAdapter(
|
|
23
51
|
{
|
|
24
52
|
headers: {
|
|
25
|
-
Authorization: `Bearer ${
|
|
53
|
+
Authorization: `Bearer ${authToken}`,
|
|
26
54
|
},
|
|
55
|
+
queryParams,
|
|
27
56
|
},
|
|
28
57
|
ctx.logger
|
|
29
58
|
);
|
|
30
59
|
|
|
31
|
-
const baseUrl = getServiceUrls(ctx.project.region).stream;
|
|
32
|
-
|
|
33
60
|
ctx.logger.trace('using stream url: %s', baseUrl);
|
|
34
61
|
|
|
35
62
|
return new StreamStorageService(baseUrl, adapter);
|