@agentuity/cli 0.0.52 → 0.0.54

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 +159 -0
  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 +77 -16
  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 +3 -1
  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 +158 -63
  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 +2 -0
  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 +203 -0
  109. package/src/cmd/build/bundler.ts +81 -15
  110. package/src/cmd/build/index.ts +3 -1
  111. package/src/cmd/build/plugin.ts +186 -68
  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 +2 -0
  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
@@ -897,3 +897,206 @@ export async function parseRoute(
897
897
  }
898
898
  return routes;
899
899
  }
900
+
901
+ /**
902
+ * Configuration extracted from createWorkbench call
903
+ */
904
+ export interface WorkbenchConfig {
905
+ route: string;
906
+ headers?: Record<string, string>;
907
+ }
908
+
909
+ /**
910
+ * Result of workbench analysis
911
+ */
912
+ export interface WorkbenchAnalysis {
913
+ hasWorkbench: boolean;
914
+ config: WorkbenchConfig | null;
915
+ }
916
+
917
+ /**
918
+ * Check if a TypeScript file actively uses a specific function
919
+ * (ignores comments and unused imports)
920
+ *
921
+ * @param content - The TypeScript source code
922
+ * @param functionName - The function name to check for (e.g., 'createWorkbench')
923
+ * @returns true if the function is both imported and called
924
+ */
925
+ export async function checkFunctionUsage(content: string, functionName: string): Promise<boolean> {
926
+ try {
927
+ const ts = await import('typescript');
928
+ const sourceFile = ts.createSourceFile('temp.ts', content, ts.ScriptTarget.Latest, true);
929
+
930
+ let hasImport = false;
931
+ let hasUsage = false;
932
+
933
+ function visitNode(node: import('typescript').Node): void {
934
+ // Check for import declarations with the function
935
+ if (ts.isImportDeclaration(node) && node.importClause?.namedBindings) {
936
+ if (ts.isNamedImports(node.importClause.namedBindings)) {
937
+ for (const element of node.importClause.namedBindings.elements) {
938
+ if (element.name.text === functionName) {
939
+ hasImport = true;
940
+ }
941
+ }
942
+ }
943
+ }
944
+ // Check for function calls
945
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
946
+ if (node.expression.text === functionName) {
947
+ hasUsage = true;
948
+ }
949
+ }
950
+ // Recursively visit child nodes
951
+ ts.forEachChild(node, visitNode);
952
+ }
953
+
954
+ visitNode(sourceFile);
955
+ // Only return true if both import and usage are present
956
+ return hasImport && hasUsage;
957
+ } catch (error) {
958
+ // Fallback to string check if AST parsing fails
959
+ console.warn(`AST parsing failed for ${functionName}, falling back to string check:`, error);
960
+ return content.includes(functionName);
961
+ }
962
+ }
963
+
964
+ /**
965
+ * Check if app.ts contains conflicting routes for a given endpoint
966
+ */
967
+ export async function checkRouteConflicts(
968
+ content: string,
969
+ workbenchEndpoint: string
970
+ ): Promise<boolean> {
971
+ try {
972
+ const ts = await import('typescript');
973
+ const sourceFile = ts.createSourceFile('app.ts', content, ts.ScriptTarget.Latest, true);
974
+
975
+ let hasConflict = false;
976
+
977
+ function visitNode(node: import('typescript').Node): void {
978
+ // Check for router.get calls
979
+ if (
980
+ ts.isCallExpression(node) &&
981
+ ts.isPropertyAccessExpression(node.expression) &&
982
+ ts.isIdentifier(node.expression.name) &&
983
+ node.expression.name.text === 'get'
984
+ ) {
985
+ // Check if first argument is the workbench endpoint
986
+ if (node.arguments.length > 0 && ts.isStringLiteral(node.arguments[0])) {
987
+ if (node.arguments[0].text === workbenchEndpoint) {
988
+ hasConflict = true;
989
+ }
990
+ }
991
+ }
992
+
993
+ ts.forEachChild(node, visitNode);
994
+ }
995
+
996
+ visitNode(sourceFile);
997
+ return hasConflict;
998
+ } catch (_error) {
999
+ return false;
1000
+ }
1001
+ }
1002
+
1003
+ /**
1004
+ * Analyze workbench usage and extract configuration
1005
+ *
1006
+ * @param content - The TypeScript source code
1007
+ * @returns workbench analysis including usage and config
1008
+ */
1009
+ export async function analyzeWorkbench(content: string): Promise<WorkbenchAnalysis> {
1010
+ try {
1011
+ const ts = await import('typescript');
1012
+ const sourceFile = ts.createSourceFile('app.ts', content, ts.ScriptTarget.Latest, true);
1013
+
1014
+ let hasImport = false;
1015
+ let hasUsage = false;
1016
+ let config: WorkbenchConfig | null = null;
1017
+
1018
+ function visitNode(node: import('typescript').Node): void {
1019
+ // Check for import declarations with createWorkbench
1020
+ if (ts.isImportDeclaration(node) && node.importClause?.namedBindings) {
1021
+ if (ts.isNamedImports(node.importClause.namedBindings)) {
1022
+ for (const element of node.importClause.namedBindings.elements) {
1023
+ if (element.name.text === 'createWorkbench') {
1024
+ hasImport = true;
1025
+ }
1026
+ }
1027
+ }
1028
+ }
1029
+
1030
+ // Check for createWorkbench function calls and extract config
1031
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
1032
+ if (node.expression.text === 'createWorkbench') {
1033
+ hasUsage = true;
1034
+
1035
+ // Extract configuration from the first argument (if any)
1036
+ if (node.arguments.length > 0) {
1037
+ const configArg = node.arguments[0];
1038
+ config = parseConfigObject(configArg, ts);
1039
+ } else {
1040
+ // Default config if no arguments provided
1041
+ config = { route: '/workbench' };
1042
+ }
1043
+ }
1044
+ }
1045
+
1046
+ // Recursively visit child nodes
1047
+ ts.forEachChild(node, visitNode);
1048
+ }
1049
+
1050
+ visitNode(sourceFile);
1051
+
1052
+ // Set default config if workbench is used but no config was parsed
1053
+ if (hasImport && hasUsage && !config) {
1054
+ config = { route: '/workbench' };
1055
+ }
1056
+
1057
+ return {
1058
+ hasWorkbench: hasImport && hasUsage,
1059
+ config: config,
1060
+ };
1061
+ } catch (error) {
1062
+ // Fallback to simple check if AST parsing fails
1063
+ console.warn('Workbench AST parsing failed, falling back to string check:', error);
1064
+ const hasWorkbench = content.includes('createWorkbench');
1065
+ return {
1066
+ hasWorkbench,
1067
+ config: hasWorkbench ? { route: '/workbench' } : null,
1068
+ };
1069
+ }
1070
+ }
1071
+
1072
+ /**
1073
+ * Parse a TypeScript object literal to extract configuration
1074
+ */
1075
+ function parseConfigObject(
1076
+ node: import('typescript').Node,
1077
+ ts: typeof import('typescript')
1078
+ ): WorkbenchConfig | null {
1079
+ if (!ts.isObjectLiteralExpression(node)) {
1080
+ return { route: '/workbench' }; // Default config
1081
+ }
1082
+
1083
+ const config: WorkbenchConfig = { route: '/workbench' };
1084
+
1085
+ for (const property of node.properties) {
1086
+ if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name)) {
1087
+ const propertyName = property.name.text;
1088
+
1089
+ if (propertyName === 'route' && ts.isStringLiteral(property.initializer)) {
1090
+ config.route = property.initializer.text;
1091
+ } else if (
1092
+ propertyName === 'headers' &&
1093
+ ts.isObjectLiteralExpression(property.initializer)
1094
+ ) {
1095
+ // Parse headers object if needed (not implemented for now)
1096
+ config.headers = {};
1097
+ }
1098
+ }
1099
+ }
1100
+
1101
+ return config;
1102
+ }
@@ -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)) {
@@ -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,
@@ -47,7 +47,9 @@ export const command = createCommand({
47
47
  tui.info('Running type check...');
48
48
  const { resolve } = await import('node:path');
49
49
  const absoluteProjectDir = resolve(projectDir);
50
- const result = await Bun.$`bunx tsc --noEmit --skipLibCheck`.cwd(absoluteProjectDir).nothrow();
50
+ const result = await Bun.$`bunx tsc --noEmit --skipLibCheck`
51
+ .cwd(absoluteProjectDir)
52
+ .nothrow();
51
53
 
52
54
  if (result.exitCode === 0) {
53
55
  tui.success('Type check passed');
@@ -1,8 +1,15 @@
1
1
  import type { BunPlugin } from 'bun';
2
2
  import { dirname, basename, join, resolve } from 'node:path';
3
- import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
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);
@@ -123,87 +174,63 @@ function generateAgentRegistry(srcDir: string, agentInfo: Array<Record<string, s
123
174
  })
124
175
  .join('\n');
125
176
 
126
- // Generate nested agent type definitions
127
- const agentTypeLines: string[] = [];
177
+ // Generate nested agent type definitions for Hono Context augmentation
178
+ const honoAgentTypeLines: string[] = [];
128
179
  for (const parentAgent of parentAgents) {
129
180
  const parentCamelName = toCamelCase(parentAgent.name);
130
181
  const children = subagentsByParent.get(parentAgent.name) || [];
131
182
 
132
183
  if (children.length === 0) {
133
184
  // No subagents
134
- agentTypeLines.push(
135
- ` ${parentCamelName}: AgentRunner<AgentRegistry['${parentCamelName}']['inputSchema'], AgentRegistry['${parentCamelName}']['outputSchema'], AgentRegistry['${parentCamelName}']['stream'] extends true ? true : false>;`
185
+ honoAgentTypeLines.push(
186
+ ` ${parentCamelName}: AgentRunner<LocalAgentRegistry['${parentCamelName}']['inputSchema'], LocalAgentRegistry['${parentCamelName}']['outputSchema'], LocalAgentRegistry['${parentCamelName}']['stream'] extends true ? true : false>;`
136
187
  );
137
188
  } else {
138
189
  // Has subagents - create intersection type
139
- agentTypeLines.push(
140
- ` ${parentCamelName}: AgentRunner<AgentRegistry['${parentCamelName}']['inputSchema'], AgentRegistry['${parentCamelName}']['outputSchema'], AgentRegistry['${parentCamelName}']['stream'] extends true ? true : false> & {`
190
+ honoAgentTypeLines.push(
191
+ ` ${parentCamelName}: AgentRunner<LocalAgentRegistry['${parentCamelName}']['inputSchema'], LocalAgentRegistry['${parentCamelName}']['outputSchema'], LocalAgentRegistry['${parentCamelName}']['stream'] extends true ? true : false> & {`
141
192
  );
142
193
  for (const child of children) {
143
194
  const childCamelName = toCamelCase(child.name);
144
195
  const fullChildName = toCamelCase(`${parentAgent.name}_${child.name}`);
145
- agentTypeLines.push(
196
+ honoAgentTypeLines.push(
146
197
  ` ${childCamelName}: AgentRunner<typeof ${fullChildName}Agent['inputSchema'], typeof ${fullChildName}Agent['outputSchema'], typeof ${fullChildName}Agent['stream'] extends true ? true : false>;`
147
198
  );
148
199
  }
149
- agentTypeLines.push(` };`);
200
+ honoAgentTypeLines.push(` };`);
150
201
  }
151
202
  }
152
- const agentTypes = agentTypeLines.join('\n');
153
-
154
- const generatedContent = `/// <reference types="hono" />
155
- // Auto-generated by Agentuity - do not edit manually
156
- ${imports}${evalsImportsStr}
157
- import type { AgentRunner, Logger } from '@agentuity/runtime';
158
- import type { KeyValueStorage, ObjectStorage, StreamStorage, VectorStorage } from '@agentuity/core';
159
-
160
- export const agentRegistry = {
161
- ${registry}
162
- } as const;
163
-
164
- export type AgentName = keyof typeof agentRegistry;
165
- export type AgentRegistry = typeof agentRegistry;
203
+ const honoAgentTypes = honoAgentTypeLines.join('\n');
166
204
 
167
- // Typed runners for each agent
168
- ${typeExports}
169
-
170
- // Augment Context to provide strongly-typed agents
171
- declare module "hono" {
172
- interface Context {
173
- agentName: AgentName;
174
- agent: {
175
- ${agentTypes}
176
- };
177
- waitUntil: (promise: Promise<void> | (() => void | Promise<void>)) => void;
178
- logger: Logger;
179
- kv: KeyValueStorage;
180
- objectstore: ObjectStorage;
181
- stream: StreamStorage;
182
- vector: VectorStorage;
183
- }
184
- }
185
- `;
186
-
187
- const agentsDir = join(srcDir, 'agents');
188
- const registryPath = join(agentsDir, 'registry.generated.ts');
205
+ // Generate agent type definitions for AgentRegistry interface augmentation
206
+ const runtimeAgentTypeLines: string[] = [];
207
+ for (const parentAgent of parentAgents) {
208
+ const parentCamelName = toCamelCase(parentAgent.name);
209
+ const children = subagentsByParent.get(parentAgent.name) || [];
189
210
 
190
- // Ensure agents directory exists
191
- if (!existsSync(agentsDir)) {
192
- mkdirSync(agentsDir, { recursive: true });
211
+ if (children.length === 0) {
212
+ // No subagents - use typeof the imported agent
213
+ runtimeAgentTypeLines.push(
214
+ ` ${parentCamelName}: AgentRunner<typeof ${parentCamelName}Agent['inputSchema'], typeof ${parentCamelName}Agent['outputSchema'], typeof ${parentCamelName}Agent['stream'] extends true ? true : false>;`
215
+ );
216
+ } else {
217
+ // Has subagents - create intersection type using typeof
218
+ runtimeAgentTypeLines.push(
219
+ ` ${parentCamelName}: AgentRunner<typeof ${parentCamelName}Agent['inputSchema'], typeof ${parentCamelName}Agent['outputSchema'], typeof ${parentCamelName}Agent['stream'] extends true ? true : false> & {`
220
+ );
221
+ for (const child of children) {
222
+ const childCamelName = toCamelCase(child.name);
223
+ const fullChildName = toCamelCase(`${parentAgent.name}_${child.name}`);
224
+ runtimeAgentTypeLines.push(
225
+ ` ${childCamelName}: AgentRunner<typeof ${fullChildName}Agent['inputSchema'], typeof ${fullChildName}Agent['outputSchema'], typeof ${fullChildName}Agent['stream'] extends true ? true : false>;`
226
+ );
227
+ }
228
+ runtimeAgentTypeLines.push(` };`);
229
+ }
193
230
  }
194
-
195
- writeFileSync(registryPath, generatedContent, 'utf-8');
231
+ const runtimeAgentTypes = runtimeAgentTypeLines.join('\n');
196
232
 
197
233
  // Generate React client types with nested structure
198
- const clientImports = agentInfo
199
- .map(({ name, path, parent }) => {
200
- const fullName = parent ? `${parent}_${name}` : name;
201
- const camelName = toCamelCase(fullName);
202
- const relativePath = path.replace(/^\.\/agents\//, './');
203
- return `import type ${camelName}Agent from '${relativePath}';`;
204
- })
205
- .join('\n');
206
-
207
234
  const clientAgentTypeLines: string[] = [];
208
235
  for (const parentAgent of parentAgents) {
209
236
  const parentCamelName = toCamelCase(parentAgent.name);
@@ -227,21 +254,74 @@ ${agentTypes}
227
254
  }
228
255
  }
229
256
  }
257
+ const reactAgentTypes = clientAgentTypeLines.join('\n');
230
258
 
231
- const clientTypesContent = `// Auto-generated by Agentuity - do not edit manually
232
- // This file augments @agentuity/react with your project's agent types
233
- ${clientImports}
259
+ const generatedContent = `/// <reference types="hono" />
260
+ // Auto-generated by Agentuity - do not edit manually
261
+ ${imports}${evalsImportsStr}
262
+ import type { AgentRunner, Logger } from '@agentuity/runtime';
263
+ import type { KeyValueStorage, ObjectStorage, StreamStorage, VectorStorage } from '@agentuity/core';
234
264
  import type { Agent } from '@agentuity/react';
235
265
 
266
+ export const agentRegistry = {
267
+ ${registry}
268
+ } as const;
269
+
270
+ // Local type aliases for Hono augmentation
271
+ type LocalAgentName = keyof typeof agentRegistry;
272
+ type LocalAgentRegistry = typeof agentRegistry;
273
+
274
+ // Typed runners for each agent
275
+ ${typeExports}
276
+
277
+ // Augment @agentuity/runtime types with strongly-typed agents from this project
278
+ declare module "@agentuity/runtime" {
279
+ // Augment the AgentRegistry interface with project-specific strongly-typed agents
280
+ export interface AgentRegistry {
281
+ ${runtimeAgentTypes}
282
+ }
283
+ }
284
+
285
+ // Augment Hono Context to provide strongly-typed agents and runtime services
286
+ // Note: Properties are added to Context via middleware in @agentuity/runtime
287
+ declare module "hono" {
288
+ interface Context {
289
+ agentName: LocalAgentName;
290
+ agent: {
291
+ ${honoAgentTypes}
292
+ };
293
+ waitUntil: (promise: Promise<void> | (() => void | Promise<void>)) => void;
294
+ logger: Logger;
295
+ kv: KeyValueStorage;
296
+ objectstore: ObjectStorage;
297
+ stream: StreamStorage;
298
+ vector: VectorStorage;
299
+ }
300
+ }
301
+
302
+ // Augment @agentuity/react types with strongly-typed agents from this project
236
303
  declare module '@agentuity/react' {
237
304
  interface AgentRegistry {
238
- ${clientAgentTypeLines.join('\n')}
305
+ ${reactAgentTypes}
239
306
  }
240
307
  }
241
308
  `;
242
309
 
243
- const clientTypesPath = join(agentsDir, 'types.generated.d.ts');
244
- writeFileSync(clientTypesPath, clientTypesContent, 'utf-8');
310
+ const agentsDir = join(srcDir, 'agents');
311
+ const registryPath = join(agentsDir, 'registry.generated.ts');
312
+ const legacyTypesPath = join(agentsDir, 'types.generated.d.ts');
313
+
314
+ // Ensure agents directory exists
315
+ if (!existsSync(agentsDir)) {
316
+ mkdirSync(agentsDir, { recursive: true });
317
+ }
318
+
319
+ writeFileSync(registryPath, generatedContent, 'utf-8');
320
+
321
+ // Remove legacy types.generated.d.ts if it exists (now consolidated into registry.generated.ts)
322
+ if (existsSync(legacyTypesPath)) {
323
+ unlinkSync(legacyTypesPath);
324
+ }
245
325
  }
246
326
 
247
327
  let metadata: Partial<BuildMetadata>;
@@ -261,6 +341,10 @@ const AgentuityBundler: BunPlugin = {
261
341
  const deploymentId = build.config.define?.['process.env.AGENTUITY_CLOUD_DEPLOYMENT_ID']
262
342
  ? JSON.parse(build.config.define['process.env.AGENTUITY_CLOUD_DEPLOYMENT_ID'])
263
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';
264
348
  const routes: Set<string> = new Set();
265
349
  const agentInfo: Array<Record<string, string>> = [];
266
350
  const agentMetadata: Map<string, Map<string, string>> = new Map<
@@ -463,11 +547,45 @@ const AgentuityBundler: BunPlugin = {
463
547
  const indexFile = join(srcDir, 'web', 'index.html');
464
548
 
465
549
  if (existsSync(indexFile)) {
550
+ // Setup workbench configuration - evaluate fresh each time during builds
551
+ const workbenchConfig = await setupWorkbench(srcDir);
552
+
466
553
  inserts.unshift(`await (async () => {
467
554
  const { serveStatic } = require('hono/bun');
468
- const { getRouter } = await import('@agentuity/runtime');
555
+ const { getRouter, registerDevModeRoutes } = await import('@agentuity/runtime');
469
556
  const router = getRouter()!;
470
- 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
+ }
471
589
  const webstatic = serveStatic({ root: import.meta.dir + '/web' });
472
590
  router.get('/', (c) => c.html(index));
473
591
  router.get('/web/chunk/*', webstatic);