@agentuity/cli 1.0.9 → 1.0.11

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 (138) hide show
  1. package/dist/cmd/build/ast.d.ts +1 -1
  2. package/dist/cmd/build/ast.d.ts.map +1 -1
  3. package/dist/cmd/build/ast.js +103 -5
  4. package/dist/cmd/build/ast.js.map +1 -1
  5. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  6. package/dist/cmd/build/entry-generator.js +1 -8
  7. package/dist/cmd/build/entry-generator.js.map +1 -1
  8. package/dist/cmd/build/vite/config-loader.d.ts +9 -0
  9. package/dist/cmd/build/vite/config-loader.d.ts.map +1 -1
  10. package/dist/cmd/build/vite/config-loader.js +30 -0
  11. package/dist/cmd/build/vite/config-loader.js.map +1 -1
  12. package/dist/cmd/build/vite/index.d.ts +2 -0
  13. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  14. package/dist/cmd/build/vite/index.js +2 -1
  15. package/dist/cmd/build/vite/index.js.map +1 -1
  16. package/dist/cmd/build/vite/metadata-generator.d.ts +4 -1
  17. package/dist/cmd/build/vite/metadata-generator.d.ts.map +1 -1
  18. package/dist/cmd/build/vite/metadata-generator.js +1 -0
  19. package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
  20. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  21. package/dist/cmd/build/vite/route-discovery.js +23 -1
  22. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  23. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
  24. package/dist/cmd/build/vite/vite-asset-server-config.js +19 -14
  25. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
  26. package/dist/cmd/build/vite/vite-builder.d.ts +2 -0
  27. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  28. package/dist/cmd/build/vite/vite-builder.js +13 -8
  29. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  30. package/dist/cmd/build/vite-bundler.d.ts +2 -0
  31. package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
  32. package/dist/cmd/build/vite-bundler.js +2 -1
  33. package/dist/cmd/build/vite-bundler.js.map +1 -1
  34. package/dist/cmd/cloud/db/list.d.ts.map +1 -1
  35. package/dist/cmd/cloud/db/list.js +14 -1
  36. package/dist/cmd/cloud/db/list.js.map +1 -1
  37. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  38. package/dist/cmd/cloud/deploy.js +36 -22
  39. package/dist/cmd/cloud/deploy.js.map +1 -1
  40. package/dist/cmd/cloud/queue/list.d.ts.map +1 -1
  41. package/dist/cmd/cloud/queue/list.js +10 -0
  42. package/dist/cmd/cloud/queue/list.js.map +1 -1
  43. package/dist/cmd/cloud/sandbox/list.d.ts.map +1 -1
  44. package/dist/cmd/cloud/sandbox/list.js +10 -0
  45. package/dist/cmd/cloud/sandbox/list.js.map +1 -1
  46. package/dist/cmd/cloud/sandbox/runtime/list.d.ts.map +1 -1
  47. package/dist/cmd/cloud/sandbox/runtime/list.js +10 -0
  48. package/dist/cmd/cloud/sandbox/runtime/list.js.map +1 -1
  49. package/dist/cmd/cloud/sandbox/snapshot/list.d.ts.map +1 -1
  50. package/dist/cmd/cloud/sandbox/snapshot/list.js +10 -0
  51. package/dist/cmd/cloud/sandbox/snapshot/list.js.map +1 -1
  52. package/dist/cmd/cloud/session/get.js +12 -12
  53. package/dist/cmd/cloud/session/get.js.map +1 -1
  54. package/dist/cmd/cloud/session/list.d.ts.map +1 -1
  55. package/dist/cmd/cloud/session/list.js +14 -4
  56. package/dist/cmd/cloud/session/list.js.map +1 -1
  57. package/dist/cmd/cloud/storage/list.d.ts.map +1 -1
  58. package/dist/cmd/cloud/storage/list.js +14 -1
  59. package/dist/cmd/cloud/storage/list.js.map +1 -1
  60. package/dist/cmd/cloud/stream/list.d.ts.map +1 -1
  61. package/dist/cmd/cloud/stream/list.js +10 -0
  62. package/dist/cmd/cloud/stream/list.js.map +1 -1
  63. package/dist/cmd/cloud/thread/list.d.ts.map +1 -1
  64. package/dist/cmd/cloud/thread/list.js +10 -0
  65. package/dist/cmd/cloud/thread/list.js.map +1 -1
  66. package/dist/cmd/dev/download.d.ts.map +1 -1
  67. package/dist/cmd/dev/download.js +63 -53
  68. package/dist/cmd/dev/download.js.map +1 -1
  69. package/dist/cmd/project/domain/check.d.ts +2 -0
  70. package/dist/cmd/project/domain/check.d.ts.map +1 -0
  71. package/dist/cmd/project/domain/check.js +131 -0
  72. package/dist/cmd/project/domain/check.js.map +1 -0
  73. package/dist/cmd/project/domain/index.d.ts +2 -0
  74. package/dist/cmd/project/domain/index.d.ts.map +1 -0
  75. package/dist/cmd/project/domain/index.js +20 -0
  76. package/dist/cmd/project/domain/index.js.map +1 -0
  77. package/dist/cmd/project/hostname/get.d.ts +2 -0
  78. package/dist/cmd/project/hostname/get.d.ts.map +1 -0
  79. package/dist/cmd/project/hostname/get.js +50 -0
  80. package/dist/cmd/project/hostname/get.js.map +1 -0
  81. package/dist/cmd/project/hostname/index.d.ts +2 -0
  82. package/dist/cmd/project/hostname/index.d.ts.map +1 -0
  83. package/dist/cmd/project/hostname/index.js +18 -0
  84. package/dist/cmd/project/hostname/index.js.map +1 -0
  85. package/dist/cmd/project/hostname/set.d.ts +2 -0
  86. package/dist/cmd/project/hostname/set.d.ts.map +1 -0
  87. package/dist/cmd/project/hostname/set.js +100 -0
  88. package/dist/cmd/project/hostname/set.js.map +1 -0
  89. package/dist/cmd/project/index.d.ts.map +1 -1
  90. package/dist/cmd/project/index.js +12 -0
  91. package/dist/cmd/project/index.js.map +1 -1
  92. package/dist/index.d.ts +1 -1
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +1 -1
  95. package/dist/index.js.map +1 -1
  96. package/dist/steps.d.ts +9 -0
  97. package/dist/steps.d.ts.map +1 -1
  98. package/dist/steps.js +131 -71
  99. package/dist/steps.js.map +1 -1
  100. package/dist/tui.d.ts +2 -2
  101. package/dist/tui.d.ts.map +1 -1
  102. package/dist/tui.js +6 -4
  103. package/dist/tui.js.map +1 -1
  104. package/dist/types.d.ts +4 -2
  105. package/dist/types.d.ts.map +1 -1
  106. package/dist/types.js.map +1 -1
  107. package/package.json +6 -7
  108. package/src/cmd/build/ast.ts +141 -5
  109. package/src/cmd/build/entry-generator.ts +1 -8
  110. package/src/cmd/build/vite/config-loader.ts +35 -0
  111. package/src/cmd/build/vite/index.ts +4 -0
  112. package/src/cmd/build/vite/metadata-generator.ts +5 -1
  113. package/src/cmd/build/vite/route-discovery.ts +34 -1
  114. package/src/cmd/build/vite/vite-asset-server-config.ts +22 -13
  115. package/src/cmd/build/vite/vite-builder.ts +20 -8
  116. package/src/cmd/build/vite-bundler.ts +4 -0
  117. package/src/cmd/cloud/db/list.ts +14 -1
  118. package/src/cmd/cloud/deploy.ts +46 -21
  119. package/src/cmd/cloud/queue/list.ts +10 -0
  120. package/src/cmd/cloud/sandbox/list.ts +10 -0
  121. package/src/cmd/cloud/sandbox/runtime/list.ts +10 -0
  122. package/src/cmd/cloud/sandbox/snapshot/list.ts +10 -0
  123. package/src/cmd/cloud/session/get.ts +12 -12
  124. package/src/cmd/cloud/session/list.ts +28 -18
  125. package/src/cmd/cloud/storage/list.ts +14 -1
  126. package/src/cmd/cloud/stream/list.ts +18 -8
  127. package/src/cmd/cloud/thread/list.ts +15 -5
  128. package/src/cmd/dev/download.ts +76 -78
  129. package/src/cmd/project/domain/check.ts +146 -0
  130. package/src/cmd/project/domain/index.ts +20 -0
  131. package/src/cmd/project/hostname/get.ts +54 -0
  132. package/src/cmd/project/hostname/index.ts +18 -0
  133. package/src/cmd/project/hostname/set.ts +123 -0
  134. package/src/cmd/project/index.ts +12 -0
  135. package/src/index.ts +1 -1
  136. package/src/steps.ts +139 -74
  137. package/src/tui.ts +6 -4
  138. package/src/types.ts +4 -2
@@ -71,3 +71,38 @@ export function getWorkbenchConfig(
71
71
  headers: workbench.headers ?? {},
72
72
  };
73
73
  }
74
+
75
+ /**
76
+ * Known Vite framework plugin name prefixes.
77
+ * Each framework's Vite plugin registers one or more plugins whose names
78
+ * start with these prefixes. We match against these to detect whether the
79
+ * user has already configured a framework plugin in their agentuity.config.ts.
80
+ */
81
+ const FRAMEWORK_PLUGIN_PREFIXES = [
82
+ 'vite:react', // @vitejs/plugin-react (vite:react-babel, vite:react-refresh, …)
83
+ 'vite:preact', // @preact/preset-vite
84
+ 'vite-plugin-svelte', // @sveltejs/vite-plugin-svelte
85
+ 'vite:vue', // @vitejs/plugin-vue (vite:vue, vite:vue-jsx)
86
+ 'vite-plugin-solid', // vite-plugin-solid
87
+ 'solid', // vite-plugin-solid also uses plain "solid"
88
+ ];
89
+
90
+ /**
91
+ * Check if the user's plugins include any known UI-framework Vite plugin
92
+ * (React, Svelte, Vue, Solid, Preact, …).
93
+ *
94
+ * Detection is name-based: Vite plugins expose a `name` property and every
95
+ * major framework plugin uses a predictable prefix. This avoids dynamically
96
+ * importing every possible framework just to compare names.
97
+ */
98
+ export function hasFrameworkPlugin(userPlugins: import('vite').PluginOption[]): boolean {
99
+ const flat = (userPlugins as unknown[]).flat(Infinity).filter(Boolean);
100
+ return flat.some(
101
+ (p: unknown) =>
102
+ p &&
103
+ typeof p === 'object' &&
104
+ 'name' in p &&
105
+ typeof (p as { name: unknown }).name === 'string' &&
106
+ FRAMEWORK_PLUGIN_PREFIXES.some((prefix) => (p as { name: string }).name.startsWith(prefix))
107
+ );
108
+ }
@@ -23,6 +23,8 @@ export interface AgentuityPluginOptions {
23
23
  deploymentId?: string;
24
24
  logLevel?: LogLevel;
25
25
  deploymentOptions?: DeployOptions;
26
+ /** Deployment config from agentuity.json (resources, mode, dependencies, domains) */
27
+ deploymentConfig?: Record<string, unknown>;
26
28
  /** Optional config profile name (e.g., 'staging', 'test') for .env.{profile} files */
27
29
  profile?: string;
28
30
  }
@@ -48,6 +50,7 @@ export function agentuityPlugin(options: AgentuityPluginOptions): Plugin {
48
50
  deploymentId = '',
49
51
  logLevel = 'info',
50
52
  deploymentOptions,
53
+ deploymentConfig,
51
54
  profile,
52
55
  } = options;
53
56
  const logger = createLogger(logLevel);
@@ -173,6 +176,7 @@ export function agentuityPlugin(options: AgentuityPluginOptions): Plugin {
173
176
  dev,
174
177
  logger,
175
178
  deploymentOptions,
179
+ deploymentConfig,
176
180
  });
177
181
 
178
182
  // Write metadata file
@@ -6,7 +6,8 @@
6
6
 
7
7
  import { join } from 'node:path';
8
8
  import { writeFileSync, mkdirSync, existsSync, readFileSync, statSync, readdirSync } from 'node:fs';
9
- import type { BuildMetadata } from '@agentuity/server';
9
+ import { type BuildMetadata, DeploymentConfig } from '@agentuity/server';
10
+ import type { z } from 'zod';
10
11
  import type { AgentMetadata } from './agent-discovery';
11
12
  import type { RouteMetadata } from './route-discovery';
12
13
  import type { Logger, DeployOptions } from '../../../types';
@@ -164,6 +165,8 @@ export interface MetadataGeneratorOptions {
164
165
  dev?: boolean;
165
166
  logger: Logger;
166
167
  deploymentOptions?: DeployOptions;
168
+ /** Deployment config from agentuity.json (resources, mode, dependencies, domains) */
169
+ deploymentConfig?: z.infer<typeof DeploymentConfig>;
167
170
  }
168
171
 
169
172
  /**
@@ -473,6 +476,7 @@ export async function generateMetadata(options: MetadataGeneratorOptions): Promi
473
476
  orgId,
474
477
  },
475
478
  deployment: {
479
+ ...options.deploymentConfig, // deployment config from agentuity.json (resources, mode, dependencies, domains)
476
480
  id: options.deploymentId || '',
477
481
  date: new Date().toISOString(),
478
482
  build: {
@@ -85,6 +85,10 @@ export async function discoverRoutes(
85
85
 
86
86
  const transpiler = new Bun.Transpiler({ loader: 'ts', target: 'bun' });
87
87
 
88
+ // Track files that are mounted as sub-routers via .route()
89
+ // These files will be parsed standalone AND via .route() — we need to deduplicate
90
+ const mountedSubrouters = new Set<string>();
91
+
88
92
  // Scan all .ts files in api directory
89
93
  const glob = new Bun.Glob('**/*.ts');
90
94
  for await (const file of glob.scan(apiDir)) {
@@ -104,7 +108,14 @@ export async function discoverRoutes(
104
108
  const relativeFilename = './' + relative(srcDir, filePath);
105
109
 
106
110
  try {
107
- const parsedRoutes = await parseRoute(rootDir, filePath, projectId, deploymentId);
111
+ const parsedRoutes = await parseRoute(
112
+ rootDir,
113
+ filePath,
114
+ projectId,
115
+ deploymentId,
116
+ undefined,
117
+ mountedSubrouters
118
+ );
108
119
 
109
120
  if (parsedRoutes.length > 0) {
110
121
  logger.trace('Discovered %d route(s) in %s', parsedRoutes.length, relativeFilename);
@@ -165,6 +176,28 @@ export async function discoverRoutes(
165
176
  }
166
177
  }
167
178
 
179
+ // Filter out routes from standalone-parsed sub-router files
180
+ // When a file is mounted via .route(), its standalone routes have wrong prefixes
181
+ // Only the .route()-prefixed routes (attached to the parent file) are correct
182
+ if (mountedSubrouters.size > 0) {
183
+ const rootDir = join(srcDir, '..');
184
+ const subrouterRelPaths = new Set<string>();
185
+ for (const absPath of mountedSubrouters) {
186
+ subrouterRelPaths.add(relative(rootDir, absPath));
187
+ }
188
+
189
+ // Remove routes whose filename matches a sub-router file
190
+ // (these are the incorrectly-prefixed standalone routes)
191
+ const filteredRoutes = routes.filter((r) => !subrouterRelPaths.has(r.filename));
192
+ const filteredRouteInfoList = routeInfoList.filter((r) => !subrouterRelPaths.has(r.filename));
193
+
194
+ // Replace arrays in-place
195
+ routes.length = 0;
196
+ routes.push(...filteredRoutes);
197
+ routeInfoList.length = 0;
198
+ routeInfoList.push(...filteredRouteInfoList);
199
+ }
200
+
168
201
  logger.debug('Discovered %d route(s)', routes.length);
169
202
 
170
203
  // Check for route conflicts
@@ -128,24 +128,33 @@ export async function generateAssetServerConfig(
128
128
  'process.env.NODE_ENV': JSON.stringify('development'),
129
129
  },
130
130
 
131
- // Plugins: User plugins first (e.g., Tailwind), then React and browser env
131
+ // Plugins: User plugins first (includes framework plugin like React/Svelte/Vue), then browser env
132
132
  // Try project's node_modules first, fall back to CLI's bundled version
133
133
  plugins: await (async () => {
134
- const projectRequire = createRequire(join(rootDir, 'package.json'));
135
- let reactPluginPath = '@vitejs/plugin-react';
136
- try {
137
- reactPluginPath = projectRequire.resolve('@vitejs/plugin-react');
138
- } catch {
139
- // Project doesn't have @vitejs/plugin-react, use CLI's bundled version
140
- }
141
- const reactPlugin = (await import(reactPluginPath)).default();
142
134
  const { browserEnvPlugin } = await import('./browser-env-plugin');
143
135
  const { publicAssetPathPlugin } = await import('./public-asset-path-plugin');
136
+ const { hasFrameworkPlugin } = await import('./config-loader');
137
+
138
+ // Auto-add React plugin if no framework plugin is present (backwards compatibility)
139
+ const resolvedUserPlugins = [...userPlugins];
140
+ if (resolvedUserPlugins.length === 0 || !hasFrameworkPlugin(resolvedUserPlugins)) {
141
+ logger.debug(
142
+ 'No framework plugin found in agentuity.config.ts plugins, adding React automatically for dev server'
143
+ );
144
+ const projectRequire = createRequire(join(rootDir, 'package.json'));
145
+ let reactPluginPath = '@vitejs/plugin-react';
146
+ try {
147
+ reactPluginPath = projectRequire.resolve('@vitejs/plugin-react');
148
+ } catch {
149
+ // Project doesn't have @vitejs/plugin-react, use CLI's bundled version
150
+ }
151
+ const reactModule = await import(reactPluginPath);
152
+ resolvedUserPlugins.unshift(reactModule.default());
153
+ }
154
+
144
155
  return [
145
- // User-defined plugins from agentuity.config.ts (e.g., Tailwind CSS)
146
- ...userPlugins,
147
- // React plugin for JSX/TSX transformation and Fast Refresh
148
- reactPlugin,
156
+ // User-defined plugins from agentuity.config.ts (framework plugin + extras)
157
+ ...resolvedUserPlugins,
149
158
  // Browser env plugin to map process.env to import.meta.env
150
159
  browserEnvPlugin(),
151
160
  // Warn about incorrect public asset paths in dev mode
@@ -61,6 +61,8 @@ export interface ViteBuildOptions {
61
61
  analyticsEnabled?: boolean;
62
62
  logger: Logger;
63
63
  deploymentOptions?: DeployOptions;
64
+ /** Deployment config from agentuity.json (resources, mode, dependencies, domains) */
65
+ deploymentConfig?: Record<string, unknown>;
64
66
  /** Optional collector for structured error reporting */
65
67
  collector?: BuildReportCollector;
66
68
  /** Optional config profile name (e.g., 'staging', 'test') for .env.{profile} files */
@@ -180,8 +182,24 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
180
182
 
181
183
  // Load custom user plugins from agentuity.config.ts if it exists
182
184
  const clientOutDir = join(rootDir, '.agentuity/client');
185
+ const { loadAgentuityConfig, hasFrameworkPlugin } = await import('./config-loader');
186
+ const userConfig = await loadAgentuityConfig(rootDir, logger);
187
+ const userPlugins = userConfig?.plugins || [];
188
+
189
+ // Auto-add React plugin if no framework plugin is present (backwards compatibility)
190
+ if (userPlugins.length === 0 || !hasFrameworkPlugin(userPlugins)) {
191
+ logger.debug(
192
+ 'No framework plugin found in agentuity.config.ts plugins, adding React automatically'
193
+ );
194
+ userPlugins.unshift(react());
195
+ }
196
+
197
+ if (userPlugins.length > 0) {
198
+ logger.debug('Loaded %d custom plugin(s) from agentuity.config.ts', userPlugins.length);
199
+ }
200
+
183
201
  const plugins = [
184
- react(),
202
+ ...userPlugins,
185
203
  browserEnvPlugin(),
186
204
  // Fix incorrect public asset paths and rewrite to CDN URLs
187
205
  publicAssetPathPlugin({ cdnBaseUrl }),
@@ -189,13 +207,6 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
189
207
  // Emit analytics beacon as hashed CDN asset (prod builds only)
190
208
  beaconPlugin({ enabled: analyticsEnabled && !dev }),
191
209
  ];
192
- const { loadAgentuityConfig } = await import('./config-loader');
193
- const userConfig = await loadAgentuityConfig(rootDir, logger);
194
- const userPlugins = userConfig?.plugins || [];
195
- plugins.push(...userPlugins);
196
- if (userPlugins.length > 0) {
197
- logger.debug('Loaded %d custom plugin(s) from agentuity.config.ts', userPlugins.length);
198
- }
199
210
 
200
211
  // Merge custom define values from user config
201
212
  const userDefine = userConfig?.define || {};
@@ -412,6 +423,7 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
412
423
  logger,
413
424
  dev,
414
425
  deploymentOptions: options.deploymentOptions,
426
+ deploymentConfig: options.deploymentConfig,
415
427
  });
416
428
 
417
429
  writeMetadataFile(rootDir, metadata, dev, logger);
@@ -27,6 +27,8 @@ export interface ViteBundleOptions {
27
27
  port?: number;
28
28
  logger: Logger;
29
29
  deploymentOptions?: DeployOptions;
30
+ /** Deployment config from agentuity.json (resources, mode, dependencies, domains) */
31
+ deploymentConfig?: Record<string, unknown>;
30
32
  /** Optional collector for structured error reporting */
31
33
  collector?: BuildReportCollector;
32
34
  }
@@ -44,6 +46,7 @@ export async function viteBundle(options: ViteBundleOptions): Promise<{ output:
44
46
  port = 3500,
45
47
  logger,
46
48
  deploymentOptions,
49
+ deploymentConfig,
47
50
  collector,
48
51
  } = options;
49
52
 
@@ -100,6 +103,7 @@ export async function viteBundle(options: ViteBundleOptions): Promise<{ output:
100
103
  deploymentId,
101
104
  logger,
102
105
  deploymentOptions,
106
+ deploymentConfig,
103
107
  collector,
104
108
  });
105
109
 
@@ -46,6 +46,14 @@ export const listSubcommand = createSubcommand({
46
46
  'Show credentials in plain text (default: masked in terminal, unmasked in JSON)'
47
47
  ),
48
48
  nameOnly: z.boolean().optional().describe('Print the name only'),
49
+ sort: z
50
+ .enum(['name', 'created'])
51
+ .optional()
52
+ .describe('field to sort by (default: created)'),
53
+ direction: z
54
+ .enum(['asc', 'desc'])
55
+ .optional()
56
+ .describe('sort direction (default: desc)'),
49
57
  }),
50
58
  response: DBListResponseSchema,
51
59
  },
@@ -60,7 +68,12 @@ export const listSubcommand = createSubcommand({
60
68
  message: 'Fetching databases',
61
69
  clearOnSuccess: true,
62
70
  callback: async () => {
63
- return listOrgResources(catalystClient, { type: 'db', orgId: opts?.orgId });
71
+ return listOrgResources(catalystClient, {
72
+ type: 'db',
73
+ orgId: opts?.orgId,
74
+ sort: opts?.sort,
75
+ direction: opts?.direction,
76
+ });
64
77
  },
65
78
  });
66
79
 
@@ -22,6 +22,7 @@ import {
22
22
  stepSkipped,
23
23
  stepError,
24
24
  pauseStepUI,
25
+ StepInterruptError,
25
26
  type Step,
26
27
  type StepContext,
27
28
  } from '../../steps';
@@ -428,6 +429,13 @@ export const deploySubcommand = createSubcommand({
428
429
  }
429
430
  }
430
431
 
432
+ // Create a unified abort controller for the entire deploy flow
433
+ const deployAbortController = new AbortController();
434
+ const deployAbortHandler = () => {
435
+ deployAbortController.abort();
436
+ };
437
+ process.on('SIGINT', deployAbortHandler);
438
+
431
439
  // Start malware check async (runs in parallel with build)
432
440
  if (deployment) {
433
441
  malwareCheckPromise = (async () => {
@@ -444,7 +452,8 @@ export const deploySubcommand = createSubcommand({
444
452
  const result = await projectDeploymentMalwareCheck(
445
453
  catalystClient,
446
454
  deployment!.id,
447
- packages
455
+ packages,
456
+ deployAbortController.signal
448
457
  );
449
458
  logger.debug(
450
459
  'Malware check complete: action=%s, flagged=%d',
@@ -599,7 +608,7 @@ export const deploySubcommand = createSubcommand({
599
608
 
600
609
  {
601
610
  label: 'Build, Verify and Package',
602
- run: async () => {
611
+ run: async (stepCtx: StepContext) => {
603
612
  if (!deployment) {
604
613
  return stepError('deployment was null');
605
614
  }
@@ -636,6 +645,7 @@ export const deploySubcommand = createSubcommand({
636
645
  region: project.region,
637
646
  logger: ctx.logger,
638
647
  deploymentOptions: opts,
648
+ deploymentConfig: project.deployment,
639
649
  collector,
640
650
  });
641
651
  capturedOutput = [...capturedOutput, ...bundleResult.output];
@@ -643,7 +653,8 @@ export const deploySubcommand = createSubcommand({
643
653
  instructions = await projectDeploymentUpdate(
644
654
  apiClient,
645
655
  deployment.id,
646
- build
656
+ build,
657
+ stepCtx.signal
647
658
  );
648
659
  return stepSuccess(capturedOutput.length > 0 ? capturedOutput : undefined);
649
660
  } catch (ex) {
@@ -789,6 +800,7 @@ export const deploySubcommand = createSubcommand({
789
800
  'Content-Type': 'application/zip',
790
801
  },
791
802
  body: zipfile,
803
+ signal: stepCtx.signal,
792
804
  });
793
805
  ctx.logger.trace(`Upload response: ${resp.status}`);
794
806
  if (!resp.ok) {
@@ -876,6 +888,7 @@ export const deploySubcommand = createSubcommand({
876
888
  duplex: 'half',
877
889
  headers,
878
890
  body,
891
+ signal: stepCtx.signal,
879
892
  })
880
893
  );
881
894
  }
@@ -910,11 +923,15 @@ export const deploySubcommand = createSubcommand({
910
923
  },
911
924
  {
912
925
  label: 'Provision Deployment',
913
- run: async () => {
926
+ run: async (stepCtx: StepContext) => {
914
927
  if (!deployment) {
915
928
  return stepError('deployment was null');
916
929
  }
917
- complete = await projectDeploymentComplete(apiClient, deployment.id);
930
+ complete = await projectDeploymentComplete(
931
+ apiClient,
932
+ deployment.id,
933
+ stepCtx.signal
934
+ );
918
935
  return stepSuccess();
919
936
  },
920
937
  },
@@ -945,12 +962,7 @@ export const deploySubcommand = createSubcommand({
945
962
  const maxAttempts = 600;
946
963
  let attempts = 0;
947
964
 
948
- // Create abort controller to allow Ctrl+C to interrupt polling
949
- const pollAbortController = new AbortController();
950
- const sigintHandler = () => {
951
- pollAbortController.abort();
952
- };
953
- process.on('SIGINT', sigintHandler);
965
+ // Reuse the deploy abort controller for polling (already aborted on Ctrl+C)
954
966
 
955
967
  try {
956
968
  if (streamId) {
@@ -1014,7 +1026,7 @@ export const deploySubcommand = createSubcommand({
1014
1026
  // Poll for deployment status
1015
1027
  while (attempts < maxAttempts) {
1016
1028
  // Check if user pressed Ctrl+C
1017
- if (pollAbortController.signal.aborted) {
1029
+ if (deployAbortController.signal.aborted) {
1018
1030
  logStreamController.abort();
1019
1031
  throw new DeploymentCancelledError();
1020
1032
  }
@@ -1023,7 +1035,8 @@ export const deploySubcommand = createSubcommand({
1023
1035
  try {
1024
1036
  statusResult = await projectDeploymentStatus(
1025
1037
  apiClient,
1026
- deployment?.id ?? ''
1038
+ deployment?.id ?? '',
1039
+ deployAbortController.signal
1027
1040
  );
1028
1041
 
1029
1042
  logger.trace('status result: %s', statusResult);
@@ -1115,14 +1128,15 @@ export const deploySubcommand = createSubcommand({
1115
1128
  callback: async () => {
1116
1129
  while (attempts < maxAttempts) {
1117
1130
  // Check if user pressed Ctrl+C
1118
- if (pollAbortController.signal.aborted) {
1131
+ if (deployAbortController.signal.aborted) {
1119
1132
  throw new DeploymentCancelledError();
1120
1133
  }
1121
1134
 
1122
1135
  attempts++;
1123
1136
  statusResult = await projectDeploymentStatus(
1124
1137
  apiClient,
1125
- deployment?.id ?? ''
1138
+ deployment?.id ?? '',
1139
+ deployAbortController.signal
1126
1140
  );
1127
1141
 
1128
1142
  if (statusResult.state === 'completed') {
@@ -1172,7 +1186,7 @@ export const deploySubcommand = createSubcommand({
1172
1186
  tui.fatal('Deployment failed', ErrorCode.BUILD_FAILED);
1173
1187
  } finally {
1174
1188
  // Clean up signal handler
1175
- process.off('SIGINT', sigintHandler);
1189
+ process.off('SIGINT', deployAbortHandler);
1176
1190
  }
1177
1191
 
1178
1192
  // Show deployment URLs
@@ -1185,15 +1199,18 @@ export const deploySubcommand = createSubcommand({
1185
1199
  );
1186
1200
  }
1187
1201
  } else {
1202
+ // Prefer vanity URLs, fall back to hash-based
1203
+ const deploymentUrl =
1204
+ complete.publicUrls.vanityDeployment ?? complete.publicUrls.deployment;
1205
+ const latestUrl = complete.publicUrls.vanityProject ?? complete.publicUrls.latest;
1188
1206
  lines.push(
1189
1207
  `${tui.ICONS.arrow} ${
1190
- tui.bold(tui.padRight('Deployment:', 12)) +
1191
- tui.link(complete.publicUrls.deployment)
1208
+ tui.bold(tui.padRight('Deployment:', 12)) + tui.link(deploymentUrl)
1192
1209
  }`
1193
1210
  );
1194
1211
  lines.push(
1195
1212
  `${tui.ICONS.arrow} ${
1196
- tui.bold(tui.padRight('Project:', 12)) + tui.link(complete.publicUrls.latest)
1213
+ tui.bold(tui.padRight('Project:', 12)) + tui.link(latestUrl)
1197
1214
  }`
1198
1215
  );
1199
1216
  }
@@ -1222,20 +1239,28 @@ export const deploySubcommand = createSubcommand({
1222
1239
  logs,
1223
1240
  urls: complete?.publicUrls
1224
1241
  ? {
1225
- deployment: complete.publicUrls.deployment,
1226
- latest: complete.publicUrls.latest,
1242
+ deployment:
1243
+ complete.publicUrls.vanityDeployment ?? complete.publicUrls.deployment,
1244
+ latest: complete.publicUrls.vanityProject ?? complete.publicUrls.latest,
1227
1245
  custom: complete.publicUrls.custom,
1228
1246
  dashboard,
1229
1247
  }
1230
1248
  : undefined,
1231
1249
  };
1232
1250
  } catch (ex) {
1251
+ // Handle step interruption (Ctrl+C during build steps)
1252
+ if (ex instanceof StepInterruptError) {
1253
+ tui.warning('Deployment cancelled');
1254
+ process.exit(ex.exitCode);
1255
+ }
1233
1256
  collector.addGeneralError('deploy', String(ex), 'DEPLOY004');
1234
1257
  if (opts.reportFile) {
1235
1258
  await collector.forceWrite();
1236
1259
  }
1237
1260
  clearGlobalCollector();
1238
1261
  tui.fatal(`unexpected error trying to deploy project. ${ex}`);
1262
+ } finally {
1263
+ process.off('SIGINT', deployAbortHandler);
1239
1264
  }
1240
1265
  },
1241
1266
  });
@@ -34,6 +34,14 @@ export const listSubcommand = createCommand({
34
34
  orgId: z.string().optional().describe('filter by organization id'),
35
35
  limit: z.coerce.number().optional().describe('Maximum number of queues to return'),
36
36
  offset: z.coerce.number().optional().describe('Offset for pagination'),
37
+ sort: z
38
+ .enum(['name', 'created', 'updated'])
39
+ .optional()
40
+ .describe('field to sort by (default: created)'),
41
+ direction: z
42
+ .enum(['asc', 'desc'])
43
+ .optional()
44
+ .describe('sort direction (default: desc)'),
37
45
  }),
38
46
  response: QueueListResponseSchema,
39
47
  },
@@ -48,6 +56,8 @@ export const listSubcommand = createCommand({
48
56
  {
49
57
  limit: opts.limit,
50
58
  offset: opts.offset,
59
+ sort: opts.sort,
60
+ direction: opts.direction,
51
61
  },
52
62
  queueOptions
53
63
  );
@@ -75,6 +75,14 @@ export const listSubcommand = createCommand({
75
75
  all: z.boolean().optional().describe('List all sandboxes regardless of project context'),
76
76
  limit: z.number().optional().describe('Maximum number of results (default: 50, max: 100)'),
77
77
  offset: z.number().optional().describe('Pagination offset'),
78
+ sort: z
79
+ .enum(['name', 'created', 'updated', 'status'])
80
+ .optional()
81
+ .describe('field to sort by (default: created)'),
82
+ direction: z
83
+ .enum(['asc', 'desc'])
84
+ .optional()
85
+ .describe('sort direction (default: desc)'),
78
86
  }),
79
87
  response: SandboxListResponseSchema,
80
88
  },
@@ -94,6 +102,8 @@ export const listSubcommand = createCommand({
94
102
  status: opts.status,
95
103
  limit: opts.limit,
96
104
  offset: opts.offset,
105
+ sort: opts.sort,
106
+ direction: opts.direction,
97
107
  });
98
108
 
99
109
  // Check if results span multiple orgs
@@ -34,6 +34,14 @@ export const listSubcommand = createCommand({
34
34
  limit: z.number().optional().describe('Maximum number of results'),
35
35
  offset: z.number().optional().describe('Offset for pagination'),
36
36
  orgId: z.string().optional().describe('filter by organization id'),
37
+ sort: z
38
+ .enum(['name', 'created'])
39
+ .optional()
40
+ .describe('field to sort by (default: created)'),
41
+ direction: z
42
+ .enum(['asc', 'desc'])
43
+ .optional()
44
+ .describe('sort direction (default: desc)'),
37
45
  }),
38
46
  response: RuntimeListResponseSchema,
39
47
  },
@@ -47,6 +55,8 @@ export const listSubcommand = createCommand({
47
55
  orgId: effectiveOrgId,
48
56
  limit: opts.limit,
49
57
  offset: opts.offset,
58
+ sort: opts.sort,
59
+ direction: opts.direction,
50
60
  });
51
61
 
52
62
  if (!options.json) {
@@ -47,6 +47,14 @@ export const listSubcommand = createCommand({
47
47
  limit: z.number().optional().describe('Maximum number of results'),
48
48
  offset: z.number().optional().describe('Offset for pagination'),
49
49
  orgId: z.string().optional().describe('filter by organization id'),
50
+ sort: z
51
+ .enum(['name', 'created', 'size'])
52
+ .optional()
53
+ .describe('field to sort by (default: created)'),
54
+ direction: z
55
+ .enum(['asc', 'desc'])
56
+ .optional()
57
+ .describe('sort direction (default: desc)'),
50
58
  }),
51
59
  response: SnapshotListResponseSchema,
52
60
  },
@@ -61,6 +69,8 @@ export const listSubcommand = createCommand({
61
69
  limit: opts.limit,
62
70
  offset: opts.offset,
63
71
  orgId: effectiveOrgId,
72
+ sort: opts.sort,
73
+ direction: opts.direction,
64
74
  });
65
75
 
66
76
  if (!options.json) {
@@ -46,10 +46,10 @@ const SessionGetResponseSchema = z.object({
46
46
  pending: z.boolean().describe('Pending'),
47
47
  success: z.boolean().describe('Success'),
48
48
  error: z.string().nullable().describe('Error message'),
49
- method: z.string().describe('HTTP method'),
50
- url: z.string().describe('Request URL'),
51
- route_id: z.string().describe('Route ID'),
52
- thread_id: z.string().describe('Thread ID'),
49
+ method: z.string().nullable().describe('HTTP method'),
50
+ url: z.string().nullable().describe('Request URL'),
51
+ route_id: z.string().nullable().describe('Route ID'),
52
+ thread_id: z.string().nullable().describe('Thread ID'),
53
53
  agents: z
54
54
  .array(
55
55
  z.object({
@@ -148,10 +148,10 @@ export const getSubcommand = createSubcommand({
148
148
  pending: session.pending,
149
149
  success: session.success,
150
150
  error: session.error,
151
- method: session.method,
152
- url: session.url,
153
- route_id: session.route_id,
154
- thread_id: session.thread_id,
151
+ method: session.method ?? null,
152
+ url: session.url ?? null,
153
+ route_id: session.route_id ?? null,
154
+ thread_id: session.thread_id ?? null,
155
155
  agents: enriched.agents,
156
156
  eval_runs: enriched.evalRuns.map((run: EvalRun) => ({
157
157
  id: run.id,
@@ -183,8 +183,8 @@ export const getSubcommand = createSubcommand({
183
183
  if (session.duration != null && session.end_time != null) {
184
184
  tableData['Duration'] = `${(session.duration / 1_000_000).toFixed(0)}ms`;
185
185
  }
186
- tableData['Method'] = session.method;
187
- tableData['URL'] = tui.link(session.url, session.url);
186
+ tableData['Method'] = session.method ?? '-';
187
+ tableData['URL'] = session.url ? tui.link(session.url, session.url) : '-';
188
188
  tableData['Trigger'] = session.trigger;
189
189
  if (session.env !== 'production') {
190
190
  tableData['Environment'] = session.env;
@@ -204,9 +204,9 @@ export const getSubcommand = createSubcommand({
204
204
  tableData['Route'] =
205
205
  `${enriched.route.method.toUpperCase()} ${enriched.route.path} ${tui.muted(`(${enriched.route.id})`)}`;
206
206
  } else {
207
- tableData['Route ID'] = session.route_id;
207
+ tableData['Route ID'] = session.route_id ?? '-';
208
208
  }
209
- tableData['Thread ID'] = session.thread_id;
209
+ tableData['Thread ID'] = session.thread_id ?? '-';
210
210
 
211
211
  tui.table([tableData], Object.keys(tableData), { layout: 'vertical', padStart: ' ' });
212
212