@agentuity/cli 0.1.43 → 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.
Files changed (72) hide show
  1. package/dist/auth.d.ts +2 -2
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +7 -5
  4. package/dist/auth.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +24 -12
  7. package/dist/cli.js.map +1 -1
  8. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  9. package/dist/cmd/build/entry-generator.js +26 -17
  10. package/dist/cmd/build/entry-generator.js.map +1 -1
  11. package/dist/cmd/build/vite/public-asset-path-plugin.d.ts +17 -20
  12. package/dist/cmd/build/vite/public-asset-path-plugin.d.ts.map +1 -1
  13. package/dist/cmd/build/vite/public-asset-path-plugin.js +62 -43
  14. package/dist/cmd/build/vite/public-asset-path-plugin.js.map +1 -1
  15. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
  16. package/dist/cmd/build/vite/vite-asset-server-config.js +3 -1
  17. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
  18. package/dist/cmd/cloud/env/org-util.d.ts +2 -1
  19. package/dist/cmd/cloud/env/org-util.d.ts.map +1 -1
  20. package/dist/cmd/cloud/env/org-util.js +4 -2
  21. package/dist/cmd/cloud/env/org-util.js.map +1 -1
  22. package/dist/cmd/cloud/stream/create.d.ts +3 -0
  23. package/dist/cmd/cloud/stream/create.d.ts.map +1 -0
  24. package/dist/cmd/cloud/stream/create.js +227 -0
  25. package/dist/cmd/cloud/stream/create.js.map +1 -0
  26. package/dist/cmd/cloud/stream/delete.d.ts.map +1 -1
  27. package/dist/cmd/cloud/stream/delete.js +2 -1
  28. package/dist/cmd/cloud/stream/delete.js.map +1 -1
  29. package/dist/cmd/cloud/stream/get.d.ts.map +1 -1
  30. package/dist/cmd/cloud/stream/get.js +2 -1
  31. package/dist/cmd/cloud/stream/get.js.map +1 -1
  32. package/dist/cmd/cloud/stream/index.d.ts.map +1 -1
  33. package/dist/cmd/cloud/stream/index.js +10 -1
  34. package/dist/cmd/cloud/stream/index.js.map +1 -1
  35. package/dist/cmd/cloud/stream/list.d.ts.map +1 -1
  36. package/dist/cmd/cloud/stream/list.js +2 -1
  37. package/dist/cmd/cloud/stream/list.js.map +1 -1
  38. package/dist/cmd/cloud/stream/util.d.ts +6 -5
  39. package/dist/cmd/cloud/stream/util.d.ts.map +1 -1
  40. package/dist/cmd/cloud/stream/util.js +26 -5
  41. package/dist/cmd/cloud/stream/util.js.map +1 -1
  42. package/dist/cmd/upgrade/index.d.ts.map +1 -1
  43. package/dist/cmd/upgrade/index.js +23 -0
  44. package/dist/cmd/upgrade/index.js.map +1 -1
  45. package/dist/cmd/upgrade/npm-availability.d.ts +44 -0
  46. package/dist/cmd/upgrade/npm-availability.d.ts.map +1 -0
  47. package/dist/cmd/upgrade/npm-availability.js +73 -0
  48. package/dist/cmd/upgrade/npm-availability.js.map +1 -0
  49. package/dist/tui.d.ts +9 -1
  50. package/dist/tui.d.ts.map +1 -1
  51. package/dist/tui.js +39 -14
  52. package/dist/tui.js.map +1 -1
  53. package/dist/version-check.d.ts.map +1 -1
  54. package/dist/version-check.js +13 -2
  55. package/dist/version-check.js.map +1 -1
  56. package/package.json +6 -6
  57. package/src/auth.ts +9 -5
  58. package/src/cli.ts +44 -12
  59. package/src/cmd/build/entry-generator.ts +26 -17
  60. package/src/cmd/build/vite/public-asset-path-plugin.ts +73 -51
  61. package/src/cmd/build/vite/vite-asset-server-config.ts +3 -1
  62. package/src/cmd/cloud/env/org-util.ts +5 -2
  63. package/src/cmd/cloud/stream/create.ts +248 -0
  64. package/src/cmd/cloud/stream/delete.ts +2 -1
  65. package/src/cmd/cloud/stream/get.ts +2 -1
  66. package/src/cmd/cloud/stream/index.ts +10 -1
  67. package/src/cmd/cloud/stream/list.ts +2 -1
  68. package/src/cmd/cloud/stream/util.ts +39 -12
  69. package/src/cmd/upgrade/index.ts +25 -0
  70. package/src/cmd/upgrade/npm-availability.ts +105 -0
  71. package/src/tui.ts +42 -14
  72. package/src/version-check.ts +19 -3
@@ -1,20 +1,21 @@
1
1
  /**
2
2
  * Vite plugin to fix incorrect public asset paths
3
3
  *
4
- * Developers sometimes accidentally use source paths or relative paths instead
5
- * of the correct absolute root path. This plugin:
4
+ * Developers should use /public/ paths for static assets from src/web/public/.
5
+ * In production, these paths are transformed to CDN URLs.
6
6
  *
7
- * 1. During build: Rewrites all public asset paths to root paths (/)
8
- * 2. During dev: Warns about incorrect paths so developers can fix them
7
+ * This plugin:
8
+ * 1. During build: Rewrites /public/* paths to CDN URLs
9
+ * 2. During dev: Warns only about incorrect source paths (src/web/public/)
9
10
  *
10
- * Patterns handled (all rewritten to root path in production):
11
- * - '/src/web/public/foo.svg' '/foo.svg'
12
- * - './src/web/public/foo.svg' '/foo.svg'
13
- * - 'src/web/public/foo.svg' → '/foo.svg'
14
- * - './public/foo.svg' → '/foo.svg'
15
- * - '/public/foo.svg' → '/foo.svg'
11
+ * Supported patterns (work in dev, rewritten to CDN in production):
12
+ * - '/public/foo.svg' CDN URL (recommended)
13
+ * - './public/foo.svg' CDN URL
16
14
  *
17
- * Vite's base config then rewrites these to CDN URLs (e.g., https://cdn.example.com/deploy/client/foo.svg)
15
+ * Incorrect patterns (warned in dev, rewritten in production):
16
+ * - '/src/web/public/foo.svg' → CDN URL
17
+ * - './src/web/public/foo.svg' → CDN URL
18
+ * - 'src/web/public/foo.svg' → CDN URL
18
19
  */
19
20
 
20
21
  import type { Plugin } from 'vite';
@@ -29,23 +30,41 @@ export interface PublicAssetPathPluginOptions {
29
30
  interface PathPattern {
30
31
  regex: RegExp;
31
32
  description: string;
33
+ /** Replacement template - use {base} for the target base URL */
34
+ replacement: string;
32
35
  }
33
36
 
34
37
  /**
35
- * Create fresh regex instances for each transform call
36
- * (RegExp with global flag maintains state via lastIndex)
38
+ * Patterns that are incorrect - reference source paths directly
37
39
  */
38
- function createPatterns(): PathPattern[] {
40
+ function createIncorrectPatterns(): PathPattern[] {
39
41
  return [
40
42
  // '/src/web/public/...' or './src/web/public/...' or 'src/web/public/...'
41
43
  {
42
44
  regex: /(['"`])(?:\.?\/)?src\/web\/public\//g,
43
45
  description: 'src/web/public/',
46
+ replacement: '$1{base}',
44
47
  },
45
- // './public/...' (relative public path - should be absolute)
48
+ ];
49
+ }
50
+
51
+ /**
52
+ * Patterns that need rewriting for production CDN
53
+ * Both patterns simply replace the prefix while preserving the rest of the path naturally.
54
+ */
55
+ function createPublicPatterns(): PathPattern[] {
56
+ return [
57
+ // './public/...' (relative public path) - replace prefix, keep rest
46
58
  {
47
59
  regex: /(['"`])\.\/public\//g,
48
60
  description: './public/',
61
+ replacement: '$1{base}',
62
+ },
63
+ // '/public/...' (absolute public path) - replace prefix, keep rest
64
+ {
65
+ regex: /(['"`])\/public\//g,
66
+ description: '/public/',
67
+ replacement: '$1{base}',
49
68
  },
50
69
  ];
51
70
  }
@@ -53,21 +72,17 @@ function createPatterns(): PathPattern[] {
53
72
  /**
54
73
  * Vite plugin that fixes public asset paths and rewrites to CDN URLs
55
74
  *
56
- * Rewrites all public asset paths to CDN URLs in production, or root paths
57
- * if no CDN base URL is provided.
75
+ * Rewrites all public asset paths to CDN URLs in production.
58
76
  *
59
77
  * @example
60
78
  * // In vite config:
61
79
  * plugins: [publicAssetPathPlugin({ cdnBaseUrl: 'https://cdn.example.com/deploy/client/' })]
62
80
  *
63
- * // Transforms in production with CDN:
64
- * // '/src/web/public/logo.svg' → 'https://cdn.example.com/deploy/client/logo.svg'
65
- * // './src/web/public/logo.svg' → 'https://cdn.example.com/deploy/client/logo.svg'
66
- * // '/public/logo.svg' → 'https://cdn.example.com/deploy/client/logo.svg'
81
+ * // In code, use /public/ paths:
82
+ * <img src="/public/logo.svg" />
67
83
  *
68
- * // Transforms in production without CDN:
69
- * // '/src/web/public/logo.svg' → '/logo.svg'
70
- * // '/public/logo.svg' → '/logo.svg'
84
+ * // Transforms in production:
85
+ * // '/public/logo.svg' → 'https://cdn.example.com/deploy/client/logo.svg'
71
86
  */
72
87
  export function publicAssetPathPlugin(options: PublicAssetPathPluginOptions = {}): Plugin {
73
88
  const { warnInDev = true, cdnBaseUrl } = options;
@@ -89,17 +104,18 @@ export function publicAssetPathPlugin(options: PublicAssetPathPluginOptions = {}
89
104
  }
90
105
 
91
106
  // Quick check: does the code contain any patterns we care about?
92
- const hasIncorrectPaths = code.includes('src/web/public/') || code.includes('./public/');
93
- const hasPublicPaths = code.includes('/public/');
107
+ const hasIncorrectSourcePaths = code.includes('src/web/public/');
108
+ const hasPublicPaths = code.includes('/public/') || code.includes('./public/');
94
109
 
95
- if (!hasIncorrectPaths && !hasPublicPaths) {
110
+ if (!hasIncorrectSourcePaths && !hasPublicPaths) {
96
111
  return null;
97
112
  }
98
113
 
99
- // In dev mode, optionally warn about incorrect paths but don't transform
114
+ // In dev mode, only warn about incorrect source paths (src/web/public/)
115
+ // /public/ and ./public/ paths work correctly in dev mode
100
116
  if (isDev) {
101
- if (warnInDev && hasIncorrectPaths) {
102
- const patterns = createPatterns();
117
+ if (warnInDev && hasIncorrectSourcePaths) {
118
+ const patterns = createIncorrectPatterns();
103
119
  const foundPatterns: string[] = [];
104
120
 
105
121
  for (const { regex, description } of patterns) {
@@ -121,37 +137,43 @@ export function publicAssetPathPlugin(options: PublicAssetPathPluginOptions = {}
121
137
  this.warn(
122
138
  `Found incorrect asset path(s) in ${id}:\n` +
123
139
  newWarnings.map((p) => ` - '${p}' should be '/public/'`).join('\n') +
124
- `\nUse absolute '/public/...' paths for production compatibility.`
140
+ `\nUse '/public/...' paths for static assets.`
125
141
  );
126
142
  }
127
143
  }
128
144
  }
129
145
  // In dev mode, never transform - Vite serves from source paths
146
+ // and the Bun server proxies /public/* to Vite
130
147
  return null;
131
148
  }
132
149
 
133
- // Build mode: transform paths
134
- let transformed = code;
135
-
136
- // Determine target URL: CDN base if provided, otherwise root
137
- const targetBase = cdnBaseUrl ? (cdnBaseUrl.endsWith('/') ? cdnBaseUrl : `${cdnBaseUrl}/`) : '/';
150
+ // Build mode: transform paths to CDN URLs
151
+ let transformed = code;
152
+
153
+ // Determine target URL: CDN base if provided, otherwise root
154
+ const targetBase = cdnBaseUrl
155
+ ? cdnBaseUrl.endsWith('/')
156
+ ? cdnBaseUrl
157
+ : `${cdnBaseUrl}/`
158
+ : '/';
159
+
160
+ // Transform incorrect source paths (src/web/public/) → CDN
161
+ if (hasIncorrectSourcePaths) {
162
+ const patterns = createIncorrectPatterns();
163
+ for (const { regex, replacement } of patterns) {
164
+ const replaceRegex = new RegExp(regex.source, regex.flags);
165
+ transformed = transformed.replace(replaceRegex, replacement.replace('{base}', targetBase));
166
+ }
167
+ }
138
168
 
139
- // First, fix incorrect source paths (src/web/public/, ./public/) targetBase
140
- if (hasIncorrectPaths) {
141
- const patterns = createPatterns();
142
- for (const { regex } of patterns) {
143
- const replaceRegex = new RegExp(regex.source, regex.flags);
144
- transformed = transformed.replace(replaceRegex, `$1${targetBase}`);
169
+ // Transform public paths → CDN
170
+ if (hasPublicPaths) {
171
+ const publicPatterns = createPublicPatterns();
172
+ for (const { regex, replacement } of publicPatterns) {
173
+ const replaceRegex = new RegExp(regex.source, regex.flags);
174
+ transformed = transformed.replace(replaceRegex, replacement.replace('{base}', targetBase));
175
+ }
145
176
  }
146
- }
147
-
148
- // Then, rewrite /public/foo → {targetBase}foo
149
- if (hasPublicPaths) {
150
- // Match '/public/...' paths in strings (single, double, or backtick quotes)
151
- // Captures: $1 = quote char, $2 = path after /public/
152
- const publicPathRegex = /(['"`])\/public\/([^'"`\s]+)/g;
153
- transformed = transformed.replace(publicPathRegex, `$1${targetBase}$2`);
154
- }
155
177
 
156
178
  // Return transformed code if changed
157
179
  if (transformed !== code) {
@@ -68,7 +68,9 @@ export async function generateAssetServerConfig(
68
68
  root: rootDir,
69
69
  base: '/',
70
70
  clearScreen: false,
71
- publicDir: false, // Don't serve public dir - Bun server handles that
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,
@@ -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, project: 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, project: 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, project: 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 { ErrorCode } from '../../../errors';
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
- project: { region: string };
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
- if (!sdkKey) {
16
- tui.fatal(
17
- `Couldn't find the AGENTUITY_SDK_KEY in ${ctx.projectDir} .env file`,
18
- ErrorCode.CONFIG_NOT_FOUND
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 ${sdkKey}`,
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);
@@ -185,6 +185,31 @@ export const command = createCommand({
185
185
  };
186
186
  }
187
187
 
188
+ // Verify the version is available on npm before proceeding
189
+ const isAvailable = await tui.spinner({
190
+ message: 'Verifying npm availability...',
191
+ clearOnSuccess: true,
192
+ callback: async () => {
193
+ const { waitForNpmAvailability } = await import('./npm-availability');
194
+ return await waitForNpmAvailability(latestVersion, {
195
+ maxAttempts: 6,
196
+ initialDelayMs: 2000,
197
+ });
198
+ },
199
+ });
200
+
201
+ if (!isAvailable) {
202
+ tui.warning('The new version is not yet available on npm.');
203
+ tui.info('This can happen right after a release. Please try again in a few minutes.');
204
+ tui.info(`You can also run: ${tui.muted('bun add -g @agentuity/cli@latest')}`);
205
+ return {
206
+ upgraded: false,
207
+ from: currentVersion,
208
+ to: latestVersion,
209
+ message: 'Version not yet available on npm',
210
+ };
211
+ }
212
+
188
213
  // Show version info
189
214
  if (!force) {
190
215
  tui.info(`Current version: ${tui.muted(normalizedCurrent)}`);