@agentuity/cli 0.0.43 → 0.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/api.d.ts +3 -3
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/auth.d.ts +10 -2
  6. package/dist/auth.d.ts.map +1 -1
  7. package/dist/banner.d.ts.map +1 -1
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cmd/auth/api.d.ts +4 -4
  10. package/dist/cmd/auth/api.d.ts.map +1 -1
  11. package/dist/cmd/auth/index.d.ts.map +1 -1
  12. package/dist/cmd/auth/login.d.ts.map +1 -1
  13. package/dist/cmd/auth/signup.d.ts.map +1 -1
  14. package/dist/cmd/auth/ssh/add.d.ts +2 -0
  15. package/dist/cmd/auth/ssh/add.d.ts.map +1 -0
  16. package/dist/cmd/auth/ssh/api.d.ts +16 -0
  17. package/dist/cmd/auth/ssh/api.d.ts.map +1 -0
  18. package/dist/cmd/auth/ssh/delete.d.ts +2 -0
  19. package/dist/cmd/auth/ssh/delete.d.ts.map +1 -0
  20. package/dist/cmd/auth/ssh/index.d.ts +3 -0
  21. package/dist/cmd/auth/ssh/index.d.ts.map +1 -0
  22. package/dist/cmd/auth/ssh/list.d.ts +2 -0
  23. package/dist/cmd/auth/ssh/list.d.ts.map +1 -0
  24. package/dist/cmd/auth/whoami.d.ts.map +1 -1
  25. package/dist/cmd/bundle/ast.d.ts +14 -3
  26. package/dist/cmd/bundle/ast.d.ts.map +1 -1
  27. package/dist/cmd/bundle/ast.test.d.ts +2 -0
  28. package/dist/cmd/bundle/ast.test.d.ts.map +1 -0
  29. package/dist/cmd/bundle/bundler.d.ts +6 -1
  30. package/dist/cmd/bundle/bundler.d.ts.map +1 -1
  31. package/dist/cmd/bundle/file.d.ts.map +1 -1
  32. package/dist/cmd/bundle/fix-duplicate-exports.d.ts +2 -0
  33. package/dist/cmd/bundle/fix-duplicate-exports.d.ts.map +1 -0
  34. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts +2 -0
  35. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts.map +1 -0
  36. package/dist/cmd/bundle/plugin.d.ts +2 -0
  37. package/dist/cmd/bundle/plugin.d.ts.map +1 -1
  38. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  39. package/dist/cmd/cloud/domain.d.ts +17 -0
  40. package/dist/cmd/cloud/domain.d.ts.map +1 -0
  41. package/dist/cmd/cloud/index.d.ts.map +1 -1
  42. package/dist/cmd/cloud/resource/add.d.ts +2 -0
  43. package/dist/cmd/cloud/resource/add.d.ts.map +1 -0
  44. package/dist/cmd/cloud/resource/delete.d.ts +2 -0
  45. package/dist/cmd/cloud/resource/delete.d.ts.map +1 -0
  46. package/dist/cmd/cloud/resource/index.d.ts +3 -0
  47. package/dist/cmd/cloud/resource/index.d.ts.map +1 -0
  48. package/dist/cmd/cloud/resource/list.d.ts +2 -0
  49. package/dist/cmd/cloud/resource/list.d.ts.map +1 -0
  50. package/dist/cmd/cloud/scp/download.d.ts +2 -0
  51. package/dist/cmd/cloud/scp/download.d.ts.map +1 -0
  52. package/dist/cmd/cloud/scp/index.d.ts +3 -0
  53. package/dist/cmd/cloud/scp/index.d.ts.map +1 -0
  54. package/dist/cmd/cloud/scp/upload.d.ts +2 -0
  55. package/dist/cmd/cloud/scp/upload.d.ts.map +1 -0
  56. package/dist/cmd/cloud/ssh.d.ts +2 -0
  57. package/dist/cmd/cloud/ssh.d.ts.map +1 -0
  58. package/dist/cmd/dev/api.d.ts +18 -0
  59. package/dist/cmd/dev/api.d.ts.map +1 -0
  60. package/dist/cmd/dev/download.d.ts +11 -0
  61. package/dist/cmd/dev/download.d.ts.map +1 -0
  62. package/dist/cmd/dev/index.d.ts.map +1 -1
  63. package/dist/cmd/dev/templates.d.ts +3 -0
  64. package/dist/cmd/dev/templates.d.ts.map +1 -0
  65. package/dist/cmd/env/delete.d.ts.map +1 -1
  66. package/dist/cmd/env/get.d.ts.map +1 -1
  67. package/dist/cmd/env/import.d.ts.map +1 -1
  68. package/dist/cmd/env/list.d.ts.map +1 -1
  69. package/dist/cmd/env/pull.d.ts.map +1 -1
  70. package/dist/cmd/env/push.d.ts.map +1 -1
  71. package/dist/cmd/env/set.d.ts.map +1 -1
  72. package/dist/cmd/profile/show.d.ts.map +1 -1
  73. package/dist/cmd/project/create.d.ts.map +1 -1
  74. package/dist/cmd/project/delete.d.ts.map +1 -1
  75. package/dist/cmd/project/list.d.ts.map +1 -1
  76. package/dist/cmd/project/show.d.ts.map +1 -1
  77. package/dist/cmd/project/template-flow.d.ts +4 -0
  78. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  79. package/dist/cmd/secret/delete.d.ts.map +1 -1
  80. package/dist/cmd/secret/get.d.ts.map +1 -1
  81. package/dist/cmd/secret/import.d.ts.map +1 -1
  82. package/dist/cmd/secret/list.d.ts.map +1 -1
  83. package/dist/cmd/secret/pull.d.ts.map +1 -1
  84. package/dist/cmd/secret/push.d.ts.map +1 -1
  85. package/dist/cmd/secret/set.d.ts.map +1 -1
  86. package/dist/config.d.ts +9 -3
  87. package/dist/config.d.ts.map +1 -1
  88. package/dist/crypto/box.d.ts +65 -0
  89. package/dist/crypto/box.d.ts.map +1 -0
  90. package/dist/crypto/box.test.d.ts +2 -0
  91. package/dist/crypto/box.test.d.ts.map +1 -0
  92. package/dist/download.d.ts.map +1 -1
  93. package/dist/steps.d.ts +4 -1
  94. package/dist/steps.d.ts.map +1 -1
  95. package/dist/terminal.d.ts.map +1 -1
  96. package/dist/tui.d.ts +31 -1
  97. package/dist/tui.d.ts.map +1 -1
  98. package/dist/types.d.ts +249 -126
  99. package/dist/types.d.ts.map +1 -1
  100. package/dist/utils/detectSubagent.d.ts +15 -0
  101. package/dist/utils/detectSubagent.d.ts.map +1 -0
  102. package/dist/utils/zip.d.ts +7 -0
  103. package/dist/utils/zip.d.ts.map +1 -0
  104. package/package.json +11 -3
  105. package/src/api-errors.md +2 -2
  106. package/src/api.ts +12 -7
  107. package/src/auth.ts +116 -7
  108. package/src/banner.ts +13 -6
  109. package/src/cli.ts +695 -63
  110. package/src/cmd/auth/api.ts +10 -16
  111. package/src/cmd/auth/index.ts +2 -1
  112. package/src/cmd/auth/login.ts +24 -8
  113. package/src/cmd/auth/signup.ts +15 -11
  114. package/src/cmd/auth/ssh/add.ts +263 -0
  115. package/src/cmd/auth/ssh/api.ts +94 -0
  116. package/src/cmd/auth/ssh/delete.ts +102 -0
  117. package/src/cmd/auth/ssh/index.ts +10 -0
  118. package/src/cmd/auth/ssh/list.ts +74 -0
  119. package/src/cmd/auth/whoami.ts +13 -13
  120. package/src/cmd/bundle/ast.test.ts +565 -0
  121. package/src/cmd/bundle/ast.ts +457 -44
  122. package/src/cmd/bundle/bundler.ts +255 -57
  123. package/src/cmd/bundle/file.ts +6 -12
  124. package/src/cmd/bundle/fix-duplicate-exports.test.ts +387 -0
  125. package/src/cmd/bundle/fix-duplicate-exports.ts +204 -0
  126. package/src/cmd/bundle/index.ts +9 -9
  127. package/src/cmd/bundle/patch/aisdk.ts +1 -1
  128. package/src/cmd/bundle/plugin.ts +373 -53
  129. package/src/cmd/cloud/deploy.ts +300 -93
  130. package/src/cmd/cloud/domain.ts +92 -0
  131. package/src/cmd/cloud/index.ts +4 -1
  132. package/src/cmd/cloud/resource/add.ts +56 -0
  133. package/src/cmd/cloud/resource/delete.ts +120 -0
  134. package/src/cmd/cloud/resource/index.ts +11 -0
  135. package/src/cmd/cloud/resource/list.ts +69 -0
  136. package/src/cmd/cloud/scp/download.ts +59 -0
  137. package/src/cmd/cloud/scp/index.ts +9 -0
  138. package/src/cmd/cloud/scp/upload.ts +62 -0
  139. package/src/cmd/cloud/ssh.ts +68 -0
  140. package/src/cmd/dev/api.ts +46 -0
  141. package/src/cmd/dev/download.ts +111 -0
  142. package/src/cmd/dev/index.ts +360 -34
  143. package/src/cmd/dev/templates.ts +84 -0
  144. package/src/cmd/env/delete.ts +5 -20
  145. package/src/cmd/env/get.ts +5 -18
  146. package/src/cmd/env/import.ts +5 -20
  147. package/src/cmd/env/list.ts +5 -18
  148. package/src/cmd/env/pull.ts +10 -23
  149. package/src/cmd/env/push.ts +5 -23
  150. package/src/cmd/env/set.ts +5 -20
  151. package/src/cmd/index.ts +2 -2
  152. package/src/cmd/profile/show.ts +15 -6
  153. package/src/cmd/project/create.ts +7 -2
  154. package/src/cmd/project/delete.ts +75 -18
  155. package/src/cmd/project/download.ts +2 -2
  156. package/src/cmd/project/list.ts +8 -8
  157. package/src/cmd/project/show.ts +3 -7
  158. package/src/cmd/project/template-flow.ts +170 -72
  159. package/src/cmd/secret/delete.ts +5 -20
  160. package/src/cmd/secret/get.ts +5 -18
  161. package/src/cmd/secret/import.ts +5 -20
  162. package/src/cmd/secret/list.ts +5 -18
  163. package/src/cmd/secret/pull.ts +10 -23
  164. package/src/cmd/secret/push.ts +5 -23
  165. package/src/cmd/secret/set.ts +5 -20
  166. package/src/config.ts +224 -24
  167. package/src/crypto/box.test.ts +431 -0
  168. package/src/crypto/box.ts +477 -0
  169. package/src/download.ts +1 -0
  170. package/src/env-util.test.ts +1 -1
  171. package/src/steps.ts +65 -6
  172. package/src/terminal.ts +24 -23
  173. package/src/tui.ts +192 -61
  174. package/src/types.ts +291 -201
  175. package/src/utils/detectSubagent.ts +31 -0
  176. package/src/utils/zip.ts +38 -0
  177. package/dist/cmd/example/create-user.d.ts +0 -2
  178. package/dist/cmd/example/create-user.d.ts.map +0 -1
  179. package/dist/cmd/example/create.d.ts +0 -2
  180. package/dist/cmd/example/create.d.ts.map +0 -1
  181. package/dist/cmd/example/deploy.d.ts +0 -2
  182. package/dist/cmd/example/deploy.d.ts.map +0 -1
  183. package/dist/cmd/example/index.d.ts +0 -2
  184. package/dist/cmd/example/index.d.ts.map +0 -1
  185. package/dist/cmd/example/list.d.ts +0 -2
  186. package/dist/cmd/example/list.d.ts.map +0 -1
  187. package/dist/cmd/example/optional-auth.d.ts +0 -3
  188. package/dist/cmd/example/optional-auth.d.ts.map +0 -1
  189. package/dist/cmd/example/run-command.d.ts +0 -2
  190. package/dist/cmd/example/run-command.d.ts.map +0 -1
  191. package/dist/cmd/example/sound.d.ts +0 -3
  192. package/dist/cmd/example/sound.d.ts.map +0 -1
  193. package/dist/cmd/example/spinner.d.ts +0 -2
  194. package/dist/cmd/example/spinner.d.ts.map +0 -1
  195. package/dist/cmd/example/steps.d.ts +0 -2
  196. package/dist/cmd/example/steps.d.ts.map +0 -1
  197. package/dist/cmd/example/version.d.ts +0 -2
  198. package/dist/cmd/example/version.d.ts.map +0 -1
  199. package/src/cmd/example/create-user.ts +0 -38
  200. package/src/cmd/example/create.ts +0 -31
  201. package/src/cmd/example/deploy.ts +0 -36
  202. package/src/cmd/example/index.ts +0 -29
  203. package/src/cmd/example/list.ts +0 -32
  204. package/src/cmd/example/optional-auth.ts +0 -38
  205. package/src/cmd/example/run-command.ts +0 -45
  206. package/src/cmd/example/sound.ts +0 -14
  207. package/src/cmd/example/spinner.ts +0 -44
  208. package/src/cmd/example/steps.ts +0 -66
  209. package/src/cmd/example/version.ts +0 -13
@@ -1,7 +1,8 @@
1
1
  import * as acornLoose from 'acorn-loose';
2
2
  import { basename, dirname, relative } from 'node:path';
3
3
  import { generate } from 'astring';
4
- import { BuildMetadata } from '../../types';
4
+ import type { BuildMetadata } from '../../types';
5
+ import { createLogger } from '@agentuity/server';
5
6
 
6
7
  interface ASTNode {
7
8
  type: string;
@@ -32,6 +33,7 @@ interface ASTObjectExpression extends ASTNode {
32
33
 
33
34
  interface ASTLiteral extends ASTNode {
34
35
  value: string;
36
+ raw?: string;
35
37
  }
36
38
 
37
39
  interface ASTMemberExpression extends ASTNode {
@@ -46,6 +48,11 @@ interface ASTExpressionStatement extends ASTNode {
46
48
  expression: ASTCallExpression;
47
49
  }
48
50
 
51
+ interface ASTVariableDeclarator extends ASTNode {
52
+ id: ASTNode;
53
+ init?: ASTNode;
54
+ }
55
+
49
56
  function parseObjectExpressionToMap(expr: ASTObjectExpression): Map<string, string> {
50
57
  const result = new Map<string, string>();
51
58
  for (const prop of expr.properties) {
@@ -97,20 +104,47 @@ function createNewMetadataNode() {
97
104
  };
98
105
  }
99
106
 
100
- const projectId = process.env.AGENTUITY_CLOUD_PROJECT_ID ?? '';
101
-
102
107
  function hash(...val: string[]): string {
103
108
  const hasher = new Bun.CryptoHasher('sha256');
104
- val.forEach((val) => hasher.update(val));
109
+ val.map((val) => hasher.update(val));
105
110
  return hasher.digest().toHex();
106
111
  }
107
112
 
108
- function getAgentId(identifier: string): string {
109
- return hash(projectId, identifier);
113
+ function hashSHA1(...val: string[]): string {
114
+ const hasher = new Bun.CryptoHasher('sha1');
115
+ val.map((val) => hasher.update(val));
116
+ return hasher.digest().toHex();
110
117
  }
111
118
 
112
- function generateRouteId(method: string, path: string): string {
113
- return hash(projectId, method, path);
119
+ function getAgentId(
120
+ projectId: string,
121
+ deploymentId: string,
122
+ filename: string,
123
+ version: string
124
+ ): string {
125
+ return `agent_${hashSHA1(projectId, deploymentId, filename, version)}`;
126
+ }
127
+
128
+ function getEvalId(
129
+ projectId: string,
130
+ deploymentId: string,
131
+ filename: string,
132
+ name: string,
133
+ version: string
134
+ ): string {
135
+ return `eval_${hashSHA1(projectId, deploymentId, filename, name, version)}`;
136
+ }
137
+
138
+ function generateRouteId(
139
+ projectId: string,
140
+ deploymentId: string,
141
+ type: string,
142
+ method: string,
143
+ filename: string,
144
+ path: string,
145
+ version: string
146
+ ): string {
147
+ return `route_${hashSHA1(projectId, deploymentId, type, method, filename, path, version)}`;
114
148
  }
115
149
 
116
150
  type AcornParseResultType = ReturnType<typeof acornLoose.parse>;
@@ -121,7 +155,8 @@ function augmentAgentMetadataNode(
121
155
  rel: string,
122
156
  version: string,
123
157
  ast: AcornParseResultType,
124
- propvalue: ASTObjectExpression
158
+ propvalue: ASTObjectExpression,
159
+ _filename: string
125
160
  ): [string, Map<string, string>] {
126
161
  const metadata = parseObjectExpressionToMap(propvalue);
127
162
  if (!metadata.has('name')) {
@@ -138,7 +173,10 @@ function augmentAgentMetadataNode(
138
173
  createObjectPropertyNode('identifier', name),
139
174
  createObjectPropertyNode('filename', rel)
140
175
  );
176
+
141
177
  const newsource = generate(ast);
178
+
179
+ // Evals imports are now handled in registry.generated.ts
142
180
  return [newsource, metadata];
143
181
  }
144
182
 
@@ -148,7 +186,8 @@ function createAgentMetadataNode(
148
186
  rel: string,
149
187
  version: string,
150
188
  ast: AcornParseResultType,
151
- callargexp: ASTObjectExpression
189
+ callargexp: ASTObjectExpression,
190
+ _filename: string
152
191
  ): [string, Map<string, string>] {
153
192
  const newmetadata = createNewMetadataNode();
154
193
  const md = new Map<string, string>();
@@ -161,21 +200,303 @@ function createAgentMetadataNode(
161
200
  newmetadata.value.properties.push(createObjectPropertyNode(key, value));
162
201
  }
163
202
  callargexp.properties.push(newmetadata);
203
+
164
204
  const newsource = generate(ast);
205
+
206
+ // Evals imports are now handled in registry.generated.ts
165
207
  return [newsource, md];
166
208
  }
167
209
 
168
- export function parseAgentMetadata(
210
+ function camelToKebab(str: string): string {
211
+ return str
212
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
213
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
214
+ .toLowerCase();
215
+ }
216
+
217
+ function setLiteralValue(literal: ASTLiteral, value: string) {
218
+ literal.value = value;
219
+ if (literal.raw !== undefined) {
220
+ literal.raw = JSON.stringify(value);
221
+ }
222
+ }
223
+
224
+ function augmentEvalMetadataNode(
225
+ id: string,
226
+ name: string,
227
+ rel: string,
228
+ version: string,
229
+ identifier: string,
230
+ metadataObj: ASTObjectExpression
231
+ ): void {
232
+ // Check if id, version, identifier, filename already exist
233
+ const existingKeys = new Set<string>();
234
+ for (const prop of metadataObj.properties) {
235
+ if (prop.key.type === 'Identifier') {
236
+ existingKeys.add(prop.key.name);
237
+ }
238
+ }
239
+
240
+ // Add or update metadata properties
241
+ if (!existingKeys.has('id')) {
242
+ metadataObj.properties.push(createObjectPropertyNode('id', id));
243
+ } else {
244
+ // Update existing id
245
+ for (const prop of metadataObj.properties) {
246
+ if (prop.key.type === 'Identifier' && prop.key.name === 'id') {
247
+ if (prop.value.type === 'Literal') {
248
+ setLiteralValue(prop.value as ASTLiteral, id);
249
+ }
250
+ break;
251
+ }
252
+ }
253
+ }
254
+
255
+ if (!existingKeys.has('version')) {
256
+ metadataObj.properties.push(createObjectPropertyNode('version', version));
257
+ } else {
258
+ for (const prop of metadataObj.properties) {
259
+ if (prop.key.type === 'Identifier' && prop.key.name === 'version') {
260
+ if (prop.value.type === 'Literal') {
261
+ setLiteralValue(prop.value as ASTLiteral, version);
262
+ }
263
+ break;
264
+ }
265
+ }
266
+ }
267
+
268
+ if (!existingKeys.has('identifier')) {
269
+ metadataObj.properties.push(createObjectPropertyNode('identifier', identifier));
270
+ } else {
271
+ for (const prop of metadataObj.properties) {
272
+ if (prop.key.type === 'Identifier' && prop.key.name === 'identifier') {
273
+ if (prop.value.type === 'Literal') {
274
+ setLiteralValue(prop.value as ASTLiteral, identifier);
275
+ }
276
+ break;
277
+ }
278
+ }
279
+ }
280
+
281
+ if (!existingKeys.has('filename')) {
282
+ metadataObj.properties.push(createObjectPropertyNode('filename', rel));
283
+ } else {
284
+ for (const prop of metadataObj.properties) {
285
+ if (prop.key.type === 'Identifier' && prop.key.name === 'filename') {
286
+ if (prop.value.type === 'Literal') {
287
+ setLiteralValue(prop.value as ASTLiteral, rel);
288
+ }
289
+ break;
290
+ }
291
+ }
292
+ }
293
+ }
294
+
295
+ export function parseEvalMetadata(
169
296
  rootDir: string,
170
297
  filename: string,
171
- contents: string
172
- ): [string, Map<string, string>] {
298
+ contents: string,
299
+ projectId: string,
300
+ deploymentId: string
301
+ ): [
302
+ string,
303
+ Array<{
304
+ filename: string;
305
+ id: string;
306
+ version: string;
307
+ identifier: string;
308
+ name: string;
309
+ description?: string;
310
+ }>,
311
+ ] {
312
+ const logLevel = (process.env.AGENTUITY_LOG_LEVEL || 'info') as
313
+ | 'trace'
314
+ | 'debug'
315
+ | 'info'
316
+ | 'warn'
317
+ | 'error';
318
+ const logger = createLogger(logLevel);
319
+ logger.trace(`Parsing evals from ${filename}`);
320
+ const ast = acornLoose.parse(contents, { ecmaVersion: 'latest', sourceType: 'module' });
321
+ const rel = relative(rootDir, filename);
322
+ const dir = dirname(filename);
323
+ const identifier = basename(dir);
324
+ const version = hash(contents);
325
+ const evals: Array<{
326
+ filename: string;
327
+ id: string;
328
+ version: string;
329
+ identifier: string;
330
+ name: string;
331
+ description?: string;
332
+ }> = [];
333
+
334
+ // Find all agent.createEval() calls
335
+ for (const body of ast.body) {
336
+ let variableDeclaration: { declarations: Array<ASTVariableDeclarator> } | undefined;
337
+
338
+ // Handle both direct VariableDeclaration and ExportNamedDeclaration
339
+ if (body.type === 'VariableDeclaration') {
340
+ variableDeclaration = body as { declarations: Array<ASTVariableDeclarator> };
341
+ } else if (body.type === 'ExportNamedDeclaration') {
342
+ const exportDecl = body as {
343
+ declaration?: { type: string; declarations?: Array<ASTVariableDeclarator> };
344
+ };
345
+ if (exportDecl.declaration?.type === 'VariableDeclaration') {
346
+ variableDeclaration = exportDecl.declaration as {
347
+ declarations: Array<ASTVariableDeclarator>;
348
+ };
349
+ }
350
+ }
351
+
352
+ if (variableDeclaration) {
353
+ for (const vardecl of variableDeclaration.declarations) {
354
+ if (vardecl.type === 'VariableDeclarator' && vardecl.init?.type === 'CallExpression') {
355
+ const call = vardecl.init as ASTCallExpression;
356
+ if (call.callee.type === 'MemberExpression') {
357
+ const memberExpr = call.callee as ASTMemberExpression;
358
+ const object = memberExpr.object as ASTNodeIdentifier;
359
+ const property = memberExpr.property as ASTNodeIdentifier;
360
+ if (
361
+ object.type === 'Identifier' &&
362
+ object.name === 'agent' &&
363
+ property.type === 'Identifier' &&
364
+ property.name === 'createEval'
365
+ ) {
366
+ // Found agent.createEval() call
367
+ if (call.arguments.length > 0) {
368
+ const firstArg = call.arguments[0] as ASTNode;
369
+ if (firstArg.type === 'ObjectExpression') {
370
+ const evalConfig = firstArg as ASTObjectExpression;
371
+ let evalName: string | undefined;
372
+ let evalDescription: string | undefined;
373
+ let variableName: string | undefined;
374
+ let metadataObj: ASTObjectExpression | undefined;
375
+
376
+ // Capture variable name if available
377
+ if (vardecl.id.type === 'Identifier') {
378
+ variableName = (vardecl.id as ASTNodeIdentifier).name;
379
+ }
380
+
381
+ // Extract metadata from the eval config
382
+ for (const prop of evalConfig.properties) {
383
+ if (prop.key.type === 'Identifier' && prop.key.name === 'metadata') {
384
+ if (prop.value.type === 'ObjectExpression') {
385
+ metadataObj = prop.value as ASTObjectExpression;
386
+ for (const metaProp of metadataObj.properties) {
387
+ if (metaProp.key.type === 'Identifier') {
388
+ if (
389
+ metaProp.key.name === 'name' &&
390
+ metaProp.value.type === 'Literal'
391
+ ) {
392
+ evalName = (metaProp.value as ASTLiteral).value;
393
+ } else if (
394
+ metaProp.key.name === 'description' &&
395
+ metaProp.value.type === 'Literal'
396
+ ) {
397
+ evalDescription = (metaProp.value as ASTLiteral).value;
398
+ }
399
+ }
400
+ }
401
+ }
402
+ }
403
+ }
404
+
405
+ // Use metadata.name if provided, otherwise use variable name
406
+ // Throw error if neither is available (should never happen)
407
+ let finalName: string;
408
+ if (evalName) {
409
+ finalName = evalName;
410
+ } else if (variableName) {
411
+ finalName = camelToKebab(variableName);
412
+ } else {
413
+ throw new Error(
414
+ 'Eval is missing a name. Please provide metadata.name or use a named export.'
415
+ );
416
+ }
417
+
418
+ logger.trace(
419
+ `Found eval: ${finalName}${evalDescription ? ` - ${evalDescription}` : ''}`
420
+ );
421
+ const evalId = getEvalId(
422
+ projectId,
423
+ deploymentId,
424
+ rel,
425
+ finalName,
426
+ version
427
+ );
428
+
429
+ // Inject metadata into AST if metadata object exists
430
+ if (metadataObj) {
431
+ augmentEvalMetadataNode(
432
+ evalId,
433
+ finalName,
434
+ rel,
435
+ version,
436
+ identifier,
437
+ metadataObj
438
+ );
439
+ }
440
+
441
+ evals.push({
442
+ filename: rel,
443
+ id: evalId,
444
+ version,
445
+ identifier,
446
+ name: finalName,
447
+ description: evalDescription,
448
+ });
449
+ }
450
+ }
451
+ }
452
+ }
453
+ }
454
+ }
455
+ }
456
+ }
457
+
458
+ // Check for duplicate eval names in the same file
459
+ // This prevents hash collisions when projectId/deploymentId are empty
460
+ const seenNames = new Map<string, number>();
461
+ for (const evalItem of evals) {
462
+ const count = seenNames.get(evalItem.name) || 0;
463
+ seenNames.set(evalItem.name, count + 1);
464
+ }
465
+
466
+ const duplicates: string[] = [];
467
+ for (const [name, count] of seenNames.entries()) {
468
+ if (count > 1) {
469
+ duplicates.push(name);
470
+ }
471
+ }
472
+
473
+ if (duplicates.length > 0) {
474
+ throw new Error(
475
+ `Duplicate eval names found in ${rel}: ${duplicates.join(', ')}. ` +
476
+ 'Eval names must be unique within the same file to prevent ID collisions.'
477
+ );
478
+ }
479
+
480
+ const newsource = generate(ast);
481
+ logger.trace(`Parsed ${evals.length} eval(s) from ${filename}`);
482
+ return [newsource, evals];
483
+ }
484
+
485
+ export async function parseAgentMetadata(
486
+ rootDir: string,
487
+ filename: string,
488
+ contents: string,
489
+ projectId: string,
490
+ deploymentId: string
491
+ ): Promise<[string, Map<string, string>]> {
173
492
  const ast = acornLoose.parse(contents, { ecmaVersion: 'latest', sourceType: 'module' });
174
493
  let exportName: string | undefined;
175
494
  const rel = relative(rootDir, filename);
176
495
  const name = basename(dirname(filename));
177
- const id = getAgentId(name);
178
496
  const version = hash(contents);
497
+ const id = getAgentId(projectId, deploymentId, rel, version);
498
+
499
+ let result: [string, Map<string, string>] | undefined;
179
500
 
180
501
  for (const body of ast.body) {
181
502
  if (body.type === 'ExportDefaultDeclaration') {
@@ -186,52 +507,85 @@ export function parseAgentMetadata(
186
507
  const callargexp = callarg as ASTObjectExpression;
187
508
  for (const prop of callargexp.properties) {
188
509
  if (prop.key.type === 'Identifier' && prop.key.name === 'metadata') {
189
- return augmentAgentMetadataNode(
510
+ result = augmentAgentMetadataNode(
190
511
  id,
191
512
  name,
192
513
  rel,
193
514
  version,
194
515
  ast,
195
- prop.value as ASTObjectExpression
516
+ prop.value as ASTObjectExpression,
517
+ filename
196
518
  );
519
+ break;
197
520
  }
198
521
  }
199
- return createAgentMetadataNode(id, name, rel, version, ast, callargexp);
522
+ if (!result) {
523
+ result = createAgentMetadataNode(
524
+ id,
525
+ name,
526
+ rel,
527
+ version,
528
+ ast,
529
+ callargexp,
530
+ filename
531
+ );
532
+ }
533
+ break;
200
534
  }
201
535
  }
202
536
  }
203
- const identifier = body.declaration as ASTNodeIdentifier;
204
- exportName = identifier.name;
205
- break;
537
+ if (!result) {
538
+ const identifier = body.declaration as ASTNodeIdentifier;
539
+ exportName = identifier.name;
540
+ break;
541
+ }
206
542
  }
207
543
  }
208
- if (!exportName) {
544
+ if (!result && !exportName) {
209
545
  throw new Error(`could not find default export for ${filename} using ${rootDir}`);
210
546
  }
211
- for (const body of ast.body) {
212
- if (body.type === 'VariableDeclaration') {
213
- for (const vardecl of body.declarations) {
214
- if (vardecl.type === 'VariableDeclarator' && vardecl.id.type === 'Identifier') {
215
- const identifier = vardecl.id as ASTNodeIdentifier;
216
- if (identifier.name === exportName) {
217
- if (vardecl.init?.type === 'CallExpression') {
218
- const call = vardecl.init as ASTCallExpression;
219
- if (call.callee.name === 'createAgent') {
220
- for (const callarg of call.arguments) {
221
- const callargexp = callarg as ASTObjectExpression;
222
- for (const prop of callargexp.properties) {
223
- if (prop.key.type === 'Identifier' && prop.key.name === 'metadata') {
224
- return augmentAgentMetadataNode(
547
+ if (!result) {
548
+ for (const body of ast.body) {
549
+ if (body.type === 'VariableDeclaration') {
550
+ for (const vardecl of body.declarations) {
551
+ if (vardecl.type === 'VariableDeclarator' && vardecl.id.type === 'Identifier') {
552
+ const identifier = vardecl.id as ASTNodeIdentifier;
553
+ if (identifier.name === exportName) {
554
+ if (vardecl.init?.type === 'CallExpression') {
555
+ const call = vardecl.init as ASTCallExpression;
556
+ if (call.callee.name === 'createAgent') {
557
+ for (const callarg of call.arguments) {
558
+ const callargexp = callarg as ASTObjectExpression;
559
+ for (const prop of callargexp.properties) {
560
+ if (
561
+ prop.key.type === 'Identifier' &&
562
+ prop.key.name === 'metadata'
563
+ ) {
564
+ result = augmentAgentMetadataNode(
565
+ id,
566
+ name,
567
+ rel,
568
+ version,
569
+ ast,
570
+ prop.value as ASTObjectExpression,
571
+ filename
572
+ );
573
+ break;
574
+ }
575
+ }
576
+ if (!result) {
577
+ result = createAgentMetadataNode(
225
578
  id,
226
579
  name,
227
580
  rel,
228
581
  version,
229
582
  ast,
230
- prop.value as ASTObjectExpression
583
+ callargexp,
584
+ filename
231
585
  );
232
586
  }
587
+ break;
233
588
  }
234
- return createAgentMetadataNode(id, name, rel, version, ast, callargexp);
235
589
  }
236
590
  }
237
591
  }
@@ -240,16 +594,56 @@ export function parseAgentMetadata(
240
594
  }
241
595
  }
242
596
  }
243
- throw new Error(
244
- `error parsing: ${filename}. could not find an proper createAgent defined in this file`
245
- );
597
+ if (!result) {
598
+ throw new Error(
599
+ `error parsing: ${filename}. could not find an proper createAgent defined in this file`
600
+ );
601
+ }
602
+
603
+ // Parse evals from eval.ts file in the same directory
604
+ const logLevel = (process.env.AGENTUITY_LOG_LEVEL || 'info') as
605
+ | 'trace'
606
+ | 'debug'
607
+ | 'info'
608
+ | 'warn'
609
+ | 'error';
610
+ const logger = createLogger(logLevel);
611
+ const agentDir = dirname(filename);
612
+ const evalsPath = `${agentDir}/eval.ts`;
613
+ logger.trace(`Checking for evals file at ${evalsPath}`);
614
+ const evalsFile = Bun.file(evalsPath);
615
+ if (await evalsFile.exists()) {
616
+ logger.trace(`Found evals file at ${evalsPath}, parsing...`);
617
+ const evalsSource = await evalsFile.text();
618
+ const transpiler = new Bun.Transpiler({ loader: 'ts' });
619
+ const evalsContents = transpiler.transformSync(evalsSource);
620
+ const [, evals] = parseEvalMetadata(
621
+ rootDir,
622
+ evalsPath,
623
+ evalsContents,
624
+ projectId,
625
+ deploymentId
626
+ );
627
+ if (evals.length > 0) {
628
+ logger.trace(`Adding ${evals.length} eval(s) to agent metadata for ${name}`);
629
+ result[1].set('evals', JSON.stringify(evals));
630
+ } else {
631
+ logger.trace(`No evals found in ${evalsPath}`);
632
+ }
633
+ } else {
634
+ logger.trace(`No evals file found at ${evalsPath}`);
635
+ }
636
+
637
+ return result;
246
638
  }
247
639
 
248
640
  type RouteDefinition = BuildMetadata['routes'];
249
641
 
250
642
  export async function parseRoute(
251
643
  rootDir: string,
252
- filename: string
644
+ filename: string,
645
+ projectId: string,
646
+ deploymentId: string
253
647
  ): Promise<BuildMetadata['routes']> {
254
648
  const contents = await Bun.file(filename).text();
255
649
  const version = hash(contents);
@@ -291,7 +685,17 @@ export async function parseRoute(
291
685
  }
292
686
 
293
687
  const rel = relative(rootDir, filename);
294
- const name = basename(dirname(filename));
688
+ const dir = dirname(filename);
689
+ const name = basename(dir);
690
+
691
+ // Detect if this is a subagent route and build proper path
692
+ const relativePath = relative(rootDir, dir)
693
+ .replace(/^src\/agents\//, '')
694
+ .replace(/^src\/apis\//, '');
695
+ const pathParts = relativePath.split('/').filter(Boolean);
696
+ const isSubagent = pathParts.length === 2 && filename.includes('src/agents');
697
+ const routeName = isSubagent ? pathParts.join('/') : name;
698
+
295
699
  const routes: RouteDefinition = [];
296
700
  const routePrefix = filename.includes('src/agents') ? '/agent' : '/api';
297
701
 
@@ -376,11 +780,20 @@ export async function parseRoute(
376
780
  break;
377
781
  }
378
782
  }
379
- const thepath = `${routePrefix}/${name}/${suffix}`
783
+ const thepath = `${routePrefix}/${routeName}/${suffix}`
380
784
  .replaceAll(/\/{2,}/g, '/')
381
785
  .replaceAll(/\/$/g, '');
786
+ const id = generateRouteId(
787
+ projectId,
788
+ deploymentId,
789
+ type,
790
+ method,
791
+ rel,
792
+ thepath,
793
+ version
794
+ );
382
795
  routes.push({
383
- id: generateRouteId(method, thepath),
796
+ id,
384
797
  method: method as 'get' | 'post' | 'put' | 'delete' | 'patch',
385
798
  type: type as 'api' | 'sms' | 'email' | 'cron',
386
799
  filename: rel,