@agentuity/cli 0.0.110 → 0.0.111

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 (193) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +19 -4
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cmd/build/vite/agent-discovery.d.ts +1 -1
  5. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  6. package/dist/cmd/build/vite/agent-discovery.js +3 -3
  7. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  8. package/dist/cmd/build/vite/index.js +1 -1
  9. package/dist/cmd/build/vite/index.js.map +1 -1
  10. package/dist/cmd/build/vite/metadata-generator.js +1 -1
  11. package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
  12. package/dist/cmd/build/vite/registry-generator.d.ts +1 -1
  13. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  14. package/dist/cmd/build/vite/registry-generator.js +70 -23
  15. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  16. package/dist/cmd/build/vite/route-discovery.d.ts +6 -0
  17. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  18. package/dist/cmd/build/vite/route-discovery.js +19 -0
  19. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  20. package/dist/cmd/build/vite/vite-builder.js +1 -1
  21. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  22. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  23. package/dist/cmd/cloud/deploy.js +63 -1
  24. package/dist/cmd/cloud/deploy.js.map +1 -1
  25. package/dist/cmd/cloud/sandbox/create.d.ts.map +1 -1
  26. package/dist/cmd/cloud/sandbox/create.js +18 -0
  27. package/dist/cmd/cloud/sandbox/create.js.map +1 -1
  28. package/dist/cmd/cloud/sandbox/delete.d.ts.map +1 -1
  29. package/dist/cmd/cloud/sandbox/delete.js +2 -6
  30. package/dist/cmd/cloud/sandbox/delete.js.map +1 -1
  31. package/dist/cmd/cloud/sandbox/download.d.ts +3 -0
  32. package/dist/cmd/cloud/sandbox/download.d.ts.map +1 -0
  33. package/dist/cmd/cloud/sandbox/download.js +89 -0
  34. package/dist/cmd/cloud/sandbox/download.js.map +1 -0
  35. package/dist/cmd/cloud/sandbox/env.d.ts +3 -0
  36. package/dist/cmd/cloud/sandbox/env.d.ts.map +1 -0
  37. package/dist/cmd/cloud/sandbox/env.js +90 -0
  38. package/dist/cmd/cloud/sandbox/env.js.map +1 -0
  39. package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -1
  40. package/dist/cmd/cloud/sandbox/get.js +5 -0
  41. package/dist/cmd/cloud/sandbox/get.js.map +1 -1
  42. package/dist/cmd/cloud/sandbox/index.d.ts.map +1 -1
  43. package/dist/cmd/cloud/sandbox/index.js +14 -0
  44. package/dist/cmd/cloud/sandbox/index.js.map +1 -1
  45. package/dist/cmd/cloud/sandbox/ls.d.ts +3 -0
  46. package/dist/cmd/cloud/sandbox/ls.d.ts.map +1 -0
  47. package/dist/cmd/cloud/sandbox/ls.js +119 -0
  48. package/dist/cmd/cloud/sandbox/ls.js.map +1 -0
  49. package/dist/cmd/cloud/sandbox/mkdir.d.ts +3 -0
  50. package/dist/cmd/cloud/sandbox/mkdir.d.ts.map +1 -0
  51. package/dist/cmd/cloud/sandbox/mkdir.js +59 -0
  52. package/dist/cmd/cloud/sandbox/mkdir.js.map +1 -0
  53. package/dist/cmd/cloud/sandbox/rm.d.ts +3 -0
  54. package/dist/cmd/cloud/sandbox/rm.d.ts.map +1 -0
  55. package/dist/cmd/cloud/sandbox/rm.js +45 -0
  56. package/dist/cmd/cloud/sandbox/rm.js.map +1 -0
  57. package/dist/cmd/cloud/sandbox/rmdir.d.ts +3 -0
  58. package/dist/cmd/cloud/sandbox/rmdir.d.ts.map +1 -0
  59. package/dist/cmd/cloud/sandbox/rmdir.js +59 -0
  60. package/dist/cmd/cloud/sandbox/rmdir.js.map +1 -0
  61. package/dist/cmd/cloud/sandbox/snapshot/create.d.ts.map +1 -1
  62. package/dist/cmd/cloud/sandbox/snapshot/create.js +0 -2
  63. package/dist/cmd/cloud/sandbox/snapshot/create.js.map +1 -1
  64. package/dist/cmd/cloud/sandbox/snapshot/get.d.ts.map +1 -1
  65. package/dist/cmd/cloud/sandbox/snapshot/get.js +0 -2
  66. package/dist/cmd/cloud/sandbox/snapshot/get.js.map +1 -1
  67. package/dist/cmd/cloud/sandbox/snapshot/list.d.ts.map +1 -1
  68. package/dist/cmd/cloud/sandbox/snapshot/list.js +0 -3
  69. package/dist/cmd/cloud/sandbox/snapshot/list.js.map +1 -1
  70. package/dist/cmd/cloud/sandbox/upload.d.ts +3 -0
  71. package/dist/cmd/cloud/sandbox/upload.d.ts.map +1 -0
  72. package/dist/cmd/cloud/sandbox/upload.js +77 -0
  73. package/dist/cmd/cloud/sandbox/upload.js.map +1 -0
  74. package/dist/cmd/dev/index.d.ts.map +1 -1
  75. package/dist/cmd/dev/index.js +17 -8
  76. package/dist/cmd/dev/index.js.map +1 -1
  77. package/dist/cmd/dev/sync.d.ts.map +1 -1
  78. package/dist/cmd/dev/sync.js +8 -14
  79. package/dist/cmd/dev/sync.js.map +1 -1
  80. package/dist/cmd/git/account/add.d.ts +17 -0
  81. package/dist/cmd/git/account/add.d.ts.map +1 -0
  82. package/dist/cmd/git/account/add.js +244 -0
  83. package/dist/cmd/git/account/add.js.map +1 -0
  84. package/dist/cmd/git/account/index.d.ts +3 -0
  85. package/dist/cmd/git/account/index.d.ts.map +1 -0
  86. package/dist/cmd/git/account/index.js +11 -0
  87. package/dist/cmd/git/account/index.js.map +1 -0
  88. package/dist/cmd/git/account/list.d.ts +2 -0
  89. package/dist/cmd/git/account/list.d.ts.map +1 -0
  90. package/dist/cmd/git/account/list.js +111 -0
  91. package/dist/cmd/git/account/list.js.map +1 -0
  92. package/dist/cmd/git/account/remove.d.ts +2 -0
  93. package/dist/cmd/git/account/remove.d.ts.map +1 -0
  94. package/dist/cmd/git/account/remove.js +171 -0
  95. package/dist/cmd/git/account/remove.js.map +1 -0
  96. package/dist/cmd/git/index.d.ts +3 -0
  97. package/dist/cmd/git/index.d.ts.map +1 -0
  98. package/dist/cmd/git/index.js +19 -0
  99. package/dist/cmd/git/index.js.map +1 -0
  100. package/dist/cmd/git/link.d.ts +32 -0
  101. package/dist/cmd/git/link.d.ts.map +1 -0
  102. package/dist/cmd/git/link.js +357 -0
  103. package/dist/cmd/git/link.js.map +1 -0
  104. package/dist/cmd/git/list.d.ts +2 -0
  105. package/dist/cmd/git/list.d.ts.map +1 -0
  106. package/dist/cmd/git/list.js +137 -0
  107. package/dist/cmd/git/list.js.map +1 -0
  108. package/dist/cmd/git/status.d.ts +2 -0
  109. package/dist/cmd/git/status.d.ts.map +1 -0
  110. package/dist/cmd/git/status.js +119 -0
  111. package/dist/cmd/git/status.js.map +1 -0
  112. package/dist/cmd/git/unlink.d.ts +2 -0
  113. package/dist/cmd/git/unlink.d.ts.map +1 -0
  114. package/dist/cmd/git/unlink.js +98 -0
  115. package/dist/cmd/git/unlink.js.map +1 -0
  116. package/dist/cmd/index.d.ts.map +1 -1
  117. package/dist/cmd/index.js +2 -0
  118. package/dist/cmd/index.js.map +1 -1
  119. package/dist/cmd/integration/api.d.ts +61 -0
  120. package/dist/cmd/integration/api.d.ts.map +1 -0
  121. package/dist/cmd/integration/api.js +176 -0
  122. package/dist/cmd/integration/api.js.map +1 -0
  123. package/dist/cmd/integration/github/connect.d.ts +2 -0
  124. package/dist/cmd/integration/github/connect.d.ts.map +1 -0
  125. package/dist/cmd/integration/github/connect.js +197 -0
  126. package/dist/cmd/integration/github/connect.js.map +1 -0
  127. package/dist/cmd/integration/github/disconnect.d.ts +2 -0
  128. package/dist/cmd/integration/github/disconnect.d.ts.map +1 -0
  129. package/dist/cmd/integration/github/disconnect.js +121 -0
  130. package/dist/cmd/integration/github/disconnect.js.map +1 -0
  131. package/dist/cmd/integration/github/index.d.ts +2 -0
  132. package/dist/cmd/integration/github/index.d.ts.map +1 -0
  133. package/dist/cmd/integration/github/index.js +21 -0
  134. package/dist/cmd/integration/github/index.js.map +1 -0
  135. package/dist/cmd/integration/index.d.ts +2 -0
  136. package/dist/cmd/integration/index.d.ts.map +1 -0
  137. package/dist/cmd/integration/index.js +16 -0
  138. package/dist/cmd/integration/index.js.map +1 -0
  139. package/dist/config.d.ts +2 -0
  140. package/dist/config.d.ts.map +1 -1
  141. package/dist/config.js +24 -0
  142. package/dist/config.js.map +1 -1
  143. package/dist/errors.d.ts +2 -1
  144. package/dist/errors.d.ts.map +1 -1
  145. package/dist/errors.js +5 -0
  146. package/dist/errors.js.map +1 -1
  147. package/dist/types.d.ts +2 -1
  148. package/dist/types.d.ts.map +1 -1
  149. package/dist/types.js +4 -0
  150. package/dist/types.js.map +1 -1
  151. package/package.json +6 -6
  152. package/src/cli.ts +20 -4
  153. package/src/cmd/build/vite/agent-discovery.ts +4 -4
  154. package/src/cmd/build/vite/index.ts +1 -1
  155. package/src/cmd/build/vite/metadata-generator.ts +1 -1
  156. package/src/cmd/build/vite/registry-generator.ts +78 -24
  157. package/src/cmd/build/vite/route-discovery.ts +20 -0
  158. package/src/cmd/build/vite/vite-builder.ts +1 -1
  159. package/src/cmd/cloud/deploy.ts +78 -1
  160. package/src/cmd/cloud/sandbox/create.ts +22 -0
  161. package/src/cmd/cloud/sandbox/delete.ts +2 -6
  162. package/src/cmd/cloud/sandbox/download.ts +96 -0
  163. package/src/cmd/cloud/sandbox/env.ts +104 -0
  164. package/src/cmd/cloud/sandbox/get.ts +5 -0
  165. package/src/cmd/cloud/sandbox/index.ts +14 -0
  166. package/src/cmd/cloud/sandbox/ls.ts +126 -0
  167. package/src/cmd/cloud/sandbox/mkdir.ts +65 -0
  168. package/src/cmd/cloud/sandbox/rm.ts +51 -0
  169. package/src/cmd/cloud/sandbox/rmdir.ts +65 -0
  170. package/src/cmd/cloud/sandbox/snapshot/create.ts +0 -2
  171. package/src/cmd/cloud/sandbox/snapshot/get.ts +0 -2
  172. package/src/cmd/cloud/sandbox/snapshot/list.ts +0 -3
  173. package/src/cmd/cloud/sandbox/upload.ts +83 -0
  174. package/src/cmd/dev/index.ts +32 -19
  175. package/src/cmd/dev/sync.ts +26 -30
  176. package/src/cmd/git/account/add.ts +317 -0
  177. package/src/cmd/git/account/index.ts +12 -0
  178. package/src/cmd/git/account/list.ts +139 -0
  179. package/src/cmd/git/account/remove.ts +212 -0
  180. package/src/cmd/git/index.ts +20 -0
  181. package/src/cmd/git/link.ts +468 -0
  182. package/src/cmd/git/list.ts +161 -0
  183. package/src/cmd/git/status.ts +144 -0
  184. package/src/cmd/git/unlink.ts +117 -0
  185. package/src/cmd/index.ts +2 -0
  186. package/src/cmd/integration/api.ts +379 -0
  187. package/src/cmd/integration/github/connect.ts +242 -0
  188. package/src/cmd/integration/github/disconnect.ts +149 -0
  189. package/src/cmd/integration/github/index.ts +21 -0
  190. package/src/cmd/integration/index.ts +16 -0
  191. package/src/config.ts +34 -0
  192. package/src/errors.ts +7 -0
  193. package/src/types.ts +4 -0
package/src/cli.ts CHANGED
@@ -15,7 +15,7 @@ import type {
15
15
  } from './types';
16
16
  import { showBanner, generateBanner } from './banner';
17
17
  import { requireAuth, optionalAuth, requireOrg, optionalOrg as selectOptionalOrg } from './auth';
18
- import { listRegions, type RegionList } from '@agentuity/server';
18
+ import { listRegions, type RegionList, ValidationOutputError } from '@agentuity/server';
19
19
  import enquirer from 'enquirer';
20
20
  import * as tui from './tui';
21
21
  import { parseArgsSchema, parseOptionsSchema, buildValidationInput } from './schema-parser';
@@ -26,6 +26,22 @@ import { getCommand } from './command-prefix';
26
26
  import { isValidateMode, outputValidation, type ValidationResult } from './output';
27
27
  import { StructuredError } from '@agentuity/core';
28
28
 
29
+ /**
30
+ * Check if an error is a CLI input validation error (Zod error from schema parsing),
31
+ * and not an API response validation error (ValidationOutputError).
32
+ */
33
+ function isCLIValidationError(error: unknown): boolean {
34
+ if (!error || typeof error !== 'object' || !('issues' in error)) {
35
+ return false;
36
+ }
37
+ // ValidationOutputError from API responses should NOT be treated as CLI validation errors
38
+ if (error instanceof ValidationOutputError) {
39
+ return false;
40
+ }
41
+ // Check for Zod error structure (has name 'ZodError' or is from SchemaValidationError)
42
+ return true;
43
+ }
44
+
29
45
  const APIClientConfigError = StructuredError('APIClientConfigError');
30
46
 
31
47
  function createAPIClient(baseCtx: CommandContext, config: Config | null): APIClient {
@@ -1143,7 +1159,7 @@ async function registerSubcommand(
1143
1159
  subcommand.webUrl
1144
1160
  );
1145
1161
  } catch (error) {
1146
- if (error && typeof error === 'object' && 'issues' in error) {
1162
+ if (isCLIValidationError(error)) {
1147
1163
  handleValidationError(error, getFullCommandPath(cmd), baseCtx);
1148
1164
  }
1149
1165
  handleProjectConfigError(
@@ -1322,7 +1338,7 @@ async function registerSubcommand(
1322
1338
  subcommand.webUrl
1323
1339
  );
1324
1340
  } catch (error) {
1325
- if (error && typeof error === 'object' && 'issues' in error) {
1341
+ if (isCLIValidationError(error)) {
1326
1342
  handleValidationError(error, getFullCommandPath(cmd), baseCtx);
1327
1343
  }
1328
1344
  handleProjectConfigError(
@@ -1447,7 +1463,7 @@ async function registerSubcommand(
1447
1463
  subcommand.webUrl
1448
1464
  );
1449
1465
  } catch (error) {
1450
- if (error && typeof error === 'object' && 'issues' in error) {
1466
+ if (isCLIValidationError(error)) {
1451
1467
  handleValidationError(error, getFullCommandPath(cmd), baseCtx);
1452
1468
  }
1453
1469
  handleProjectConfigError(
@@ -65,7 +65,7 @@ export interface AgentMetadata {
65
65
 
66
66
  export interface EvalMetadata {
67
67
  id: string;
68
- evalId: string;
68
+ identifier: string;
69
69
  name: string;
70
70
  filename: string;
71
71
  version: string;
@@ -459,13 +459,13 @@ function extractEvalsFromSource(
459
459
 
460
460
  if (evalName) {
461
461
  const id = getEvalId(projectId, deploymentId, filename, evalName, version);
462
- const evalId = generateStableEvalId(projectId, agentId, evalName);
462
+ const identifier = generateStableEvalId(projectId, agentId, evalName);
463
463
 
464
- logger.trace(`Found eval '${evalName}' in ${filename} (evalId: ${evalId})`);
464
+ logger.trace(`Found eval '${evalName}' in ${filename} (identifier: ${identifier})`);
465
465
 
466
466
  evals.push({
467
467
  id,
468
- evalId,
468
+ identifier,
469
469
  name: evalName,
470
470
  filename,
471
471
  version,
@@ -87,7 +87,7 @@ export function agentuityPlugin(options: AgentuityPluginOptions): Plugin {
87
87
  }
88
88
 
89
89
  if (routeInfoList.length > 0) {
90
- generateRouteRegistry(srcDir, routeInfoList, agents);
90
+ await generateRouteRegistry(srcDir, routeInfoList, agents);
91
91
  logger.trace('Generated route registry with %d route(s)', routeInfoList.length);
92
92
  }
93
93
 
@@ -443,7 +443,7 @@ export async function generateMetadata(options: MetadataGeneratorOptions): Promi
443
443
  evals: agent.evals?.map((evalItem) => ({
444
444
  filename: evalItem.filename,
445
445
  id: evalItem.id,
446
- evalId: evalItem.evalId,
446
+ identifier: evalItem.identifier,
447
447
  name: evalItem.name,
448
448
  version: evalItem.version,
449
449
  description: evalItem.description,
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { join } from 'node:path';
8
8
  import { writeFileSync, mkdirSync, existsSync, unlinkSync, readFileSync } from 'node:fs';
9
+ import { stat } from 'node:fs/promises';
9
10
  import { StructuredError } from '@agentuity/core';
10
11
  import { toCamelCase, toPascalCase } from '../../../utils/string';
11
12
  import type { AgentMetadata } from './agent-discovery';
@@ -29,6 +30,28 @@ function sanitizePathSegment(segment: string): string {
29
30
  return toCamelCase(segment.replace(ROUTE_PARAM_CHARS, ''));
30
31
  }
31
32
 
33
+ /**
34
+ * Generate TypeScript type for path parameters.
35
+ * Returns 'never' if no path params, or '{ param1: string; param2: string }' format.
36
+ */
37
+ function generatePathParamsType(pathParams?: string[]): string {
38
+ if (!pathParams || pathParams.length === 0) {
39
+ return 'never';
40
+ }
41
+ return `{ ${pathParams.map((p) => `${p}: string`).join('; ')} }`;
42
+ }
43
+
44
+ /**
45
+ * Generate TypeScript tuple type for path parameters (for positional args).
46
+ * Returns '[]' if no path params, or '[string, string]' format.
47
+ */
48
+ function generatePathParamsTupleType(pathParams?: string[]): string {
49
+ if (!pathParams || pathParams.length === 0) {
50
+ return '[]';
51
+ }
52
+ return `[${pathParams.map(() => 'string').join(', ')}]`;
53
+ }
54
+
32
55
  /**
33
56
  * Generate src/generated/registry.ts with agent registry and types
34
57
  */
@@ -362,8 +385,10 @@ function generateRPCRegistryType(
362
385
  jsdoc.push(`${indent} */`);
363
386
  lines.push(...jsdoc);
364
387
 
388
+ const pathParamsType = generatePathParamsType(routeInfo.pathParams);
389
+ const pathParamsTupleType = generatePathParamsTupleType(routeInfo.pathParams);
365
390
  lines.push(
366
- `${indent}${key}: { input: ${value.input}; output: ${value.output}; type: ${value.type} };`
391
+ `${indent}${key}: { input: ${value.input}; output: ${value.output}; type: ${value.type}; params: ${pathParamsType}; paramsTuple: ${pathParamsTupleType} };`
367
392
  );
368
393
  } else {
369
394
  // Nested node
@@ -393,7 +418,7 @@ function generateRPCRuntimeMetadata(
393
418
  sseRoutes: RouteInfo[]
394
419
  ): string {
395
420
  interface MetadataNode {
396
- [key: string]: MetadataNode | { type: string };
421
+ [key: string]: MetadataNode | { type: string; path: string; pathParams?: string[] };
397
422
  }
398
423
 
399
424
  const tree: MetadataNode = {};
@@ -431,7 +456,14 @@ function generateRPCRuntimeMetadata(
431
456
  ? 'stream'
432
457
  : route.method.toLowerCase();
433
458
 
434
- current[terminalMethod] = { type: routeType };
459
+ const metadata: { type: string; path: string; pathParams?: string[] } = {
460
+ type: routeType,
461
+ path: route.path,
462
+ };
463
+ if (route.pathParams && route.pathParams.length > 0) {
464
+ metadata.pathParams = route.pathParams;
465
+ }
466
+ current[terminalMethod] = metadata;
435
467
  };
436
468
 
437
469
  apiRoutes.forEach((r) => addRoute(r, r.routeType === 'stream' ? 'stream' : 'api'));
@@ -461,15 +493,15 @@ function generateRPCRuntimeMetadata(
461
493
  * Creates a module augmentation for @agentuity/react that provides
462
494
  * strongly-typed route keys with input/output schema information.
463
495
  */
464
- export function generateRouteRegistry(
496
+ export async function generateRouteRegistry(
465
497
  srcDir: string,
466
498
  routes: RouteInfo[],
467
499
  agents: AgentMetadata[] = []
468
- ): void {
469
- // Check if project uses @agentuity/react
500
+ ): Promise<void> {
470
501
  const projectRoot = join(srcDir, '..');
471
502
  const packageJsonPath = join(projectRoot, 'package.json');
472
503
  let hasReactDependency = false;
504
+ let hasFrontendDependency = false;
473
505
 
474
506
  try {
475
507
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
@@ -477,10 +509,25 @@ export function generateRouteRegistry(
477
509
  packageJson.dependencies?.['@agentuity/react'] ||
478
510
  packageJson.devDependencies?.['@agentuity/react']
479
511
  );
512
+ hasFrontendDependency = !!(
513
+ packageJson.dependencies?.['@agentuity/frontend'] ||
514
+ packageJson.devDependencies?.['@agentuity/frontend']
515
+ );
480
516
  } catch {
481
- // If we can't read package.json, assume no React dependency
517
+ // If we can't read package.json, assume no frontend dependencies
482
518
  }
483
519
 
520
+ const webDir = join(srcDir, 'web');
521
+ let hasWebDirectory = false;
522
+ try {
523
+ const webDirStat = await stat(webDir);
524
+ hasWebDirectory = webDirStat.isDirectory();
525
+ } catch {
526
+ // Directory doesn't exist
527
+ }
528
+
529
+ const shouldEmitFrontendClient = hasFrontendDependency && !hasReactDependency && hasWebDirectory;
530
+
484
531
  // Filter routes by type and sort by path for deterministic output
485
532
  const sortByPath = (a: RouteInfo, b: RouteInfo) => a.path.localeCompare(b.path);
486
533
  const apiRoutes = routes
@@ -769,13 +816,18 @@ export function generateRouteRegistry(
769
816
  // because only 'json' validators extract input schemas
770
817
  // Also check if agentVariable exists but import wasn't added (missing agentImportPath)
771
818
  const hasValidAgentImport = route.agentVariable ? !!importName : false;
819
+
820
+ // Generate pathParams type
821
+ const pathParamsType = generatePathParamsType(route.pathParams);
822
+
772
823
  if (!route.inputSchemaVariable && !route.outputSchemaVariable && !hasValidAgentImport) {
773
824
  const streamValue = route.stream === true ? 'true' : 'false';
774
825
  return `\t'${routeKey}': {
775
- \t\tinputSchema: never;
776
- \t\toutputSchema: never;
777
- \t\tstream: ${streamValue};
778
- \t};`;
826
+ \t\tinputSchema: never;
827
+ \t\toutputSchema: never;
828
+ \t\tstream: ${streamValue};
829
+ \t\tparams: ${pathParamsType};
830
+ \t};`;
779
831
  }
780
832
  const streamValue = importName
781
833
  ? `typeof ${importName} extends { stream?: infer S } ? S : false`
@@ -784,10 +836,11 @@ export function generateRouteRegistry(
784
836
  : 'false';
785
837
 
786
838
  return `\t'${routeKey}': {
787
- \t\tinputSchema: ${pascalName}InputSchema;
788
- \t\toutputSchema: ${pascalName}OutputSchema;
789
- \t\tstream: ${streamValue};
790
- \t};`;
839
+ \t\tinputSchema: ${pascalName}InputSchema;
840
+ \t\toutputSchema: ${pascalName}OutputSchema;
841
+ \t\tstream: ${streamValue};
842
+ \t\tparams: ${pathParamsType};
843
+ \t};`;
791
844
  };
792
845
 
793
846
  // Generate route entries with METHOD prefix for API routes
@@ -817,7 +870,7 @@ export function generateRouteRegistry(
817
870
  const generatedContent = `// @generated
818
871
  // Auto-generated by Agentuity - DO NOT EDIT
819
872
  ${importsStr}${typeImports}${
820
- !hasReactDependency
873
+ shouldEmitFrontendClient
821
874
  ? `
822
875
  import { createClient } from '@agentuity/frontend';`
823
876
  : ''
@@ -842,7 +895,7 @@ ${routeSchemaTypes}
842
895
  * Individual route Input/Output types are exported above for direct usage.
843
896
  */
844
897
  ${
845
- !hasReactDependency
898
+ shouldEmitFrontendClient
846
899
  ? `
847
900
  /**
848
901
  * RPC Route Registry
@@ -907,13 +960,14 @@ if (typeof globalThis !== 'undefined') {
907
960
  (globalThis as Record<string, unknown>).__rpcRouteMetadata = _rpcRouteMetadata;
908
961
  }
909
962
  ${
910
- !hasReactDependency
963
+ shouldEmitFrontendClient
911
964
  ? `
912
965
  /**
913
966
  * Create a type-safe API client with optional configuration.
914
967
  *
915
- * This function is only generated when @agentuity/react is not installed.
916
- * If using React, import createAPIClient from '@agentuity/react' instead.
968
+ * This function is only generated when @agentuity/frontend is installed
969
+ * but @agentuity/react is not. For React apps, import createAPIClient
970
+ * from '@agentuity/react' instead.
917
971
  *
918
972
  * @example
919
973
  * \`\`\`typescript
@@ -932,7 +986,8 @@ export function createAPIClient(options?: Parameters<typeof createClient>[0]): i
932
986
  return createClient(options || {}, _rpcRouteMetadata) as import('@agentuity/frontend').Client<RPCRouteRegistry>;
933
987
  }
934
988
  `
935
- : `
989
+ : hasReactDependency
990
+ ? `
936
991
  /**
937
992
  * Type-safe API client is available from @agentuity/react
938
993
  *
@@ -945,6 +1000,7 @@ export function createAPIClient(options?: Parameters<typeof createClient>[0]): i
945
1000
  * \`\`\`
946
1001
  */
947
1002
  `
1003
+ : ''
948
1004
  }
949
1005
 
950
1006
  // FOUND AN ERROR IN THIS FILE?
@@ -955,9 +1011,7 @@ export function createAPIClient(options?: Parameters<typeof createClient>[0]): i
955
1011
  const generatedDir = join(srcDir, 'generated');
956
1012
  const registryPath = join(generatedDir, 'routes.ts');
957
1013
 
958
- if (!existsSync(generatedDir)) {
959
- mkdirSync(generatedDir, { recursive: true });
960
- }
1014
+ mkdirSync(generatedDir, { recursive: true });
961
1015
 
962
1016
  // Collapse 2+ consecutive empty lines into 1 empty line (3+ \n becomes 2 \n)
963
1017
  const cleanedContent = generatedContent.replace(/\n{3,}/g, '\n\n');
@@ -40,6 +40,24 @@ export interface RouteInfo {
40
40
  inputSchemaCode?: string;
41
41
  outputSchemaCode?: string;
42
42
  stream?: boolean;
43
+ pathParams?: string[];
44
+ }
45
+
46
+ /**
47
+ * Extract path parameters from a route path.
48
+ * Matches patterns like :id, :userId, :id?, *path, etc.
49
+ */
50
+ export function extractPathParams(path: string): string[] {
51
+ const params: string[] = [];
52
+ const parts = path.split('/');
53
+ for (const part of parts) {
54
+ if (part.startsWith(':')) {
55
+ params.push(part.replace(/^:|[?+*]$/g, ''));
56
+ } else if (part.startsWith('*') && part.length > 1) {
57
+ params.push(part.substring(1).replace(/[?+*]$/g, ''));
58
+ }
59
+ }
60
+ return params;
43
61
  }
44
62
 
45
63
  /**
@@ -90,6 +108,7 @@ export async function discoverRoutes(
90
108
 
91
109
  // Convert to RouteInfo for registry
92
110
  for (const route of parsedRoutes) {
111
+ const pathParams = extractPathParams(route.path);
93
112
  routeInfoList.push({
94
113
  method: route.method.toUpperCase(),
95
114
  path: route.path,
@@ -106,6 +125,7 @@ export async function discoverRoutes(
106
125
  : route.type === 'stream'
107
126
  ? true
108
127
  : undefined,
128
+ pathParams: pathParams.length > 0 ? pathParams : undefined,
109
129
  });
110
130
  }
111
131
  }
@@ -305,7 +305,7 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
305
305
  // Generate agent and route registries for type augmentation BEFORE builds
306
306
  // (TypeScript needs these files to exist during type checking)
307
307
  generateAgentRegistry(srcDir, agentMetadata);
308
- generateRouteRegistry(srcDir, routeInfoList);
308
+ await generateRouteRegistry(srcDir, routeInfoList);
309
309
  logger.debug('Agent and route registries generated');
310
310
 
311
311
  // Check if web frontend exists
@@ -8,7 +8,14 @@ import { isRunningFromExecutable } from '../upgrade';
8
8
  import { createSubcommand, DeployOptionsSchema } from '../../types';
9
9
  import { getUserAgent } from '../../api';
10
10
  import * as tui from '../../tui';
11
- import { saveProjectDir, getDefaultConfigDir, loadProjectSDKKey } from '../../config';
11
+ import {
12
+ saveProjectDir,
13
+ getDefaultConfigDir,
14
+ loadProjectSDKKey,
15
+ updateProjectConfig,
16
+ } from '../../config';
17
+ import { getProjectGithubStatus } from '../integration/api';
18
+ import { runGitLink } from '../git/link';
12
19
  import {
13
20
  runSteps,
14
21
  stepSuccess,
@@ -163,6 +170,76 @@ export const deploySubcommand = createSubcommand({
163
170
  try {
164
171
  await saveProjectDir(projectDir);
165
172
 
173
+ // Check GitHub status and prompt for setup if not linked
174
+ // Skip in non-TTY environments (CI, automated runs) to prevent hanging
175
+ const hasTTY = process.stdin.isTTY && process.stdout.isTTY;
176
+ if (!useExistingDeployment && !project.skipGitSetup && hasTTY) {
177
+ try {
178
+ const githubStatus = await getProjectGithubStatus(apiClient, project.projectId);
179
+
180
+ if (githubStatus.linked && githubStatus.autoDeploy) {
181
+ // GitHub is already set up with auto-deploy, tell user to push instead
182
+ tui.newline();
183
+ tui.info(
184
+ `This project is linked to ${tui.bold(githubStatus.repoFullName ?? 'GitHub')} with automatic deployments enabled.`
185
+ );
186
+ tui.newline();
187
+ tui.info(
188
+ `Push a commit to the ${tui.bold(githubStatus.branch ?? 'main')} branch to trigger a deployment.`
189
+ );
190
+ tui.newline();
191
+ throw new DeploymentCancelledError();
192
+ }
193
+
194
+ if (!githubStatus.linked) {
195
+ tui.newline();
196
+ const wantSetup = await tui.confirm(
197
+ 'Would you like to set up automatic deployments from GitHub?'
198
+ );
199
+
200
+ if (wantSetup) {
201
+ const result = await runGitLink({
202
+ apiClient,
203
+ projectId: project.projectId,
204
+ orgId: project.orgId,
205
+ logger,
206
+ skipAlreadyLinkedCheck: true,
207
+ config,
208
+ });
209
+
210
+ if (result.linked && result.autoDeploy) {
211
+ // GitHub linked with auto-deploy, tell user to push instead
212
+ tui.newline();
213
+ tui.info('GitHub integration set up successfully!');
214
+ tui.newline();
215
+ tui.info('Push a commit to trigger your first deployment.');
216
+ tui.newline();
217
+ throw new DeploymentCancelledError();
218
+ } else if (result.linked) {
219
+ // Linked but auto-deploy disabled, continue with manual deploy
220
+ tui.newline();
221
+ tui.info('GitHub repository linked. Continuing with deployment...');
222
+ tui.newline();
223
+ }
224
+ } else {
225
+ await updateProjectConfig(projectDir, { skipGitSetup: true }, config);
226
+ tui.newline();
227
+ tui.info(
228
+ `Skipping GitHub setup. Run ${tui.bold(getCommand('git link'))} later to enable it.`
229
+ );
230
+ tui.newline();
231
+ }
232
+ }
233
+ } catch (err) {
234
+ // Re-throw intentional cancellations
235
+ if (err instanceof DeploymentCancelledError) {
236
+ throw err;
237
+ }
238
+ // Log other errors as non-fatal and continue
239
+ logger.trace('Failed to check GitHub status: %s', err);
240
+ }
241
+ }
242
+
166
243
  await runSteps(
167
244
  [
168
245
  !project.deployment?.domains?.length
@@ -4,6 +4,12 @@ import * as tui from '../../../tui';
4
4
  import { createSandboxClient, parseFileArgs } from './util';
5
5
  import { getCommand } from '../../../command-prefix';
6
6
  import { sandboxCreate } from '@agentuity/server';
7
+ import { StructuredError } from '@agentuity/core';
8
+
9
+ const InvalidMetadataError = StructuredError(
10
+ 'InvalidMetadataError',
11
+ 'Metadata must be a valid JSON object'
12
+ );
7
13
 
8
14
  const SandboxCreateResponseSchema = z.object({
9
15
  sandboxId: z.string().describe('Unique sandbox identifier'),
@@ -55,6 +61,7 @@ export const createSubcommand = createCommand({
55
61
  .array(z.string())
56
62
  .optional()
57
63
  .describe('Apt packages to install (can be specified multiple times)'),
64
+ metadata: z.string().optional().describe('JSON object of user-defined metadata'),
58
65
  }),
59
66
  response: SandboxCreateResponseSchema,
60
67
  },
@@ -77,6 +84,20 @@ export const createSubcommand = createCommand({
77
84
  const files = parseFileArgs(opts.file);
78
85
  const hasFiles = files.length > 0;
79
86
 
87
+ let metadata: Record<string, unknown> | undefined;
88
+ if (opts.metadata) {
89
+ let parsed: unknown;
90
+ try {
91
+ parsed = JSON.parse(opts.metadata);
92
+ } catch {
93
+ throw new InvalidMetadataError();
94
+ }
95
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
96
+ throw new InvalidMetadataError();
97
+ }
98
+ metadata = parsed as Record<string, unknown>;
99
+ }
100
+
80
101
  const result = await sandboxCreate(client, {
81
102
  options: {
82
103
  resources:
@@ -93,6 +114,7 @@ export const createSubcommand = createCommand({
93
114
  command: hasFiles ? { exec: [], files } : undefined,
94
115
  snapshot: opts.snapshot,
95
116
  dependencies: opts.dependency,
117
+ metadata,
96
118
  },
97
119
  orgId,
98
120
  });
@@ -14,7 +14,7 @@ const SandboxDeleteResponseSchema = z.object({
14
14
 
15
15
  export const deleteSubcommand = createCommand({
16
16
  name: 'delete',
17
- aliases: ['del', 'rm', 'remove', 'destroy'],
17
+ aliases: ['del', 'remove', 'destroy'],
18
18
  description: 'Delete a sandbox',
19
19
  tags: ['destructive', 'deletes-resource', 'slow', 'requires-auth'],
20
20
  requires: { auth: true, region: true, org: true },
@@ -25,11 +25,7 @@ export const deleteSubcommand = createCommand({
25
25
  description: 'Delete a sandbox',
26
26
  },
27
27
  {
28
- command: getCommand('cloud sandbox rm abc123'),
29
- description: 'Delete using alias',
30
- },
31
- {
32
- command: getCommand('cloud sandbox rm abc123 --confirm'),
28
+ command: getCommand('cloud sandbox delete abc123 --confirm'),
33
29
  description: 'Delete without confirmation prompt',
34
30
  },
35
31
  ],
@@ -0,0 +1,96 @@
1
+ import { z } from 'zod';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { createCommand } from '../../../types';
4
+ import * as tui from '../../../tui';
5
+ import { createSandboxClient } from './util';
6
+ import { getCommand } from '../../../command-prefix';
7
+ import { sandboxDownloadArchive } from '@agentuity/server';
8
+
9
+ export const downloadSubcommand = createCommand({
10
+ name: 'download',
11
+ aliases: ['dl'],
12
+ description: 'Download files from a sandbox as a compressed archive',
13
+ tags: ['slow', 'requires-auth'],
14
+ requires: { auth: true, region: true, org: true },
15
+ examples: [
16
+ {
17
+ command: getCommand('cloud sandbox download sbx_abc123 ./backup.tar.gz'),
18
+ description: 'Download sandbox files as tar.gz archive',
19
+ },
20
+ {
21
+ command: getCommand('cloud sandbox download sbx_abc123 ./backup.zip --format zip'),
22
+ description: 'Download sandbox files as zip archive',
23
+ },
24
+ {
25
+ command: getCommand('cloud sandbox download sbx_abc123 ./backup.tar.gz --path /subdir'),
26
+ description: 'Download only a specific directory',
27
+ },
28
+ ],
29
+ schema: {
30
+ args: z.object({
31
+ sandboxId: z.string().describe('The sandbox ID'),
32
+ output: z.string().describe('Output file path for the archive'),
33
+ }),
34
+ options: z.object({
35
+ path: z.string().optional().describe('Path in sandbox to download (defaults to root)'),
36
+ format: z
37
+ .enum(['zip', 'tar.gz'])
38
+ .default('tar.gz')
39
+ .optional()
40
+ .describe('Archive format (zip or tar.gz)'),
41
+ }),
42
+ response: z.object({
43
+ success: z.boolean(),
44
+ output: z.string(),
45
+ bytes: z.number(),
46
+ }),
47
+ },
48
+
49
+ async handler(ctx) {
50
+ const { args, opts, options, auth, region, logger, orgId } = ctx;
51
+
52
+ const client = createSandboxClient(logger, auth, region);
53
+ const format = opts.format || 'tar.gz';
54
+
55
+ const stream = await sandboxDownloadArchive(client, {
56
+ sandboxId: args.sandboxId,
57
+ path: opts.path || '.',
58
+ format,
59
+ orgId,
60
+ });
61
+
62
+ const chunks: Uint8Array[] = [];
63
+ const reader = stream.getReader();
64
+
65
+ while (true) {
66
+ const { done, value } = await reader.read();
67
+ if (done) break;
68
+ chunks.push(value);
69
+ }
70
+
71
+ const totalBytes = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
72
+ const buffer = new Uint8Array(totalBytes);
73
+ let offset = 0;
74
+ for (const chunk of chunks) {
75
+ buffer.set(chunk, offset);
76
+ offset += chunk.length;
77
+ }
78
+
79
+ writeFileSync(args.output, buffer);
80
+
81
+ if (!options.json) {
82
+ tui.success(`Downloaded ${formatSize(totalBytes)} to ${args.output}`);
83
+ }
84
+
85
+ return { success: true, output: args.output, bytes: totalBytes };
86
+ },
87
+ });
88
+
89
+ function formatSize(bytes: number): string {
90
+ if (bytes < 1024) return `${bytes} bytes`;
91
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
92
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
93
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
94
+ }
95
+
96
+ export default downloadSubcommand;