@agentuity/cli 0.0.53 → 0.0.55

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 (137) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +66 -8
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cmd/auth/ssh/add.d.ts.map +1 -1
  5. package/dist/cmd/auth/ssh/add.js +28 -13
  6. package/dist/cmd/auth/ssh/add.js.map +1 -1
  7. package/dist/cmd/auth/ssh/delete.d.ts.map +1 -1
  8. package/dist/cmd/auth/ssh/delete.js +28 -18
  9. package/dist/cmd/auth/ssh/delete.js.map +1 -1
  10. package/dist/cmd/auth/ssh/list.d.ts.map +1 -1
  11. package/dist/cmd/auth/ssh/list.js +5 -6
  12. package/dist/cmd/auth/ssh/list.js.map +1 -1
  13. package/dist/cmd/build/ast.d.ts +34 -0
  14. package/dist/cmd/build/ast.d.ts.map +1 -1
  15. package/dist/cmd/build/ast.js +165 -5
  16. package/dist/cmd/build/ast.js.map +1 -1
  17. package/dist/cmd/build/bundler.d.ts +2 -1
  18. package/dist/cmd/build/bundler.d.ts.map +1 -1
  19. package/dist/cmd/build/bundler.js +78 -17
  20. package/dist/cmd/build/bundler.js.map +1 -1
  21. package/dist/cmd/build/index.d.ts.map +1 -1
  22. package/dist/cmd/build/index.js +2 -0
  23. package/dist/cmd/build/index.js.map +1 -1
  24. package/dist/cmd/build/plugin.d.ts.map +1 -1
  25. package/dist/cmd/build/plugin.js +75 -7
  26. package/dist/cmd/build/plugin.js.map +1 -1
  27. package/dist/cmd/cloud/deployment/list.d.ts.map +1 -1
  28. package/dist/cmd/cloud/deployment/list.js +28 -23
  29. package/dist/cmd/cloud/deployment/list.js.map +1 -1
  30. package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
  31. package/dist/cmd/cloud/deployment/show.js +50 -47
  32. package/dist/cmd/cloud/deployment/show.js.map +1 -1
  33. package/dist/cmd/cloud/env/get.d.ts.map +1 -1
  34. package/dist/cmd/cloud/env/get.js +16 -14
  35. package/dist/cmd/cloud/env/get.js.map +1 -1
  36. package/dist/cmd/cloud/env/list.d.ts.map +1 -1
  37. package/dist/cmd/cloud/env/list.js +24 -20
  38. package/dist/cmd/cloud/env/list.js.map +1 -1
  39. package/dist/cmd/cloud/keyvalue/get.d.ts.map +1 -1
  40. package/dist/cmd/cloud/keyvalue/get.js +18 -16
  41. package/dist/cmd/cloud/keyvalue/get.js.map +1 -1
  42. package/dist/cmd/cloud/keyvalue/keys.d.ts.map +1 -1
  43. package/dist/cmd/cloud/keyvalue/keys.js +11 -11
  44. package/dist/cmd/cloud/keyvalue/keys.js.map +1 -1
  45. package/dist/cmd/cloud/keyvalue/list-namespaces.d.ts.map +1 -1
  46. package/dist/cmd/cloud/keyvalue/list-namespaces.js +11 -7
  47. package/dist/cmd/cloud/keyvalue/list-namespaces.js.map +1 -1
  48. package/dist/cmd/cloud/keyvalue/search.d.ts.map +1 -1
  49. package/dist/cmd/cloud/keyvalue/search.js +16 -17
  50. package/dist/cmd/cloud/keyvalue/search.js.map +1 -1
  51. package/dist/cmd/cloud/keyvalue/stats.d.ts.map +1 -1
  52. package/dist/cmd/cloud/keyvalue/stats.js +38 -23
  53. package/dist/cmd/cloud/keyvalue/stats.js.map +1 -1
  54. package/dist/cmd/cloud/objectstore/get.d.ts.map +1 -1
  55. package/dist/cmd/cloud/objectstore/get.js +17 -15
  56. package/dist/cmd/cloud/objectstore/get.js.map +1 -1
  57. package/dist/cmd/cloud/objectstore/list-buckets.d.ts.map +1 -1
  58. package/dist/cmd/cloud/objectstore/list-buckets.js +12 -8
  59. package/dist/cmd/cloud/objectstore/list-buckets.js.map +1 -1
  60. package/dist/cmd/cloud/objectstore/list-keys.d.ts.map +1 -1
  61. package/dist/cmd/cloud/objectstore/list-keys.js +13 -10
  62. package/dist/cmd/cloud/objectstore/list-keys.js.map +1 -1
  63. package/dist/cmd/cloud/resource/list.d.ts.map +1 -1
  64. package/dist/cmd/cloud/resource/list.js +38 -27
  65. package/dist/cmd/cloud/resource/list.js.map +1 -1
  66. package/dist/cmd/cloud/secret/get.d.ts.map +1 -1
  67. package/dist/cmd/cloud/secret/get.js +17 -15
  68. package/dist/cmd/cloud/secret/get.js.map +1 -1
  69. package/dist/cmd/cloud/secret/list.d.ts.map +1 -1
  70. package/dist/cmd/cloud/secret/list.js +24 -20
  71. package/dist/cmd/cloud/secret/list.js.map +1 -1
  72. package/dist/cmd/cloud/session/logs.d.ts.map +1 -1
  73. package/dist/cmd/cloud/session/logs.js +18 -15
  74. package/dist/cmd/cloud/session/logs.js.map +1 -1
  75. package/dist/cmd/dev/agents.d.ts.map +1 -1
  76. package/dist/cmd/dev/agents.js +55 -41
  77. package/dist/cmd/dev/agents.js.map +1 -1
  78. package/dist/cmd/dev/index.d.ts.map +1 -1
  79. package/dist/cmd/dev/index.js +34 -14
  80. package/dist/cmd/dev/index.js.map +1 -1
  81. package/dist/cmd/profile/create.js +1 -1
  82. package/dist/cmd/profile/create.js.map +1 -1
  83. package/dist/cmd/profile/delete.d.ts.map +1 -1
  84. package/dist/cmd/profile/delete.js +1 -1
  85. package/dist/cmd/profile/delete.js.map +1 -1
  86. package/dist/cmd/profile/list.d.ts.map +1 -1
  87. package/dist/cmd/profile/list.js +29 -11
  88. package/dist/cmd/profile/list.js.map +1 -1
  89. package/dist/cmd/profile/show.d.ts.map +1 -1
  90. package/dist/cmd/profile/show.js +7 -10
  91. package/dist/cmd/profile/show.js.map +1 -1
  92. package/dist/cmd/project/delete.js +1 -1
  93. package/dist/cmd/project/delete.js.map +1 -1
  94. package/dist/cmd/version/index.d.ts.map +1 -1
  95. package/dist/cmd/version/index.js +1 -1
  96. package/dist/cmd/version/index.js.map +1 -1
  97. package/dist/tui.d.ts.map +1 -1
  98. package/dist/tui.js +3 -1
  99. package/dist/tui.js.map +1 -1
  100. package/dist/types.d.ts +32 -8
  101. package/dist/types.d.ts.map +1 -1
  102. package/dist/types.js.map +1 -1
  103. package/package.json +3 -3
  104. package/src/cli.ts +109 -8
  105. package/src/cmd/auth/ssh/add.ts +37 -17
  106. package/src/cmd/auth/ssh/delete.ts +36 -23
  107. package/src/cmd/auth/ssh/list.ts +8 -6
  108. package/src/cmd/build/ast.ts +209 -5
  109. package/src/cmd/build/bundler.ts +82 -16
  110. package/src/cmd/build/index.ts +2 -0
  111. package/src/cmd/build/plugin.ts +93 -7
  112. package/src/cmd/cloud/deployment/list.ts +30 -26
  113. package/src/cmd/cloud/deployment/show.ts +47 -42
  114. package/src/cmd/cloud/env/get.ts +14 -12
  115. package/src/cmd/cloud/env/list.ts +24 -22
  116. package/src/cmd/cloud/keyvalue/get.ts +19 -14
  117. package/src/cmd/cloud/keyvalue/keys.ts +10 -12
  118. package/src/cmd/cloud/keyvalue/list-namespaces.ts +10 -8
  119. package/src/cmd/cloud/keyvalue/search.ts +14 -17
  120. package/src/cmd/cloud/keyvalue/stats.ts +52 -28
  121. package/src/cmd/cloud/objectstore/get.ts +18 -13
  122. package/src/cmd/cloud/objectstore/list-buckets.ts +11 -9
  123. package/src/cmd/cloud/objectstore/list-keys.ts +12 -11
  124. package/src/cmd/cloud/resource/list.ts +33 -23
  125. package/src/cmd/cloud/secret/get.ts +15 -13
  126. package/src/cmd/cloud/secret/list.ts +24 -22
  127. package/src/cmd/cloud/session/logs.ts +18 -17
  128. package/src/cmd/dev/agents.ts +70 -50
  129. package/src/cmd/dev/index.ts +41 -14
  130. package/src/cmd/profile/create.ts +3 -3
  131. package/src/cmd/profile/delete.ts +5 -2
  132. package/src/cmd/profile/list.ts +31 -11
  133. package/src/cmd/profile/show.ts +15 -12
  134. package/src/cmd/project/delete.ts +1 -1
  135. package/src/cmd/version/index.ts +5 -1
  136. package/src/tui.ts +3 -1
  137. package/src/types.ts +32 -10
@@ -126,7 +126,7 @@ function getAgentId(
126
126
  filename: string,
127
127
  version: string
128
128
  ): string {
129
- return `agent_${hashSHA1(projectId, deploymentId, filename, version)}`;
129
+ return `agentid_${hashSHA1(projectId, deploymentId, filename, version)}`;
130
130
  }
131
131
 
132
132
  function getEvalId(
@@ -152,7 +152,7 @@ function generateRouteId(
152
152
  }
153
153
 
154
154
  function generateStableAgentId(projectId: string, name: string): string {
155
- return `agentid_${hashSHA1(projectId, name)}`.substring(0, 64);
155
+ return `agent_${hashSHA1(projectId, name)}`.substring(0, 64);
156
156
  }
157
157
 
158
158
  function generateStableEvalId(projectId: string, agentId: string, name: string): string {
@@ -164,7 +164,7 @@ type AcornParseResultType = ReturnType<typeof acornLoose.parse>;
164
164
  function augmentAgentMetadataNode(
165
165
  projectId: string,
166
166
  id: string,
167
- name: string,
167
+ identifier: string,
168
168
  rel: string,
169
169
  version: string,
170
170
  ast: AcornParseResultType,
@@ -178,7 +178,8 @@ function augmentAgentMetadataNode(
178
178
  `missing required metadata.name in ${filename}${location}. This Agent should have a unique and human readable name for this project.`
179
179
  );
180
180
  }
181
- if (metadata.has('identifier') && name !== metadata.get('identifier')) {
181
+ const name = metadata.get('name')!;
182
+ if (metadata.has('identifier') && identifier !== metadata.get('identifier')) {
182
183
  const location = ast.loc?.start ? ` on line ${ast.loc.start}` : '';
183
184
  throw new Error(
184
185
  `metadata.identifier (${metadata.get('identifier')}) in ${filename}${location} is mismatched (${name}). This is an internal error.`
@@ -188,7 +189,7 @@ function augmentAgentMetadataNode(
188
189
  const description = descriptionNode ? (descriptionNode as ASTLiteral).value : '';
189
190
  const agentId = generateStableAgentId(projectId, name);
190
191
  metadata.set('version', version);
191
- metadata.set('identifier', name);
192
+ metadata.set('identifier', identifier);
192
193
  metadata.set('filename', rel);
193
194
  metadata.set('id', id);
194
195
  metadata.set('agentId', agentId);
@@ -897,3 +898,206 @@ export async function parseRoute(
897
898
  }
898
899
  return routes;
899
900
  }
901
+
902
+ /**
903
+ * Configuration extracted from createWorkbench call
904
+ */
905
+ export interface WorkbenchConfig {
906
+ route: string;
907
+ headers?: Record<string, string>;
908
+ }
909
+
910
+ /**
911
+ * Result of workbench analysis
912
+ */
913
+ export interface WorkbenchAnalysis {
914
+ hasWorkbench: boolean;
915
+ config: WorkbenchConfig | null;
916
+ }
917
+
918
+ /**
919
+ * Check if a TypeScript file actively uses a specific function
920
+ * (ignores comments and unused imports)
921
+ *
922
+ * @param content - The TypeScript source code
923
+ * @param functionName - The function name to check for (e.g., 'createWorkbench')
924
+ * @returns true if the function is both imported and called
925
+ */
926
+ export async function checkFunctionUsage(content: string, functionName: string): Promise<boolean> {
927
+ try {
928
+ const ts = await import('typescript');
929
+ const sourceFile = ts.createSourceFile('temp.ts', content, ts.ScriptTarget.Latest, true);
930
+
931
+ let hasImport = false;
932
+ let hasUsage = false;
933
+
934
+ function visitNode(node: import('typescript').Node): void {
935
+ // Check for import declarations with the function
936
+ if (ts.isImportDeclaration(node) && node.importClause?.namedBindings) {
937
+ if (ts.isNamedImports(node.importClause.namedBindings)) {
938
+ for (const element of node.importClause.namedBindings.elements) {
939
+ if (element.name.text === functionName) {
940
+ hasImport = true;
941
+ }
942
+ }
943
+ }
944
+ }
945
+ // Check for function calls
946
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
947
+ if (node.expression.text === functionName) {
948
+ hasUsage = true;
949
+ }
950
+ }
951
+ // Recursively visit child nodes
952
+ ts.forEachChild(node, visitNode);
953
+ }
954
+
955
+ visitNode(sourceFile);
956
+ // Only return true if both import and usage are present
957
+ return hasImport && hasUsage;
958
+ } catch (error) {
959
+ // Fallback to string check if AST parsing fails
960
+ console.warn(`AST parsing failed for ${functionName}, falling back to string check:`, error);
961
+ return content.includes(functionName);
962
+ }
963
+ }
964
+
965
+ /**
966
+ * Check if app.ts contains conflicting routes for a given endpoint
967
+ */
968
+ export async function checkRouteConflicts(
969
+ content: string,
970
+ workbenchEndpoint: string
971
+ ): Promise<boolean> {
972
+ try {
973
+ const ts = await import('typescript');
974
+ const sourceFile = ts.createSourceFile('app.ts', content, ts.ScriptTarget.Latest, true);
975
+
976
+ let hasConflict = false;
977
+
978
+ function visitNode(node: import('typescript').Node): void {
979
+ // Check for router.get calls
980
+ if (
981
+ ts.isCallExpression(node) &&
982
+ ts.isPropertyAccessExpression(node.expression) &&
983
+ ts.isIdentifier(node.expression.name) &&
984
+ node.expression.name.text === 'get'
985
+ ) {
986
+ // Check if first argument is the workbench endpoint
987
+ if (node.arguments.length > 0 && ts.isStringLiteral(node.arguments[0])) {
988
+ if (node.arguments[0].text === workbenchEndpoint) {
989
+ hasConflict = true;
990
+ }
991
+ }
992
+ }
993
+
994
+ ts.forEachChild(node, visitNode);
995
+ }
996
+
997
+ visitNode(sourceFile);
998
+ return hasConflict;
999
+ } catch (_error) {
1000
+ return false;
1001
+ }
1002
+ }
1003
+
1004
+ /**
1005
+ * Analyze workbench usage and extract configuration
1006
+ *
1007
+ * @param content - The TypeScript source code
1008
+ * @returns workbench analysis including usage and config
1009
+ */
1010
+ export async function analyzeWorkbench(content: string): Promise<WorkbenchAnalysis> {
1011
+ try {
1012
+ const ts = await import('typescript');
1013
+ const sourceFile = ts.createSourceFile('app.ts', content, ts.ScriptTarget.Latest, true);
1014
+
1015
+ let hasImport = false;
1016
+ let hasUsage = false;
1017
+ let config: WorkbenchConfig | null = null;
1018
+
1019
+ function visitNode(node: import('typescript').Node): void {
1020
+ // Check for import declarations with createWorkbench
1021
+ if (ts.isImportDeclaration(node) && node.importClause?.namedBindings) {
1022
+ if (ts.isNamedImports(node.importClause.namedBindings)) {
1023
+ for (const element of node.importClause.namedBindings.elements) {
1024
+ if (element.name.text === 'createWorkbench') {
1025
+ hasImport = true;
1026
+ }
1027
+ }
1028
+ }
1029
+ }
1030
+
1031
+ // Check for createWorkbench function calls and extract config
1032
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
1033
+ if (node.expression.text === 'createWorkbench') {
1034
+ hasUsage = true;
1035
+
1036
+ // Extract configuration from the first argument (if any)
1037
+ if (node.arguments.length > 0) {
1038
+ const configArg = node.arguments[0];
1039
+ config = parseConfigObject(configArg, ts);
1040
+ } else {
1041
+ // Default config if no arguments provided
1042
+ config = { route: '/workbench' };
1043
+ }
1044
+ }
1045
+ }
1046
+
1047
+ // Recursively visit child nodes
1048
+ ts.forEachChild(node, visitNode);
1049
+ }
1050
+
1051
+ visitNode(sourceFile);
1052
+
1053
+ // Set default config if workbench is used but no config was parsed
1054
+ if (hasImport && hasUsage && !config) {
1055
+ config = { route: '/workbench' };
1056
+ }
1057
+
1058
+ return {
1059
+ hasWorkbench: hasImport && hasUsage,
1060
+ config: config,
1061
+ };
1062
+ } catch (error) {
1063
+ // Fallback to simple check if AST parsing fails
1064
+ console.warn('Workbench AST parsing failed, falling back to string check:', error);
1065
+ const hasWorkbench = content.includes('createWorkbench');
1066
+ return {
1067
+ hasWorkbench,
1068
+ config: hasWorkbench ? { route: '/workbench' } : null,
1069
+ };
1070
+ }
1071
+ }
1072
+
1073
+ /**
1074
+ * Parse a TypeScript object literal to extract configuration
1075
+ */
1076
+ function parseConfigObject(
1077
+ node: import('typescript').Node,
1078
+ ts: typeof import('typescript')
1079
+ ): WorkbenchConfig | null {
1080
+ if (!ts.isObjectLiteralExpression(node)) {
1081
+ return { route: '/workbench' }; // Default config
1082
+ }
1083
+
1084
+ const config: WorkbenchConfig = { route: '/workbench' };
1085
+
1086
+ for (const property of node.properties) {
1087
+ if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name)) {
1088
+ const propertyName = property.name.text;
1089
+
1090
+ if (propertyName === 'route' && ts.isStringLiteral(property.initializer)) {
1091
+ config.route = property.initializer.text;
1092
+ } else if (
1093
+ propertyName === 'headers' &&
1094
+ ts.isObjectLiteralExpression(property.initializer)
1095
+ ) {
1096
+ // Parse headers object if needed (not implemented for now)
1097
+ config.headers = {};
1098
+ }
1099
+ }
1100
+ }
1101
+
1102
+ return config;
1103
+ }
@@ -1,5 +1,6 @@
1
1
  import { $ } from 'bun';
2
2
  import { join, relative, resolve, dirname } from 'node:path';
3
+ import { createRequire } from 'node:module';
3
4
  import { cpSync, existsSync, mkdirSync, rmSync } from 'node:fs';
4
5
  import gitParseUrl from 'git-url-parse';
5
6
  import AgentuityBundler, { getBuildMetadata } from './plugin';
@@ -7,6 +8,8 @@ import { getFilesRecursively } from './file';
7
8
  import { getVersion } from '../../version';
8
9
  import type { Project } from '../../types';
9
10
  import { fixDuplicateExportsInDirectory } from './fix-duplicate-exports';
11
+ import { createLogger } from '@agentuity/server';
12
+ import type { LogLevel } from '../../types';
10
13
 
11
14
  export interface BundleOptions {
12
15
  rootDir: string;
@@ -16,6 +19,7 @@ export interface BundleOptions {
16
19
  projectId?: string;
17
20
  deploymentId?: string;
18
21
  project?: Project;
22
+ port?: number;
19
23
  }
20
24
 
21
25
  export async function bundle({
@@ -25,6 +29,7 @@ export async function bundle({
25
29
  dev = false,
26
30
  rootDir,
27
31
  project,
32
+ port,
28
33
  }: BundleOptions) {
29
34
  const appFile = join(rootDir, 'app.ts');
30
35
  if (!existsSync(appFile)) {
@@ -45,7 +50,7 @@ export async function bundle({
45
50
  }
46
51
  const files = await getFilesRecursively(dir);
47
52
  for (const filename of files) {
48
- if (/\.[jt]s?$/.test(filename)) {
53
+ if (/\.[jt]s?$/.test(filename) && !filename.includes('.generated.')) {
49
54
  appEntrypoints.push(filename);
50
55
  }
51
56
  }
@@ -246,26 +251,87 @@ export async function bundle({
246
251
  }
247
252
  }
248
253
  })();
254
+ }
249
255
 
250
- if (!dev && buildmetadata) {
251
- const webPublicDir = join(webDir, 'public');
252
- if (existsSync(webPublicDir)) {
253
- const assets = buildmetadata.assets;
254
- const webOutPublicDir = join(outDir, 'web', 'public');
255
- cpSync(webPublicDir, webOutPublicDir, { recursive: true });
256
- [...new Bun.Glob('**.*').scanSync(webOutPublicDir)].forEach((f) => {
257
- const bf = Bun.file(join(webOutPublicDir, f));
258
- assets.push({
259
- filename: join('public', f),
260
- kind: 'static',
261
- contentType: bf.type,
262
- size: bf.size,
263
- });
264
- });
256
+ // Bundle workbench app if detected via setupWorkbench
257
+ const { analyzeWorkbench } = await import('./ast');
258
+ if (existsSync(appFile)) {
259
+ const appContent = await Bun.file(appFile).text();
260
+ const analysis = await analyzeWorkbench(appContent);
261
+
262
+ if (analysis.hasWorkbench) {
263
+ // Encode workbench config for environment variable
264
+ const { encodeWorkbenchConfig } = await import('@agentuity/core');
265
+ const config = analysis.config || { route: '/workbench', headers: {} };
266
+ // Add port to config (defaults to 3500 if not provided)
267
+ const configWithPort = { ...config, port: port || 3500 };
268
+ const encodedConfig = encodeWorkbenchConfig(configWithPort);
269
+ const workbenchDefine = {
270
+ ...define,
271
+ AGENTUITY_WORKBENCH_CONFIG_INLINE: JSON.stringify(encodedConfig),
272
+ };
273
+ const logger = createLogger((process.env.AGENTUITY_LOG_LEVEL as LogLevel) || 'info');
274
+ try {
275
+ const projectRequire = createRequire(resolve(rootDir, 'package.json'));
276
+ const workbenchPkgPath = projectRequire.resolve('@agentuity/workbench/package.json');
277
+ const workbenchAppDir = join(dirname(workbenchPkgPath), 'src', 'app');
278
+
279
+ if (existsSync(workbenchAppDir)) {
280
+ const workbenchIndexFile = join(workbenchAppDir, 'index.html');
281
+ if (existsSync(workbenchIndexFile)) {
282
+ // Bundle workbench using same config as main web app
283
+ const workbenchBuildConfig: Bun.BuildConfig = {
284
+ entrypoints: [workbenchIndexFile],
285
+ root: workbenchAppDir,
286
+ outdir: join(outDir, 'workbench'),
287
+ define: workbenchDefine,
288
+ sourcemap: dev ? 'inline' : 'linked',
289
+ plugins: [AgentuityBundler],
290
+ target: 'browser',
291
+ format: 'esm',
292
+ banner: `// Generated file. DO NOT EDIT`,
293
+ minify: true,
294
+ splitting: true,
295
+ packages: 'bundle',
296
+ naming: {
297
+ entry: '[dir]/[name].[ext]',
298
+ chunk: 'workbench/chunk/[name]-[hash].[ext]',
299
+ asset: 'workbench/asset/[name]-[hash].[ext]',
300
+ },
301
+ };
302
+
303
+ const workbenchResult = await Bun.build(workbenchBuildConfig);
304
+ if (workbenchResult.success) {
305
+ logger.debug('Workbench bundled successfully');
306
+ } else {
307
+ logger.error('Workbench bundling failed:', workbenchResult.logs.join('\n'));
308
+ }
309
+ }
310
+ }
311
+ } catch (error) {
312
+ logger.error('Failed to bundle workbench:', error);
265
313
  }
266
314
  }
267
315
  }
268
316
 
317
+ if (!dev && buildmetadata) {
318
+ const webPublicDir = join(webDir, 'public');
319
+ if (existsSync(webPublicDir)) {
320
+ const assets = buildmetadata.assets;
321
+ const webOutPublicDir = join(outDir, 'web', 'public');
322
+ cpSync(webPublicDir, webOutPublicDir, { recursive: true });
323
+ [...new Bun.Glob('**.*').scanSync(webOutPublicDir)].forEach((f) => {
324
+ const bf = Bun.file(join(webOutPublicDir, f));
325
+ assets.push({
326
+ filename: join('public', f),
327
+ kind: 'static',
328
+ contentType: bf.type,
329
+ size: bf.size,
330
+ });
331
+ });
332
+ }
333
+ }
334
+
269
335
  if (!dev && Bun.which('git') && buildmetadata?.deployment) {
270
336
  buildmetadata.deployment.git = {
271
337
  commit: process.env.GIT_SHA || process.env.GITHUB_SHA,
@@ -39,6 +39,8 @@ export const command = createCommand({
39
39
  rootDir: projectDir,
40
40
  dev: opts.dev || false,
41
41
  project,
42
+ orgId: project?.orgId,
43
+ projectId: project?.projectId,
42
44
  });
43
45
 
44
46
  // Run TypeScript type checking after registry generation (skip in dev mode)
@@ -2,7 +2,14 @@ import type { BunPlugin } from 'bun';
2
2
  import { dirname, basename, join, resolve } from 'node:path';
3
3
  import { existsSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';
4
4
  import type { BuildMetadata } from '@agentuity/server';
5
- import { parseAgentMetadata, parseRoute, parseEvalMetadata } from './ast';
5
+ import {
6
+ parseAgentMetadata,
7
+ parseRoute,
8
+ parseEvalMetadata,
9
+ analyzeWorkbench,
10
+ checkRouteConflicts,
11
+ type WorkbenchConfig,
12
+ } from './ast';
6
13
  import { applyPatch, generatePatches } from './patch';
7
14
  import { detectSubagent } from '../../utils/detectSubagent';
8
15
  import { createLogger } from '@agentuity/server';
@@ -19,6 +26,50 @@ function toPascalCase(str: string): string {
19
26
  return camel.charAt(0).toUpperCase() + camel.slice(1);
20
27
  }
21
28
 
29
+ /**
30
+ * Setup workbench configuration by analyzing app.ts file
31
+ */
32
+ async function setupWorkbench(srcDir: string): Promise<WorkbenchConfig | null> {
33
+ // Look for app.ts in both root and src directories
34
+ const rootAppFile = join(dirname(srcDir), 'app.ts');
35
+ const srcAppFile = join(srcDir, 'app.ts');
36
+
37
+ let appFile = '';
38
+ if (existsSync(rootAppFile)) {
39
+ appFile = rootAppFile;
40
+ } else if (existsSync(srcAppFile)) {
41
+ appFile = srcAppFile;
42
+ }
43
+
44
+ if (!appFile || !existsSync(appFile)) {
45
+ return null;
46
+ }
47
+
48
+ const appContent = await Bun.file(appFile).text();
49
+ const analysis = await analyzeWorkbench(appContent);
50
+
51
+ if (!analysis.hasWorkbench) {
52
+ return null;
53
+ }
54
+
55
+ const workbenchConfig = analysis.config;
56
+
57
+ // Check for route conflicts if workbench is being used
58
+ if (workbenchConfig?.route) {
59
+ const hasConflict = await checkRouteConflicts(appContent, workbenchConfig.route);
60
+ if (hasConflict) {
61
+ const logger = createLogger((process.env.AGENTUITY_LOG_LEVEL as LogLevel) || 'info');
62
+ logger.error(`🚨 Route conflict detected!\n`);
63
+ logger.error(
64
+ ` Workbench route '${workbenchConfig.route}' conflicts with existing application route`
65
+ );
66
+ logger.error(` Please use a different route or remove the conflicting route.\n`);
67
+ }
68
+ }
69
+
70
+ return workbenchConfig;
71
+ }
72
+
22
73
  function generateAgentRegistry(srcDir: string, agentInfo: Array<Record<string, string>>) {
23
74
  // Separate parent agents and subagents
24
75
  const parentAgents = agentInfo.filter((a) => !a.parent);
@@ -226,7 +277,6 @@ ${typeExports}
226
277
  // Augment @agentuity/runtime types with strongly-typed agents from this project
227
278
  declare module "@agentuity/runtime" {
228
279
  // Augment the AgentRegistry interface with project-specific strongly-typed agents
229
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
230
280
  export interface AgentRegistry {
231
281
  ${runtimeAgentTypes}
232
282
  }
@@ -291,6 +341,10 @@ const AgentuityBundler: BunPlugin = {
291
341
  const deploymentId = build.config.define?.['process.env.AGENTUITY_CLOUD_DEPLOYMENT_ID']
292
342
  ? JSON.parse(build.config.define['process.env.AGENTUITY_CLOUD_DEPLOYMENT_ID'])
293
343
  : '';
344
+ const isDevMode =
345
+ (build.config.define?.['process.env.NODE_ENV']
346
+ ? JSON.parse(build.config.define['process.env.NODE_ENV'])
347
+ : 'production') === 'development';
294
348
  const routes: Set<string> = new Set();
295
349
  const agentInfo: Array<Record<string, string>> = [];
296
350
  const agentMetadata: Map<string, Map<string, string>> = new Map<
@@ -493,11 +547,45 @@ const AgentuityBundler: BunPlugin = {
493
547
  const indexFile = join(srcDir, 'web', 'index.html');
494
548
 
495
549
  if (existsSync(indexFile)) {
550
+ // Setup workbench configuration - evaluate fresh each time during builds
551
+ const workbenchConfig = await setupWorkbench(srcDir);
552
+
496
553
  inserts.unshift(`await (async () => {
497
554
  const { serveStatic } = require('hono/bun');
498
- const { getRouter } = await import('@agentuity/runtime');
555
+ const { getRouter, registerDevModeRoutes } = await import('@agentuity/runtime');
499
556
  const router = getRouter()!;
500
- const index = await Bun.file(import.meta.dir + '/web/index.html').text();
557
+
558
+ // Setup workbench routes if workbench was bundled
559
+ const workbenchIndexPath = import.meta.dir + '/workbench/index.html';
560
+ if (await Bun.file(workbenchIndexPath).exists()) {
561
+ let workbenchIndex = await Bun.file(workbenchIndexPath).text();
562
+
563
+ // Always serve assets at /workbench/* regardless of HTML route
564
+ const workbenchStatic = serveStatic({ root: import.meta.dir + '/workbench' });
565
+ router.get('/workbench/*', workbenchStatic);
566
+
567
+ // Use the workbench config determined at build time
568
+ const route = ${JSON.stringify(workbenchConfig?.route || '/workbench')};
569
+
570
+ // If using custom route, update HTML to point to absolute /workbench/ paths
571
+ if (route !== '/workbench') {
572
+ workbenchIndex = workbenchIndex.replace(new RegExp('src="\\\\.\\\\/workbench\\\\/', 'g'), 'src="/workbench/');
573
+ }
574
+
575
+ // Serve HTML at the configured route
576
+ router.get(route, (c) => c.html(workbenchIndex));
577
+ }
578
+
579
+ let index = await Bun.file(import.meta.dir + '/web/index.html').text();
580
+ if (${isDevMode}) {
581
+ const end = index.lastIndexOf('</html>');
582
+ const html = registerDevModeRoutes(router);
583
+ if (end > 0) {
584
+ index = index.substring(0,end) + html + index.substring(end);
585
+ } else {
586
+ index += html;
587
+ }
588
+ }
501
589
  const webstatic = serveStatic({ root: import.meta.dir + '/web' });
502
590
  router.get('/', (c) => c.html(index));
503
591
  router.get('/web/chunk/*', webstatic);
@@ -511,9 +599,7 @@ const AgentuityBundler: BunPlugin = {
511
599
  // 1. Evals are already imported when agents are registered (see line 421-422)
512
600
  // 2. The registry is for type definitions only, not runtime execution
513
601
  // 3. Importing it causes bundler resolution issues since it's generated during build
514
- if (agentInfo.length > 0) {
515
- generateAgentRegistry(srcDir, agentInfo);
516
- }
602
+ generateAgentRegistry(srcDir, agentInfo);
517
603
 
518
604
  // create the workbench routes
519
605
  inserts.push(`await (async() => {
@@ -53,42 +53,46 @@ export const listSubcommand = createSubcommand({
53
53
  },
54
54
  async handler(ctx) {
55
55
  const projectId = resolveProjectId(ctx, { projectId: ctx.opts['project-id'] });
56
- const { apiClient, opts } = ctx;
56
+ const { apiClient, opts, options } = ctx;
57
57
 
58
58
  try {
59
59
  const deployments = await projectDeploymentList(apiClient, projectId, opts.count);
60
60
 
61
- if (deployments.length === 0) {
62
- tui.info('No deployments found.');
63
- return [];
64
- }
65
-
66
- const tableData = deployments.map((d) => ({
67
- ID: d.id,
68
- State: d.state || 'unknown',
69
- Active: d.active ? 'Yes' : '',
70
- Created: new Date(d.createdAt).toLocaleString(),
71
- Message: d.message || '',
72
- Tags: d.tags.join(', '),
73
- }));
74
-
75
- tui.table(tableData, [
76
- { name: 'ID', alignment: 'left' },
77
- { name: 'State', alignment: 'left' },
78
- { name: 'Active', alignment: 'center' },
79
- { name: 'Created', alignment: 'left' },
80
- { name: 'Message', alignment: 'left' },
81
- { name: 'Tags', alignment: 'left' },
82
- ]);
83
-
84
- return deployments.map((d) => ({
61
+ const result = deployments.map((d) => ({
85
62
  id: d.id,
86
63
  state: d.state,
87
64
  active: d.active,
88
65
  createdAt: d.createdAt,
89
- message: d.message,
66
+ message: d.message ?? undefined,
90
67
  tags: d.tags,
91
68
  }));
69
+
70
+ // Skip TUI output in JSON mode
71
+ if (!options.json) {
72
+ if (deployments.length === 0) {
73
+ tui.info('No deployments found.');
74
+ } else {
75
+ const tableData = deployments.map((d) => ({
76
+ ID: d.id,
77
+ State: d.state || 'unknown',
78
+ Active: d.active ? 'Yes' : '',
79
+ Created: new Date(d.createdAt).toLocaleString(),
80
+ Message: d.message || '',
81
+ Tags: d.tags.join(', '),
82
+ }));
83
+
84
+ tui.table(tableData, [
85
+ { name: 'ID', alignment: 'left' },
86
+ { name: 'State', alignment: 'left' },
87
+ { name: 'Active', alignment: 'center' },
88
+ { name: 'Created', alignment: 'left' },
89
+ { name: 'Message', alignment: 'left' },
90
+ { name: 'Tags', alignment: 'left' },
91
+ ]);
92
+ }
93
+ }
94
+
95
+ return result;
92
96
  } catch (ex) {
93
97
  tui.fatal(
94
98
  `Failed to list deployments: ${ex instanceof Error ? ex.message : String(ex)}`,