@agentuity/cli 0.0.110 → 0.0.112

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 (258) hide show
  1. package/bin/cli.ts +4 -0
  2. package/dist/agents-docs.d.ts +5 -4
  3. package/dist/agents-docs.d.ts.map +1 -1
  4. package/dist/agents-docs.js +28 -8
  5. package/dist/agents-docs.js.map +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +19 -4
  8. package/dist/cli.js.map +1 -1
  9. package/dist/cmd/auth/apikey.d.ts +2 -0
  10. package/dist/cmd/auth/apikey.d.ts.map +1 -0
  11. package/dist/cmd/auth/apikey.js +31 -0
  12. package/dist/cmd/auth/apikey.js.map +1 -0
  13. package/dist/cmd/auth/index.d.ts.map +1 -1
  14. package/dist/cmd/auth/index.js +9 -1
  15. package/dist/cmd/auth/index.js.map +1 -1
  16. package/dist/cmd/build/ast.d.ts.map +1 -1
  17. package/dist/cmd/build/ast.js +103 -2
  18. package/dist/cmd/build/ast.js.map +1 -1
  19. package/dist/cmd/build/entry-generator.d.ts +2 -1
  20. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  21. package/dist/cmd/build/entry-generator.js +152 -9
  22. package/dist/cmd/build/entry-generator.js.map +1 -1
  23. package/dist/cmd/build/vite/agent-discovery.d.ts +1 -1
  24. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  25. package/dist/cmd/build/vite/agent-discovery.js +7 -6
  26. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  27. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  28. package/dist/cmd/build/vite/index.js +3 -2
  29. package/dist/cmd/build/vite/index.js.map +1 -1
  30. package/dist/cmd/build/vite/metadata-generator.js +1 -1
  31. package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
  32. package/dist/cmd/build/vite/registry-generator.d.ts +1 -1
  33. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  34. package/dist/cmd/build/vite/registry-generator.js +115 -23
  35. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  36. package/dist/cmd/build/vite/route-discovery.d.ts +6 -0
  37. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  38. package/dist/cmd/build/vite/route-discovery.js +19 -0
  39. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  40. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  41. package/dist/cmd/build/vite/vite-builder.js +3 -2
  42. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  43. package/dist/cmd/cloud/deploy-fork.d.ts +32 -0
  44. package/dist/cmd/cloud/deploy-fork.d.ts.map +1 -0
  45. package/dist/cmd/cloud/deploy-fork.js +258 -0
  46. package/dist/cmd/cloud/deploy-fork.js.map +1 -0
  47. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  48. package/dist/cmd/cloud/deploy.js +125 -4
  49. package/dist/cmd/cloud/deploy.js.map +1 -1
  50. package/dist/cmd/cloud/sandbox/create.d.ts.map +1 -1
  51. package/dist/cmd/cloud/sandbox/create.js +18 -0
  52. package/dist/cmd/cloud/sandbox/create.js.map +1 -1
  53. package/dist/cmd/cloud/sandbox/delete.d.ts.map +1 -1
  54. package/dist/cmd/cloud/sandbox/delete.js +2 -6
  55. package/dist/cmd/cloud/sandbox/delete.js.map +1 -1
  56. package/dist/cmd/cloud/sandbox/download.d.ts +3 -0
  57. package/dist/cmd/cloud/sandbox/download.d.ts.map +1 -0
  58. package/dist/cmd/cloud/sandbox/download.js +89 -0
  59. package/dist/cmd/cloud/sandbox/download.js.map +1 -0
  60. package/dist/cmd/cloud/sandbox/env.d.ts +3 -0
  61. package/dist/cmd/cloud/sandbox/env.d.ts.map +1 -0
  62. package/dist/cmd/cloud/sandbox/env.js +90 -0
  63. package/dist/cmd/cloud/sandbox/env.js.map +1 -0
  64. package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -1
  65. package/dist/cmd/cloud/sandbox/get.js +24 -0
  66. package/dist/cmd/cloud/sandbox/get.js.map +1 -1
  67. package/dist/cmd/cloud/sandbox/index.d.ts.map +1 -1
  68. package/dist/cmd/cloud/sandbox/index.js +14 -0
  69. package/dist/cmd/cloud/sandbox/index.js.map +1 -1
  70. package/dist/cmd/cloud/sandbox/ls.d.ts +3 -0
  71. package/dist/cmd/cloud/sandbox/ls.d.ts.map +1 -0
  72. package/dist/cmd/cloud/sandbox/ls.js +119 -0
  73. package/dist/cmd/cloud/sandbox/ls.js.map +1 -0
  74. package/dist/cmd/cloud/sandbox/mkdir.d.ts +3 -0
  75. package/dist/cmd/cloud/sandbox/mkdir.d.ts.map +1 -0
  76. package/dist/cmd/cloud/sandbox/mkdir.js +59 -0
  77. package/dist/cmd/cloud/sandbox/mkdir.js.map +1 -0
  78. package/dist/cmd/cloud/sandbox/rm.d.ts +3 -0
  79. package/dist/cmd/cloud/sandbox/rm.d.ts.map +1 -0
  80. package/dist/cmd/cloud/sandbox/rm.js +45 -0
  81. package/dist/cmd/cloud/sandbox/rm.js.map +1 -0
  82. package/dist/cmd/cloud/sandbox/rmdir.d.ts +3 -0
  83. package/dist/cmd/cloud/sandbox/rmdir.d.ts.map +1 -0
  84. package/dist/cmd/cloud/sandbox/rmdir.js +59 -0
  85. package/dist/cmd/cloud/sandbox/rmdir.js.map +1 -0
  86. package/dist/cmd/cloud/sandbox/snapshot/create.d.ts.map +1 -1
  87. package/dist/cmd/cloud/sandbox/snapshot/create.js +0 -2
  88. package/dist/cmd/cloud/sandbox/snapshot/create.js.map +1 -1
  89. package/dist/cmd/cloud/sandbox/snapshot/get.d.ts.map +1 -1
  90. package/dist/cmd/cloud/sandbox/snapshot/get.js +0 -2
  91. package/dist/cmd/cloud/sandbox/snapshot/get.js.map +1 -1
  92. package/dist/cmd/cloud/sandbox/snapshot/list.d.ts.map +1 -1
  93. package/dist/cmd/cloud/sandbox/snapshot/list.js +0 -3
  94. package/dist/cmd/cloud/sandbox/snapshot/list.js.map +1 -1
  95. package/dist/cmd/cloud/sandbox/upload.d.ts +3 -0
  96. package/dist/cmd/cloud/sandbox/upload.d.ts.map +1 -0
  97. package/dist/cmd/cloud/sandbox/upload.js +77 -0
  98. package/dist/cmd/cloud/sandbox/upload.js.map +1 -0
  99. package/dist/cmd/cloud/ssh.d.ts.map +1 -1
  100. package/dist/cmd/cloud/ssh.js +9 -3
  101. package/dist/cmd/cloud/ssh.js.map +1 -1
  102. package/dist/cmd/dev/index.d.ts.map +1 -1
  103. package/dist/cmd/dev/index.js +34 -19
  104. package/dist/cmd/dev/index.js.map +1 -1
  105. package/dist/cmd/dev/sync.d.ts.map +1 -1
  106. package/dist/cmd/dev/sync.js +8 -14
  107. package/dist/cmd/dev/sync.js.map +1 -1
  108. package/dist/cmd/git/account/add.d.ts +17 -0
  109. package/dist/cmd/git/account/add.d.ts.map +1 -0
  110. package/dist/cmd/git/account/add.js +244 -0
  111. package/dist/cmd/git/account/add.js.map +1 -0
  112. package/dist/cmd/git/account/index.d.ts +3 -0
  113. package/dist/cmd/git/account/index.d.ts.map +1 -0
  114. package/dist/cmd/git/account/index.js +11 -0
  115. package/dist/cmd/git/account/index.js.map +1 -0
  116. package/dist/cmd/git/account/list.d.ts +2 -0
  117. package/dist/cmd/git/account/list.d.ts.map +1 -0
  118. package/dist/cmd/git/account/list.js +111 -0
  119. package/dist/cmd/git/account/list.js.map +1 -0
  120. package/dist/cmd/git/account/remove.d.ts +2 -0
  121. package/dist/cmd/git/account/remove.d.ts.map +1 -0
  122. package/dist/cmd/git/account/remove.js +171 -0
  123. package/dist/cmd/git/account/remove.js.map +1 -0
  124. package/dist/cmd/git/index.d.ts +3 -0
  125. package/dist/cmd/git/index.d.ts.map +1 -0
  126. package/dist/cmd/git/index.js +19 -0
  127. package/dist/cmd/git/index.js.map +1 -0
  128. package/dist/cmd/git/link.d.ts +32 -0
  129. package/dist/cmd/git/link.d.ts.map +1 -0
  130. package/dist/cmd/git/link.js +357 -0
  131. package/dist/cmd/git/link.js.map +1 -0
  132. package/dist/cmd/git/list.d.ts +2 -0
  133. package/dist/cmd/git/list.d.ts.map +1 -0
  134. package/dist/cmd/git/list.js +137 -0
  135. package/dist/cmd/git/list.js.map +1 -0
  136. package/dist/cmd/git/status.d.ts +2 -0
  137. package/dist/cmd/git/status.d.ts.map +1 -0
  138. package/dist/cmd/git/status.js +119 -0
  139. package/dist/cmd/git/status.js.map +1 -0
  140. package/dist/cmd/git/unlink.d.ts +2 -0
  141. package/dist/cmd/git/unlink.d.ts.map +1 -0
  142. package/dist/cmd/git/unlink.js +98 -0
  143. package/dist/cmd/git/unlink.js.map +1 -0
  144. package/dist/cmd/index.d.ts.map +1 -1
  145. package/dist/cmd/index.js +2 -0
  146. package/dist/cmd/index.js.map +1 -1
  147. package/dist/cmd/integration/api.d.ts +61 -0
  148. package/dist/cmd/integration/api.d.ts.map +1 -0
  149. package/dist/cmd/integration/api.js +176 -0
  150. package/dist/cmd/integration/api.js.map +1 -0
  151. package/dist/cmd/integration/github/connect.d.ts +2 -0
  152. package/dist/cmd/integration/github/connect.d.ts.map +1 -0
  153. package/dist/cmd/integration/github/connect.js +197 -0
  154. package/dist/cmd/integration/github/connect.js.map +1 -0
  155. package/dist/cmd/integration/github/disconnect.d.ts +2 -0
  156. package/dist/cmd/integration/github/disconnect.d.ts.map +1 -0
  157. package/dist/cmd/integration/github/disconnect.js +121 -0
  158. package/dist/cmd/integration/github/disconnect.js.map +1 -0
  159. package/dist/cmd/integration/github/index.d.ts +2 -0
  160. package/dist/cmd/integration/github/index.d.ts.map +1 -0
  161. package/dist/cmd/integration/github/index.js +21 -0
  162. package/dist/cmd/integration/github/index.js.map +1 -0
  163. package/dist/cmd/integration/index.d.ts +2 -0
  164. package/dist/cmd/integration/index.d.ts.map +1 -0
  165. package/dist/cmd/integration/index.js +16 -0
  166. package/dist/cmd/integration/index.js.map +1 -0
  167. package/dist/config.d.ts +2 -0
  168. package/dist/config.d.ts.map +1 -1
  169. package/dist/config.js +25 -1
  170. package/dist/config.js.map +1 -1
  171. package/dist/errors.d.ts +2 -1
  172. package/dist/errors.d.ts.map +1 -1
  173. package/dist/errors.js +5 -0
  174. package/dist/errors.js.map +1 -1
  175. package/dist/log-collector.d.ts +30 -0
  176. package/dist/log-collector.d.ts.map +1 -0
  177. package/dist/log-collector.js +74 -0
  178. package/dist/log-collector.js.map +1 -0
  179. package/dist/output.d.ts.map +1 -1
  180. package/dist/output.js +2 -1
  181. package/dist/output.js.map +1 -1
  182. package/dist/steps.d.ts.map +1 -1
  183. package/dist/steps.js +48 -3
  184. package/dist/steps.js.map +1 -1
  185. package/dist/tui/box.d.ts.map +1 -1
  186. package/dist/tui/box.js +1 -6
  187. package/dist/tui/box.js.map +1 -1
  188. package/dist/tui/symbols.d.ts.map +1 -1
  189. package/dist/tui/symbols.js +4 -0
  190. package/dist/tui/symbols.js.map +1 -1
  191. package/dist/tui.d.ts +21 -12
  192. package/dist/tui.d.ts.map +1 -1
  193. package/dist/tui.js +74 -25
  194. package/dist/tui.js.map +1 -1
  195. package/dist/types.d.ts +73 -1
  196. package/dist/types.d.ts.map +1 -1
  197. package/dist/types.js +4 -0
  198. package/dist/types.js.map +1 -1
  199. package/dist/typescript-errors.d.ts.map +1 -1
  200. package/dist/typescript-errors.js +2 -2
  201. package/dist/typescript-errors.js.map +1 -1
  202. package/package.json +6 -6
  203. package/src/agents-docs.ts +42 -8
  204. package/src/cli.ts +20 -4
  205. package/src/cmd/auth/apikey.ts +36 -0
  206. package/src/cmd/auth/index.ts +9 -1
  207. package/src/cmd/build/ast.ts +120 -2
  208. package/src/cmd/build/entry-generator.ts +157 -10
  209. package/src/cmd/build/vite/agent-discovery.ts +8 -5
  210. package/src/cmd/build/vite/index.ts +3 -2
  211. package/src/cmd/build/vite/metadata-generator.ts +1 -1
  212. package/src/cmd/build/vite/registry-generator.ts +125 -24
  213. package/src/cmd/build/vite/route-discovery.ts +20 -0
  214. package/src/cmd/build/vite/vite-builder.ts +3 -2
  215. package/src/cmd/cloud/deploy-fork.ts +296 -0
  216. package/src/cmd/cloud/deploy.ts +148 -4
  217. package/src/cmd/cloud/sandbox/create.ts +22 -0
  218. package/src/cmd/cloud/sandbox/delete.ts +2 -6
  219. package/src/cmd/cloud/sandbox/download.ts +96 -0
  220. package/src/cmd/cloud/sandbox/env.ts +104 -0
  221. package/src/cmd/cloud/sandbox/get.ts +22 -0
  222. package/src/cmd/cloud/sandbox/index.ts +14 -0
  223. package/src/cmd/cloud/sandbox/ls.ts +126 -0
  224. package/src/cmd/cloud/sandbox/mkdir.ts +65 -0
  225. package/src/cmd/cloud/sandbox/rm.ts +51 -0
  226. package/src/cmd/cloud/sandbox/rmdir.ts +65 -0
  227. package/src/cmd/cloud/sandbox/snapshot/create.ts +0 -2
  228. package/src/cmd/cloud/sandbox/snapshot/get.ts +0 -2
  229. package/src/cmd/cloud/sandbox/snapshot/list.ts +0 -3
  230. package/src/cmd/cloud/sandbox/upload.ts +83 -0
  231. package/src/cmd/cloud/ssh.ts +13 -3
  232. package/src/cmd/dev/index.ts +49 -31
  233. package/src/cmd/dev/sync.ts +26 -30
  234. package/src/cmd/git/account/add.ts +317 -0
  235. package/src/cmd/git/account/index.ts +12 -0
  236. package/src/cmd/git/account/list.ts +139 -0
  237. package/src/cmd/git/account/remove.ts +212 -0
  238. package/src/cmd/git/index.ts +20 -0
  239. package/src/cmd/git/link.ts +468 -0
  240. package/src/cmd/git/list.ts +161 -0
  241. package/src/cmd/git/status.ts +144 -0
  242. package/src/cmd/git/unlink.ts +117 -0
  243. package/src/cmd/index.ts +2 -0
  244. package/src/cmd/integration/api.ts +379 -0
  245. package/src/cmd/integration/github/connect.ts +242 -0
  246. package/src/cmd/integration/github/disconnect.ts +149 -0
  247. package/src/cmd/integration/github/index.ts +21 -0
  248. package/src/cmd/integration/index.ts +16 -0
  249. package/src/config.ts +35 -1
  250. package/src/errors.ts +7 -0
  251. package/src/log-collector.ts +77 -0
  252. package/src/output.ts +2 -1
  253. package/src/steps.ts +52 -4
  254. package/src/tui/box.ts +1 -7
  255. package/src/tui/symbols.ts +5 -0
  256. package/src/tui.ts +77 -25
  257. package/src/types.ts +89 -0
  258. package/src/typescript-errors.ts +2 -1
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { join } from 'node:path';
7
- import type { Logger, WorkbenchConfig } from '../../types';
7
+ import type { Logger, WorkbenchConfig, AnalyticsConfig } from '../../types';
8
8
  import { discoverRoutes } from './vite/route-discovery';
9
9
 
10
10
  interface GenerateEntryOptions {
@@ -14,6 +14,7 @@ interface GenerateEntryOptions {
14
14
  logger: Logger;
15
15
  mode: 'dev' | 'prod';
16
16
  workbench?: WorkbenchConfig;
17
+ analytics?: boolean | AnalyticsConfig;
17
18
  vitePort?: number; // Port of Vite asset server (dev mode only)
18
19
  }
19
20
 
@@ -21,7 +22,8 @@ interface GenerateEntryOptions {
21
22
  * Generate entry file with clean Vite-native architecture
22
23
  */
23
24
  export async function generateEntryFile(options: GenerateEntryOptions): Promise<void> {
24
- const { rootDir, projectId, deploymentId, logger, mode, workbench, vitePort } = options;
25
+ const { rootDir, projectId, deploymentId, logger, mode, workbench, analytics, vitePort } =
26
+ options;
25
27
 
26
28
  const srcDir = join(rootDir, 'src');
27
29
  const generatedDir = join(srcDir, 'generated');
@@ -70,7 +72,11 @@ export async function generateEntryFile(options: GenerateEntryOptions): Promise<
70
72
  ` loadBuildMetadata,`,
71
73
  ` createWorkbenchRouter,`,
72
74
  ` bootstrapRuntimeEnv,`,
73
- ` patchBunS3ForStorageDev,`,
75
+ ` patchBunS3ForStorageDev,`,
76
+ ` getOrganizationId,`,
77
+ ` getProjectId,`,
78
+ ` isDevMode as runtimeIsDevMode,`,
79
+ ` createWebSessionMiddleware,`,
74
80
  ];
75
81
 
76
82
  const imports = [
@@ -200,6 +206,126 @@ if (isDevelopment() && process.env.VITE_PORT) {
200
206
  // See: https://github.com/oven-sh/bun/issues/20183
201
207
  const getEnv = (key: string) => process.env[key];
202
208
  const isDevelopment = () => getEnv('NODE' + '_' + 'ENV') !== 'production';
209
+ `;
210
+
211
+ // Generate analytics config and injection helper
212
+ const analyticsEnabled = analytics !== false;
213
+ const analyticsConfig: AnalyticsConfig = typeof analytics === 'object' ? analytics : {};
214
+
215
+ const analyticsHelper = analyticsEnabled
216
+ ? `
217
+ // Analytics configuration - edit agentuity.config.ts to configure
218
+ const analyticsConfig = {
219
+ enabled: ${analyticsConfig.enabled !== false},
220
+ requireConsent: ${analyticsConfig.requireConsent ?? false},
221
+ trackClicks: ${analyticsConfig.trackClicks ?? true},
222
+ trackScroll: ${analyticsConfig.trackScroll ?? true},
223
+ trackOutboundLinks: ${analyticsConfig.trackOutboundLinks ?? true},
224
+ trackForms: ${analyticsConfig.trackForms ?? false},
225
+ trackWebVitals: ${analyticsConfig.trackWebVitals ?? true},
226
+ trackErrors: ${analyticsConfig.trackErrors ?? true},
227
+ trackSPANavigation: ${analyticsConfig.trackSPANavigation ?? true},
228
+ sampleRate: ${analyticsConfig.sampleRate ?? 1},
229
+ excludePatterns: ${JSON.stringify(analyticsConfig.excludePatterns ?? [])},
230
+ globalProperties: ${JSON.stringify(analyticsConfig.globalProperties ?? {})},
231
+ };
232
+
233
+ // Inject analytics config and script into HTML
234
+ // Note: Only static config is injected (org, project, devmode, tracking options)
235
+ // Session and thread IDs are read from cookies by the beacon script
236
+ function injectAnalytics(html: string): string {
237
+ if (!analyticsConfig.enabled) return html;
238
+
239
+ const orgId = getOrganizationId() || '';
240
+ const projectId = getProjectId() || '';
241
+ const isDevmode = runtimeIsDevMode();
242
+
243
+ // Only include static config - session/thread come from cookies
244
+ const pageConfig = {
245
+ ...analyticsConfig,
246
+ orgId,
247
+ projectId,
248
+ isDevmode,
249
+ };
250
+
251
+ const configScript = \`<script>window.__AGENTUITY_ANALYTICS__=\${JSON.stringify(pageConfig)};</script>\`;
252
+ // Session script sets cookies and window.__AGENTUITY_SESSION__ (dynamic, not cached)
253
+ const sessionScript = '<script src="/_agentuity/webanalytics/session.js" async></script>';
254
+ // Beacon script reads from __AGENTUITY_SESSION__ and sends events (static, cached)
255
+ const beaconScript = '<script src="/_agentuity/webanalytics/analytics.js" async></script>';
256
+ const injection = configScript + sessionScript + beaconScript;
257
+
258
+ // Inject before </head> or at start of <body>
259
+ if (html.includes('</head>')) {
260
+ return html.replace('</head>', injection + '</head>');
261
+ }
262
+ if (html.includes('<body')) {
263
+ return html.replace(/<body([^>]*)>/, \`<body$1>\${injection}\`);
264
+ }
265
+ return injection + html;
266
+ }
267
+
268
+ // Serve analytics routes
269
+ function registerAnalyticsRoutes(app: ReturnType<typeof createRouter>): void {
270
+ // Dynamic session config script - sets cookies and returns session/thread IDs
271
+ // This endpoint is NOT cached - it generates unique session data per request
272
+ app.get('/_agentuity/webanalytics/session.js', createWebSessionMiddleware(), async (c: Context) => {
273
+ const sessionId = c.get('sessionId') || '';
274
+ const thread = c.get('thread');
275
+ const threadId = thread?.id || '';
276
+
277
+ const sessionScript = \`window.__AGENTUITY_SESSION__={sessionId:"\${sessionId}",threadId:"\${threadId}"};\`;
278
+
279
+ return new Response(sessionScript, {
280
+ headers: {
281
+ 'Content-Type': 'application/javascript; charset=utf-8',
282
+ 'Cache-Control': 'no-store, no-cache, must-revalidate',
283
+ },
284
+ });
285
+ });
286
+
287
+ // Static beacon script - can be cached
288
+ app.get('/_agentuity/webanalytics/analytics.js', async (c: Context) => {
289
+ // Beacon waits for window.__AGENTUITY_SESSION__ before sending events
290
+ const beaconScript = \`(function(){
291
+ var w=window,d=document,c=w.__AGENTUITY_ANALYTICS__;
292
+ if(!c||!c.enabled)return;
293
+ var q=[],t=null,sr=false,E='/_agentuity/webanalytics/collect',geo=null;
294
+ function id(){return crypto.randomUUID?crypto.randomUUID():Date.now()+'-'+Math.random().toString(36).substr(2,9)}
295
+ function base(type){var e={id:id(),timestamp:Date.now(),timezone_offset:new Date().getTimezoneOffset(),event_type:type,url:location.href,path:location.pathname,referrer:d.referrer||'',title:d.title||'',screen_width:screen.width||0,screen_height:screen.height||0,viewport_width:innerWidth||0,viewport_height:innerHeight||0,device_pixel_ratio:devicePixelRatio||1,user_agent:navigator.userAgent||'',language:navigator.language||''};if(geo){e.country=geo.country||'';e.region=geo.region||'';e.city=geo.city||'';e.timezone=geo.timezone||''}return e}
296
+ fetch('https://agentuity.sh/location').then(function(r){return r.json()}).then(function(g){geo=g;try{sessionStorage.setItem('agentuity_geo',JSON.stringify(g))}catch(e){}}).catch(function(){try{var cached=sessionStorage.getItem('agentuity_geo');if(cached)geo=JSON.parse(cached)}catch(e){}});try{var cached=sessionStorage.getItem('agentuity_geo');if(cached)geo=JSON.parse(cached)}catch(e){}
297
+ function getSession(){return w.__AGENTUITY_SESSION__}
298
+ function waitForSession(cb){var s=getSession();if(s){cb(s);return}var attempts=0,maxAttempts=50;var iv=setInterval(function(){s=getSession();if(s||++attempts>=maxAttempts){clearInterval(iv);cb(s)}},100)}
299
+ function doFlush(s){if(!q.length)return;var events=q.splice(0);if(c.isDevmode){console.debug('[Agentuity Analytics]',events);return}var sid=s?s.sessionId:'',tid=s?s.threadId:'';var p={org_id:c.orgId,project_id:c.projectId,session_id:sid,thread_id:tid,visitor_id:localStorage.getItem('agentuity_vid')||'vid_'+id(),events:events};try{localStorage.setItem('agentuity_vid',p.visitor_id)}catch(e){}navigator.sendBeacon?navigator.sendBeacon(E,JSON.stringify(p)):fetch(E,{method:'POST',body:JSON.stringify(p),keepalive:true}).catch(function(){})}
300
+ function flush(){if(sr){doFlush(getSession())}else{waitForSession(function(s){sr=true;doFlush(s)})}}
301
+ function queue(e){if(c.sampleRate<1&&Math.random()>c.sampleRate)return;q.push(e);q.length>=10?flush():t||(t=setTimeout(function(){t=null;flush()},5000))}
302
+ function pv(){var e=base('pageview');if(performance.getEntriesByType){var n=performance.getEntriesByType('navigation')[0];if(n){e.load_time=Math.round(n.loadEventEnd-n.startTime);e.dom_ready=Math.round(n.domContentLoadedEventEnd-n.startTime);e.ttfb=Math.round(n.responseStart-n.requestStart)}}queue(e)}
303
+ w.addEventListener('visibilitychange',function(){d.visibilityState==='hidden'&&flush()});
304
+ w.addEventListener('pagehide',flush);
305
+ if(c.trackSPANavigation){var op=history.pushState,or=history.replaceState,cp=location.pathname;function ch(){var np=location.pathname;np!==cp&&(cp=np,pv())}history.pushState=function(){op.apply(this,arguments);ch()};history.replaceState=function(){or.apply(this,arguments);ch()};w.addEventListener('popstate',ch)}
306
+ if(c.trackErrors){w.addEventListener('error',function(e){var ev=base('error');ev.event_name='js_error';ev.event_data={message:e.message||'Unknown',filename:e.filename||'',lineno:e.lineno||0};queue(ev)});w.addEventListener('unhandledrejection',function(e){var ev=base('error');ev.event_name='unhandled_rejection';ev.event_data={message:e.reason instanceof Error?e.reason.message:String(e.reason)};queue(ev)})}
307
+ if(c.trackClicks){d.addEventListener('click',function(e){var t=e.target;if(!t)return;var a=t.closest('[data-analytics]');if(!a)return;var ev=base('click');ev.event_name=a.getAttribute('data-analytics');queue(ev)},true)}
308
+ if(c.trackScroll){var ms=new Set(),mx=0;function gs(){var st=w.scrollY||d.documentElement.scrollTop,sh=d.documentElement.scrollHeight-d.documentElement.clientHeight;return sh<=0?100:Math.min(100,Math.round(st/sh*100))}w.addEventListener('scroll',function(){var dp=gs();if(dp>mx)mx=dp;[25,50,75,100].forEach(function(m){if(dp>=m&&!ms.has(m)){ms.add(m);var ev=base('scroll');ev.event_name='scroll_'+m;ev.scroll_depth=m;queue(ev)}})},{passive:true})}
309
+ if(c.trackWebVitals!==false&&typeof PerformanceObserver!=='undefined'){var wvLcp=0,wvCls=0,wvInp=0,wvPath=location.pathname,wvSent={};function wvReset(){wvLcp=0;wvCls=0;wvInp=0;wvSent={}}function wvSend(){var p=wvPath;if(wvLcp>0&&!wvSent.lcp){wvSent.lcp=1;var ev=base('web_vital');ev.event_name='lcp';ev.lcp=Math.round(wvLcp);ev.path=p;queue(ev)}if(!wvSent.cls){wvSent.cls=1;var ev=base('web_vital');ev.event_name='cls';ev.cls=Math.round(wvCls*1000)/1000;ev.path=p;queue(ev)}if(wvInp>0&&!wvSent.inp){wvSent.inp=1;var ev=base('web_vital');ev.event_name='inp';ev.inp=Math.round(wvInp);ev.path=p;queue(ev)}flush()}try{var fcpObs=new PerformanceObserver(function(l){l.getEntries().forEach(function(e){if(e.name==='first-contentful-paint'){var ev=base('web_vital');ev.event_name='fcp';ev.fcp=Math.round(e.startTime);queue(ev);flush();fcpObs.disconnect()}})});fcpObs.observe({type:'paint',buffered:true})}catch(e){}try{new PerformanceObserver(function(l){var entries=l.getEntries();if(entries.length)wvLcp=entries[entries.length-1].startTime}).observe({type:'largest-contentful-paint',buffered:true})}catch(e){}try{new PerformanceObserver(function(l){l.getEntries().forEach(function(e){if(!e.hadRecentInput&&e.value)wvCls+=e.value})}).observe({type:'layout-shift',buffered:true})}catch(e){}try{new PerformanceObserver(function(l){l.getEntries().forEach(function(e){if(e.duration&&e.duration>wvInp)wvInp=e.duration})}).observe({type:'event',buffered:true})}catch(e){}d.addEventListener('visibilitychange',function(){if(d.visibilityState==='hidden')wvSend()});w.addEventListener('pagehide',wvSend);if(c.trackSPANavigation){var wvOp=history.pushState,wvOr=history.replaceState;function wvNav(){var np=location.pathname;if(np!==wvPath){wvSend();wvPath=np;wvReset()}}history.pushState=function(){wvOp.apply(this,arguments);wvNav()};history.replaceState=function(){wvOr.apply(this,arguments);wvNav()};w.addEventListener('popstate',wvNav)}}
310
+ d.readyState==='complete'?pv():w.addEventListener('load',pv);
311
+ w.agentuityAnalytics={track:function(n,p){var e=base('custom');e.event_name=n;if(p)e.event_data=p;queue(e)},flush:flush};
312
+ })();\`;
313
+
314
+ return new Response(beaconScript, {
315
+ headers: {
316
+ 'Content-Type': 'application/javascript; charset=utf-8',
317
+ 'Cache-Control': 'public, max-age=3600',
318
+ },
319
+ });
320
+ });
321
+ }
322
+ `
323
+ : `
324
+ // Analytics disabled
325
+ function injectAnalytics(html: string): string {
326
+ return html;
327
+ }
328
+ function registerAnalyticsRoutes(_app: ReturnType<typeof createRouter>): void {}
203
329
  `;
204
330
 
205
331
  // Web routes (runtime mode detection)
@@ -207,6 +333,9 @@ const isDevelopment = () => getEnv('NODE' + '_' + 'ENV') !== 'production';
207
333
  if (hasWebFrontend) {
208
334
  webRoutes = `
209
335
  // Web routes - Runtime mode detection (dev proxies to Vite, prod serves static)
336
+ // Note: Session/thread cookies are set by /_agentuity/webanalytics/session.js (loaded via script tag)
337
+ // This keeps the HTML response static and cacheable
338
+
210
339
  if (isDevelopment()) {
211
340
  // Development mode: Proxy HTML from Vite to enable React Fast Refresh
212
341
  const VITE_ASSET_PORT = parseInt(process.env.VITE_PORT || '${vitePort || 5173}', 10);
@@ -219,12 +348,15 @@ if (isDevelopment()) {
219
348
  const res = await fetch(viteUrl, { signal: AbortSignal.timeout(10000) });
220
349
 
221
350
  // Get HTML text and transform relative paths to absolute
222
- const html = await res.text();
223
- const transformedHtml = html
351
+ let html = await res.text();
352
+ html = html
224
353
  .replace(/src="\\.\\//g, 'src="/src/web/')
225
354
  .replace(/href="\\.\\//g, 'href="/src/web/');
226
355
 
227
- return new Response(transformedHtml, {
356
+ // Inject analytics config and script (session/thread read from cookies by beacon)
357
+ html = injectAnalytics(html);
358
+
359
+ return new Response(html, {
228
360
  status: res.status,
229
361
  headers: res.headers,
230
362
  });
@@ -255,15 +387,24 @@ if (isDevelopment()) {
255
387
  } else {
256
388
  // Production mode: Serve static files from bundled output
257
389
  const indexHtmlPath = import.meta.dir + '/client/index.html';
258
- const indexHtml = existsSync(indexHtmlPath)
390
+ const baseIndexHtml = existsSync(indexHtmlPath)
259
391
  ? readFileSync(indexHtmlPath, 'utf-8')
260
392
  : '';
261
393
 
262
- if (!indexHtml) {
394
+ if (!baseIndexHtml) {
263
395
  otel.logger.warn('Production HTML not found at %s', indexHtmlPath);
264
396
  }
397
+
398
+ const prodHtmlHandler = (c: Context) => {
399
+ if (!baseIndexHtml) {
400
+ return c.text('Production build incomplete', 500);
401
+ }
402
+ // Inject analytics config and script (session/thread loaded via session.js)
403
+ const html = injectAnalytics(baseIndexHtml);
404
+ return c.html(html);
405
+ };
265
406
 
266
- app.get('/', (c: Context) => indexHtml ? c.html(indexHtml) : c.text('Production build incomplete', 500));
407
+ app.get('/', prodHtmlHandler);
267
408
 
268
409
  // Serve static assets from /assets/* (Vite bundled output)
269
410
  app.use('/assets/*', serveStatic({ root: import.meta.dir + '/client' }));
@@ -285,7 +426,7 @@ if (isDevelopment()) {
285
426
  if (/\\.[a-zA-Z0-9]+$/.test(path)) {
286
427
  return c.notFound();
287
428
  }
288
- return c.html(indexHtml);
429
+ return prodHtmlHandler(c);
289
430
  });
290
431
  }
291
432
  `;
@@ -412,6 +553,8 @@ ${imports.join('\n')}
412
553
 
413
554
  ${modeDetection}
414
555
 
556
+ ${analyticsHelper}
557
+
415
558
  // Step 0: Bootstrap runtime environment (load profile-specific .env files)
416
559
  // Only in development - production env vars are injected by platform
417
560
  // This must happen BEFORE any imports that depend on environment variables
@@ -482,6 +625,10 @@ await sessionProvider.initialize(appState);
482
625
  // Step 6: Mount routes (AFTER middleware is applied)
483
626
 
484
627
  ${healthRoutes}
628
+
629
+ // Register analytics routes (if enabled)
630
+ registerAnalyticsRoutes(app);
631
+
485
632
  ${assetProxyRoutes}
486
633
  ${apiMount}
487
634
  ${workbenchApiMount}
@@ -65,7 +65,7 @@ export interface AgentMetadata {
65
65
 
66
66
  export interface EvalMetadata {
67
67
  id: string;
68
- evalId: string;
68
+ identifier: string;
69
69
  name: string;
70
70
  filename: string;
71
71
  version: string;
@@ -311,6 +311,7 @@ function extractAgentMetadata(
311
311
  */
312
312
  async function extractEvalMetadata(
313
313
  evalsPath: string,
314
+ relativeEvalsPath: string,
314
315
  agentId: string,
315
316
  projectId: string,
316
317
  deploymentId: string,
@@ -325,7 +326,7 @@ async function extractEvalMetadata(
325
326
  const evalsSource = await evalsFile.text();
326
327
  return extractEvalsFromSource(
327
328
  evalsSource,
328
- evalsPath,
329
+ relativeEvalsPath,
329
330
  agentId,
330
331
  projectId,
331
332
  deploymentId,
@@ -459,13 +460,13 @@ function extractEvalsFromSource(
459
460
 
460
461
  if (evalName) {
461
462
  const id = getEvalId(projectId, deploymentId, filename, evalName, version);
462
- const evalId = generateStableEvalId(projectId, agentId, evalName);
463
+ const identifier = generateStableEvalId(projectId, agentId, evalName);
463
464
 
464
- logger.trace(`Found eval '${evalName}' in ${filename} (evalId: ${evalId})`);
465
+ logger.trace(`Found eval '${evalName}' in ${filename} (identifier: ${identifier})`);
465
466
 
466
467
  evals.push({
467
468
  id,
468
- evalId,
469
+ identifier,
469
470
  name: evalName,
470
471
  filename,
471
472
  version,
@@ -569,8 +570,10 @@ export async function discoverAgents(
569
570
  // 2. Check for evals in separate eval.ts file in same directory
570
571
  const agentDir = dirname(filePath);
571
572
  const evalsPath = join(agentDir, 'eval.ts');
573
+ const relativeEvalsPath = relative(rootDir, evalsPath);
572
574
  const evalsInSeparateFile = await extractEvalMetadata(
573
575
  evalsPath,
576
+ relativeEvalsPath,
574
577
  agentMetadata.agentId,
575
578
  projectId,
576
579
  deploymentId,
@@ -87,7 +87,7 @@ export function agentuityPlugin(options: AgentuityPluginOptions): Plugin {
87
87
  }
88
88
 
89
89
  if (routeInfoList.length > 0) {
90
- generateRouteRegistry(srcDir, routeInfoList, agents);
90
+ await generateRouteRegistry(srcDir, routeInfoList, agents);
91
91
  logger.trace('Generated route registry with %d route(s)', routeInfoList.length);
92
92
  }
93
93
 
@@ -96,7 +96,7 @@ export function agentuityPlugin(options: AgentuityPluginOptions): Plugin {
96
96
  const lifecycleResult = await generateLifecycleTypes(rootDir, srcDir, logger);
97
97
  logger.debug(`[vite-plugin] generateLifecycleTypes returned: ${lifecycleResult}`);
98
98
 
99
- // Generate entry file (pass workbench config for route mounting)
99
+ // Generate entry file (pass workbench and analytics config)
100
100
  await generateEntryFile({
101
101
  rootDir,
102
102
  projectId,
@@ -104,6 +104,7 @@ export function agentuityPlugin(options: AgentuityPluginOptions): Plugin {
104
104
  logger,
105
105
  mode: dev ? 'dev' : 'prod',
106
106
  workbench: workbenchConfig.enabled ? workbenchConfig : undefined,
107
+ analytics: config?.analytics,
107
108
  });
108
109
 
109
110
  logger.trace('buildStart: Discovery complete');
@@ -443,7 +443,7 @@ export async function generateMetadata(options: MetadataGeneratorOptions): Promi
443
443
  evals: agent.evals?.map((evalItem) => ({
444
444
  filename: evalItem.filename,
445
445
  id: evalItem.id,
446
- evalId: evalItem.evalId,
446
+ identifier: evalItem.identifier,
447
447
  name: evalItem.name,
448
448
  version: evalItem.version,
449
449
  description: evalItem.description,
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { join } from 'node:path';
8
8
  import { writeFileSync, mkdirSync, existsSync, unlinkSync, readFileSync } from 'node:fs';
9
+ import { stat } from 'node:fs/promises';
9
10
  import { StructuredError } from '@agentuity/core';
10
11
  import { toCamelCase, toPascalCase } from '../../../utils/string';
11
12
  import type { AgentMetadata } from './agent-discovery';
@@ -29,6 +30,28 @@ function sanitizePathSegment(segment: string): string {
29
30
  return toCamelCase(segment.replace(ROUTE_PARAM_CHARS, ''));
30
31
  }
31
32
 
33
+ /**
34
+ * Generate TypeScript type for path parameters.
35
+ * Returns 'never' if no path params, or '{ param1: string; param2: string }' format.
36
+ */
37
+ function generatePathParamsType(pathParams?: string[]): string {
38
+ if (!pathParams || pathParams.length === 0) {
39
+ return 'never';
40
+ }
41
+ return `{ ${pathParams.map((p) => `${p}: string`).join('; ')} }`;
42
+ }
43
+
44
+ /**
45
+ * Generate TypeScript tuple type for path parameters (for positional args).
46
+ * Returns '[]' if no path params, or '[string, string]' format.
47
+ */
48
+ function generatePathParamsTupleType(pathParams?: string[]): string {
49
+ if (!pathParams || pathParams.length === 0) {
50
+ return '[]';
51
+ }
52
+ return `[${pathParams.map(() => 'string').join(', ')}]`;
53
+ }
54
+
32
55
  /**
33
56
  * Generate src/generated/registry.ts with agent registry and types
34
57
  */
@@ -61,6 +84,43 @@ export function generateAgentRegistry(srcDir: string, agents: AgentMetadata[]):
61
84
  });
62
85
  }
63
86
 
87
+ // Collect eval files that need to be imported for createEval calls to run
88
+ // These are eval.ts files in the same directory as agents that have evals
89
+ const evalImports: string[] = [];
90
+ const seenEvalPaths = new Set<string>();
91
+
92
+ for (const agent of sortedAgents) {
93
+ if (agent.evals && agent.evals.length > 0) {
94
+ // Check if any eval comes from a separate eval.ts file (not the agent file itself)
95
+ for (const evalMeta of agent.evals) {
96
+ // Skip if eval is defined in the agent file itself
97
+ if (evalMeta.filename === agent.filename) continue;
98
+
99
+ // Build the relative path for the eval file
100
+ let evalRelativePath = evalMeta.filename;
101
+ if (evalRelativePath.startsWith('./agent/')) {
102
+ evalRelativePath = evalRelativePath
103
+ .replace(/^\.\/agent\//, '../agent/')
104
+ .replace(/\.tsx?$/, '.js');
105
+ } else if (evalRelativePath.startsWith('src/agent/')) {
106
+ evalRelativePath = evalRelativePath
107
+ .replace(/^src\/agent\//, '../agent/')
108
+ .replace(/\.tsx?$/, '.js');
109
+ } else if (evalRelativePath.includes('/src/agent/')) {
110
+ // Handle absolute paths by extracting the relative part
111
+ evalRelativePath = evalRelativePath
112
+ .replace(/^.*\/src\/agent\//, '../agent/')
113
+ .replace(/\.tsx?$/, '.js');
114
+ }
115
+ // Avoid duplicate imports
116
+ if (!seenEvalPaths.has(evalRelativePath)) {
117
+ seenEvalPaths.add(evalRelativePath);
118
+ evalImports.push(`import '${evalRelativePath}';`);
119
+ }
120
+ }
121
+ }
122
+ }
123
+
64
124
  // Generate imports for all agents
65
125
  const imports = sortedAgents
66
126
  .map(({ name, filename }) => {
@@ -149,11 +209,21 @@ export function generateAgentRegistry(srcDir: string, agents: AgentMetadata[]):
149
209
  })
150
210
  .join('\n');
151
211
 
212
+ // Build eval imports section (side-effect imports for createEval registration)
213
+ const evalImportsSection =
214
+ evalImports.length > 0
215
+ ? `
216
+ // Eval file imports (side-effect imports to register evals via createEval)
217
+ ${evalImports.join('\n')}
218
+ `
219
+ : '';
220
+
152
221
  const generatedContent = `// @generated
153
222
  // Auto-generated by Agentuity - DO NOT EDIT
154
223
  ${imports}
155
224
  import type { AgentRunner } from '@agentuity/runtime';
156
225
  import type { InferInput, InferOutput } from '@agentuity/core';
226
+ ${evalImportsSection}
157
227
 
158
228
  // ============================================================================
159
229
  // Schema Type Exports
@@ -362,8 +432,10 @@ function generateRPCRegistryType(
362
432
  jsdoc.push(`${indent} */`);
363
433
  lines.push(...jsdoc);
364
434
 
435
+ const pathParamsType = generatePathParamsType(routeInfo.pathParams);
436
+ const pathParamsTupleType = generatePathParamsTupleType(routeInfo.pathParams);
365
437
  lines.push(
366
- `${indent}${key}: { input: ${value.input}; output: ${value.output}; type: ${value.type} };`
438
+ `${indent}${key}: { input: ${value.input}; output: ${value.output}; type: ${value.type}; params: ${pathParamsType}; paramsTuple: ${pathParamsTupleType} };`
367
439
  );
368
440
  } else {
369
441
  // Nested node
@@ -393,7 +465,7 @@ function generateRPCRuntimeMetadata(
393
465
  sseRoutes: RouteInfo[]
394
466
  ): string {
395
467
  interface MetadataNode {
396
- [key: string]: MetadataNode | { type: string };
468
+ [key: string]: MetadataNode | { type: string; path: string; pathParams?: string[] };
397
469
  }
398
470
 
399
471
  const tree: MetadataNode = {};
@@ -431,7 +503,14 @@ function generateRPCRuntimeMetadata(
431
503
  ? 'stream'
432
504
  : route.method.toLowerCase();
433
505
 
434
- current[terminalMethod] = { type: routeType };
506
+ const metadata: { type: string; path: string; pathParams?: string[] } = {
507
+ type: routeType,
508
+ path: route.path,
509
+ };
510
+ if (route.pathParams && route.pathParams.length > 0) {
511
+ metadata.pathParams = route.pathParams;
512
+ }
513
+ current[terminalMethod] = metadata;
435
514
  };
436
515
 
437
516
  apiRoutes.forEach((r) => addRoute(r, r.routeType === 'stream' ? 'stream' : 'api'));
@@ -461,15 +540,15 @@ function generateRPCRuntimeMetadata(
461
540
  * Creates a module augmentation for @agentuity/react that provides
462
541
  * strongly-typed route keys with input/output schema information.
463
542
  */
464
- export function generateRouteRegistry(
543
+ export async function generateRouteRegistry(
465
544
  srcDir: string,
466
545
  routes: RouteInfo[],
467
546
  agents: AgentMetadata[] = []
468
- ): void {
469
- // Check if project uses @agentuity/react
547
+ ): Promise<void> {
470
548
  const projectRoot = join(srcDir, '..');
471
549
  const packageJsonPath = join(projectRoot, 'package.json');
472
550
  let hasReactDependency = false;
551
+ let hasFrontendDependency = false;
473
552
 
474
553
  try {
475
554
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
@@ -477,10 +556,25 @@ export function generateRouteRegistry(
477
556
  packageJson.dependencies?.['@agentuity/react'] ||
478
557
  packageJson.devDependencies?.['@agentuity/react']
479
558
  );
559
+ hasFrontendDependency = !!(
560
+ packageJson.dependencies?.['@agentuity/frontend'] ||
561
+ packageJson.devDependencies?.['@agentuity/frontend']
562
+ );
563
+ } catch {
564
+ // If we can't read package.json, assume no frontend dependencies
565
+ }
566
+
567
+ const webDir = join(srcDir, 'web');
568
+ let hasWebDirectory = false;
569
+ try {
570
+ const webDirStat = await stat(webDir);
571
+ hasWebDirectory = webDirStat.isDirectory();
480
572
  } catch {
481
- // If we can't read package.json, assume no React dependency
573
+ // Directory doesn't exist
482
574
  }
483
575
 
576
+ const shouldEmitFrontendClient = hasFrontendDependency && !hasReactDependency && hasWebDirectory;
577
+
484
578
  // Filter routes by type and sort by path for deterministic output
485
579
  const sortByPath = (a: RouteInfo, b: RouteInfo) => a.path.localeCompare(b.path);
486
580
  const apiRoutes = routes
@@ -769,13 +863,18 @@ export function generateRouteRegistry(
769
863
  // because only 'json' validators extract input schemas
770
864
  // Also check if agentVariable exists but import wasn't added (missing agentImportPath)
771
865
  const hasValidAgentImport = route.agentVariable ? !!importName : false;
866
+
867
+ // Generate pathParams type
868
+ const pathParamsType = generatePathParamsType(route.pathParams);
869
+
772
870
  if (!route.inputSchemaVariable && !route.outputSchemaVariable && !hasValidAgentImport) {
773
871
  const streamValue = route.stream === true ? 'true' : 'false';
774
872
  return `\t'${routeKey}': {
775
- \t\tinputSchema: never;
776
- \t\toutputSchema: never;
777
- \t\tstream: ${streamValue};
778
- \t};`;
873
+ \t\tinputSchema: never;
874
+ \t\toutputSchema: never;
875
+ \t\tstream: ${streamValue};
876
+ \t\tparams: ${pathParamsType};
877
+ \t};`;
779
878
  }
780
879
  const streamValue = importName
781
880
  ? `typeof ${importName} extends { stream?: infer S } ? S : false`
@@ -784,10 +883,11 @@ export function generateRouteRegistry(
784
883
  : 'false';
785
884
 
786
885
  return `\t'${routeKey}': {
787
- \t\tinputSchema: ${pascalName}InputSchema;
788
- \t\toutputSchema: ${pascalName}OutputSchema;
789
- \t\tstream: ${streamValue};
790
- \t};`;
886
+ \t\tinputSchema: ${pascalName}InputSchema;
887
+ \t\toutputSchema: ${pascalName}OutputSchema;
888
+ \t\tstream: ${streamValue};
889
+ \t\tparams: ${pathParamsType};
890
+ \t};`;
791
891
  };
792
892
 
793
893
  // Generate route entries with METHOD prefix for API routes
@@ -817,7 +917,7 @@ export function generateRouteRegistry(
817
917
  const generatedContent = `// @generated
818
918
  // Auto-generated by Agentuity - DO NOT EDIT
819
919
  ${importsStr}${typeImports}${
820
- !hasReactDependency
920
+ shouldEmitFrontendClient
821
921
  ? `
822
922
  import { createClient } from '@agentuity/frontend';`
823
923
  : ''
@@ -842,7 +942,7 @@ ${routeSchemaTypes}
842
942
  * Individual route Input/Output types are exported above for direct usage.
843
943
  */
844
944
  ${
845
- !hasReactDependency
945
+ shouldEmitFrontendClient
846
946
  ? `
847
947
  /**
848
948
  * RPC Route Registry
@@ -907,13 +1007,14 @@ if (typeof globalThis !== 'undefined') {
907
1007
  (globalThis as Record<string, unknown>).__rpcRouteMetadata = _rpcRouteMetadata;
908
1008
  }
909
1009
  ${
910
- !hasReactDependency
1010
+ shouldEmitFrontendClient
911
1011
  ? `
912
1012
  /**
913
1013
  * Create a type-safe API client with optional configuration.
914
1014
  *
915
- * This function is only generated when @agentuity/react is not installed.
916
- * If using React, import createAPIClient from '@agentuity/react' instead.
1015
+ * This function is only generated when @agentuity/frontend is installed
1016
+ * but @agentuity/react is not. For React apps, import createAPIClient
1017
+ * from '@agentuity/react' instead.
917
1018
  *
918
1019
  * @example
919
1020
  * \`\`\`typescript
@@ -932,7 +1033,8 @@ export function createAPIClient(options?: Parameters<typeof createClient>[0]): i
932
1033
  return createClient(options || {}, _rpcRouteMetadata) as import('@agentuity/frontend').Client<RPCRouteRegistry>;
933
1034
  }
934
1035
  `
935
- : `
1036
+ : hasReactDependency
1037
+ ? `
936
1038
  /**
937
1039
  * Type-safe API client is available from @agentuity/react
938
1040
  *
@@ -945,6 +1047,7 @@ export function createAPIClient(options?: Parameters<typeof createClient>[0]): i
945
1047
  * \`\`\`
946
1048
  */
947
1049
  `
1050
+ : ''
948
1051
  }
949
1052
 
950
1053
  // FOUND AN ERROR IN THIS FILE?
@@ -955,9 +1058,7 @@ export function createAPIClient(options?: Parameters<typeof createClient>[0]): i
955
1058
  const generatedDir = join(srcDir, 'generated');
956
1059
  const registryPath = join(generatedDir, 'routes.ts');
957
1060
 
958
- if (!existsSync(generatedDir)) {
959
- mkdirSync(generatedDir, { recursive: true });
960
- }
1061
+ mkdirSync(generatedDir, { recursive: true });
961
1062
 
962
1063
  // Collapse 2+ consecutive empty lines into 1 empty line (3+ \n becomes 2 \n)
963
1064
  const cleanedContent = generatedContent.replace(/\n{3,}/g, '\n\n');
@@ -40,6 +40,24 @@ export interface RouteInfo {
40
40
  inputSchemaCode?: string;
41
41
  outputSchemaCode?: string;
42
42
  stream?: boolean;
43
+ pathParams?: string[];
44
+ }
45
+
46
+ /**
47
+ * Extract path parameters from a route path.
48
+ * Matches patterns like :id, :userId, :id?, *path, etc.
49
+ */
50
+ export function extractPathParams(path: string): string[] {
51
+ const params: string[] = [];
52
+ const parts = path.split('/');
53
+ for (const part of parts) {
54
+ if (part.startsWith(':')) {
55
+ params.push(part.replace(/^:|[?+*]$/g, ''));
56
+ } else if (part.startsWith('*') && part.length > 1) {
57
+ params.push(part.substring(1).replace(/[?+*]$/g, ''));
58
+ }
59
+ }
60
+ return params;
43
61
  }
44
62
 
45
63
  /**
@@ -90,6 +108,7 @@ export async function discoverRoutes(
90
108
 
91
109
  // Convert to RouteInfo for registry
92
110
  for (const route of parsedRoutes) {
111
+ const pathParams = extractPathParams(route.path);
93
112
  routeInfoList.push({
94
113
  method: route.method.toUpperCase(),
95
114
  path: route.path,
@@ -106,6 +125,7 @@ export async function discoverRoutes(
106
125
  : route.type === 'stream'
107
126
  ? true
108
127
  : undefined,
128
+ pathParams: pathParams.length > 0 ? pathParams : undefined,
109
129
  });
110
130
  }
111
131
  }