@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.
Files changed (93) 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 +163 -18
  10. package/dist/cmd/build/entry-generator.js.map +1 -1
  11. package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
  12. package/dist/cmd/build/vite/metadata-generator.js +19 -9
  13. package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
  14. package/dist/cmd/build/vite/public-asset-path-plugin.d.ts +24 -15
  15. package/dist/cmd/build/vite/public-asset-path-plugin.d.ts.map +1 -1
  16. package/dist/cmd/build/vite/public-asset-path-plugin.js +92 -47
  17. package/dist/cmd/build/vite/public-asset-path-plugin.js.map +1 -1
  18. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
  19. package/dist/cmd/build/vite/vite-asset-server-config.js +9 -6
  20. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
  21. package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
  22. package/dist/cmd/build/vite/vite-asset-server.js +1 -1
  23. package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
  24. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  25. package/dist/cmd/build/vite/vite-builder.js +12 -11
  26. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  27. package/dist/cmd/cloud/env/org-util.d.ts +2 -1
  28. package/dist/cmd/cloud/env/org-util.d.ts.map +1 -1
  29. package/dist/cmd/cloud/env/org-util.js +4 -2
  30. package/dist/cmd/cloud/env/org-util.js.map +1 -1
  31. package/dist/cmd/cloud/stream/create.d.ts +3 -0
  32. package/dist/cmd/cloud/stream/create.d.ts.map +1 -0
  33. package/dist/cmd/cloud/stream/create.js +227 -0
  34. package/dist/cmd/cloud/stream/create.js.map +1 -0
  35. package/dist/cmd/cloud/stream/delete.d.ts.map +1 -1
  36. package/dist/cmd/cloud/stream/delete.js +2 -1
  37. package/dist/cmd/cloud/stream/delete.js.map +1 -1
  38. package/dist/cmd/cloud/stream/get.d.ts.map +1 -1
  39. package/dist/cmd/cloud/stream/get.js +2 -1
  40. package/dist/cmd/cloud/stream/get.js.map +1 -1
  41. package/dist/cmd/cloud/stream/index.d.ts.map +1 -1
  42. package/dist/cmd/cloud/stream/index.js +10 -1
  43. package/dist/cmd/cloud/stream/index.js.map +1 -1
  44. package/dist/cmd/cloud/stream/list.d.ts.map +1 -1
  45. package/dist/cmd/cloud/stream/list.js +2 -1
  46. package/dist/cmd/cloud/stream/list.js.map +1 -1
  47. package/dist/cmd/cloud/stream/util.d.ts +6 -5
  48. package/dist/cmd/cloud/stream/util.d.ts.map +1 -1
  49. package/dist/cmd/cloud/stream/util.js +26 -5
  50. package/dist/cmd/cloud/stream/util.js.map +1 -1
  51. package/dist/cmd/support/report.d.ts.map +1 -1
  52. package/dist/cmd/support/report.js +58 -23
  53. package/dist/cmd/support/report.js.map +1 -1
  54. package/dist/cmd/upgrade/index.d.ts.map +1 -1
  55. package/dist/cmd/upgrade/index.js +23 -0
  56. package/dist/cmd/upgrade/index.js.map +1 -1
  57. package/dist/cmd/upgrade/npm-availability.d.ts +44 -0
  58. package/dist/cmd/upgrade/npm-availability.d.ts.map +1 -0
  59. package/dist/cmd/upgrade/npm-availability.js +73 -0
  60. package/dist/cmd/upgrade/npm-availability.js.map +1 -0
  61. package/dist/internal-logger.d.ts +7 -0
  62. package/dist/internal-logger.d.ts.map +1 -1
  63. package/dist/internal-logger.js +82 -28
  64. package/dist/internal-logger.js.map +1 -1
  65. package/dist/tui.d.ts +9 -1
  66. package/dist/tui.d.ts.map +1 -1
  67. package/dist/tui.js +39 -14
  68. package/dist/tui.js.map +1 -1
  69. package/dist/version-check.d.ts.map +1 -1
  70. package/dist/version-check.js +13 -2
  71. package/dist/version-check.js.map +1 -1
  72. package/package.json +8 -7
  73. package/src/auth.ts +9 -5
  74. package/src/cli.ts +44 -12
  75. package/src/cmd/build/entry-generator.ts +163 -18
  76. package/src/cmd/build/vite/metadata-generator.ts +20 -9
  77. package/src/cmd/build/vite/public-asset-path-plugin.ts +105 -53
  78. package/src/cmd/build/vite/vite-asset-server-config.ts +9 -6
  79. package/src/cmd/build/vite/vite-asset-server.ts +3 -1
  80. package/src/cmd/build/vite/vite-builder.ts +21 -20
  81. package/src/cmd/cloud/env/org-util.ts +5 -2
  82. package/src/cmd/cloud/stream/create.ts +248 -0
  83. package/src/cmd/cloud/stream/delete.ts +2 -1
  84. package/src/cmd/cloud/stream/get.ts +2 -1
  85. package/src/cmd/cloud/stream/index.ts +10 -1
  86. package/src/cmd/cloud/stream/list.ts +2 -1
  87. package/src/cmd/cloud/stream/util.ts +39 -12
  88. package/src/cmd/support/report.ts +82 -28
  89. package/src/cmd/upgrade/index.ts +25 -0
  90. package/src/cmd/upgrade/npm-availability.ts +105 -0
  91. package/src/internal-logger.ts +91 -27
  92. package/src/tui.ts +42 -14
  93. 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
- 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,
@@ -99,12 +101,13 @@ export async function generateAssetServerConfig(
99
101
  credentials: true,
100
102
  },
101
103
 
102
- // HMR configuration - client must connect to Vite asset server directly
103
- // Do NOT set port/clientPort - let Vite use the actual server port it binds to
104
- // (important when strictPort: false and Vite falls back to an alternate port)
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
- protocol: 'ws',
107
- host: '127.0.0.1',
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(`HMR WebSocket configured to connect to ws://127.0.0.1:${actualPort}`);
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 (e.g., '/src/web/public/...' '/public/...')
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
- build: {
207
- outDir: clientOutDir,
208
- rollupOptions: {
209
- input: htmlPath,
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, 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);