@agentuity/cli 0.1.0 → 0.1.1
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.
- package/dist/cmd/build/entry-generator.d.ts.map +1 -1
- package/dist/cmd/build/entry-generator.js +32 -133
- package/dist/cmd/build/entry-generator.js.map +1 -1
- package/dist/cmd/build/webanalytics-generator.d.ts +16 -0
- package/dist/cmd/build/webanalytics-generator.d.ts.map +1 -0
- package/dist/cmd/build/webanalytics-generator.js +186 -0
- package/dist/cmd/build/webanalytics-generator.js.map +1 -0
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +0 -16
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/tui.d.ts +4 -2
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +8 -5
- package/dist/tui.js.map +1 -1
- package/package.json +5 -5
- package/src/cmd/build/entry-generator.ts +40 -135
- package/src/cmd/build/webanalytics-generator.ts +205 -0
- package/src/cmd/cloud/deploy.ts +1 -19
- package/src/tui.ts +8 -5
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import type { Logger, WorkbenchConfig, AnalyticsConfig } from '../../types';
|
|
8
8
|
import { discoverRoutes } from './vite/route-discovery';
|
|
9
|
+
import { generateWebAnalyticsFile } from './webanalytics-generator';
|
|
9
10
|
|
|
10
11
|
interface GenerateEntryOptions {
|
|
11
12
|
rootDir: string;
|
|
@@ -31,6 +32,14 @@ export async function generateEntryFile(options: GenerateEntryOptions): Promise<
|
|
|
31
32
|
|
|
32
33
|
logger.trace(`Generating unified entry file (supports both dev and prod modes)...`);
|
|
33
34
|
|
|
35
|
+
// Check if analytics is enabled
|
|
36
|
+
const analyticsEnabled = analytics !== false;
|
|
37
|
+
|
|
38
|
+
// Generate web analytics files only if enabled
|
|
39
|
+
if (analyticsEnabled) {
|
|
40
|
+
await generateWebAnalyticsFile({ rootDir, logger, analytics });
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
// Discover routes to determine which files need to be imported
|
|
35
44
|
const { routeInfoList } = await discoverRoutes(srcDir, projectId, deploymentId, logger);
|
|
36
45
|
|
|
@@ -73,10 +82,6 @@ export async function generateEntryFile(options: GenerateEntryOptions): Promise<
|
|
|
73
82
|
` createWorkbenchRouter,`,
|
|
74
83
|
` bootstrapRuntimeEnv,`,
|
|
75
84
|
` patchBunS3ForStorageDev,`,
|
|
76
|
-
` getOrganizationId,`,
|
|
77
|
-
` getProjectId,`,
|
|
78
|
-
` isDevMode as runtimeIsDevMode,`,
|
|
79
|
-
` createWebSessionMiddleware,`,
|
|
80
85
|
];
|
|
81
86
|
|
|
82
87
|
const imports = [
|
|
@@ -89,6 +94,10 @@ export async function generateEntryFile(options: GenerateEntryOptions): Promise<
|
|
|
89
94
|
].filter(Boolean);
|
|
90
95
|
|
|
91
96
|
imports.push(`import { type LogLevel } from '@agentuity/core';`);
|
|
97
|
+
if (analyticsEnabled) {
|
|
98
|
+
imports.push(`import { injectAnalytics, registerAnalyticsRoutes } from './webanalytics.js';`);
|
|
99
|
+
imports.push(`import { analyticsConfig } from './analytics-config.js';`);
|
|
100
|
+
}
|
|
92
101
|
|
|
93
102
|
// Generate route mounting code for all discovered routes
|
|
94
103
|
// Sort route files for deterministic output
|
|
@@ -206,126 +215,6 @@ if (isDevelopment() && process.env.VITE_PORT) {
|
|
|
206
215
|
// See: https://github.com/oven-sh/bun/issues/20183
|
|
207
216
|
const getEnv = (key: string) => process.env[key];
|
|
208
217
|
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 {}
|
|
329
218
|
`;
|
|
330
219
|
|
|
331
220
|
// Web routes (runtime mode detection)
|
|
@@ -353,8 +242,12 @@ if (isDevelopment()) {
|
|
|
353
242
|
.replace(/src="\\.\\//g, 'src="/src/web/')
|
|
354
243
|
.replace(/href="\\.\\//g, 'href="/src/web/');
|
|
355
244
|
|
|
356
|
-
|
|
357
|
-
|
|
245
|
+
${
|
|
246
|
+
analyticsEnabled
|
|
247
|
+
? ` // Inject analytics config and script (session/thread read from cookies by beacon)
|
|
248
|
+
html = injectAnalytics(html, analyticsConfig);`
|
|
249
|
+
: ''
|
|
250
|
+
}
|
|
358
251
|
|
|
359
252
|
return new Response(html, {
|
|
360
253
|
status: res.status,
|
|
@@ -399,9 +292,13 @@ if (isDevelopment()) {
|
|
|
399
292
|
if (!baseIndexHtml) {
|
|
400
293
|
return c.text('Production build incomplete', 500);
|
|
401
294
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
295
|
+
${
|
|
296
|
+
analyticsEnabled
|
|
297
|
+
? ` // Inject analytics config and script (session/thread loaded via session.js)
|
|
298
|
+
const html = injectAnalytics(baseIndexHtml, analyticsConfig);
|
|
299
|
+
return c.html(html);`
|
|
300
|
+
: ` return c.html(baseIndexHtml);`
|
|
301
|
+
}
|
|
405
302
|
};
|
|
406
303
|
|
|
407
304
|
app.get('/', prodHtmlHandler);
|
|
@@ -553,8 +450,6 @@ ${imports.join('\n')}
|
|
|
553
450
|
|
|
554
451
|
${modeDetection}
|
|
555
452
|
|
|
556
|
-
${analyticsHelper}
|
|
557
|
-
|
|
558
453
|
// Step 0: Bootstrap runtime environment (load profile-specific .env files)
|
|
559
454
|
// Only in development - production env vars are injected by platform
|
|
560
455
|
// This must happen BEFORE any imports that depend on environment variables
|
|
@@ -589,11 +484,17 @@ app.use('*', createBaseMiddleware({
|
|
|
589
484
|
meter: otel.meter,
|
|
590
485
|
}));
|
|
591
486
|
|
|
592
|
-
app.use('/_agentuity/*', createCorsMiddleware());
|
|
487
|
+
app.use('/_agentuity/workbench/*', createCorsMiddleware());
|
|
593
488
|
app.use('/api/*', createCorsMiddleware());
|
|
594
489
|
|
|
595
490
|
// Critical: otelMiddleware creates session/thread/waitUntilHandler
|
|
596
|
-
|
|
491
|
+
// Only apply to routes that need full session tracking:
|
|
492
|
+
// - /api/* routes (agent/API invocations)
|
|
493
|
+
// - /_agentuity/workbench/* routes (workbench API)
|
|
494
|
+
// Explicitly excluded (no session tracking, no Catalyst events):
|
|
495
|
+
// - /_agentuity/webanalytics/* (web analytics - uses lightweight cookie-only middleware)
|
|
496
|
+
// - /_agentuity/health, /_agentuity/ready, /_agentuity/idle (health checks)
|
|
497
|
+
app.use('/_agentuity/workbench/*', createOtelMiddleware());
|
|
597
498
|
app.use('/api/*', createOtelMiddleware());
|
|
598
499
|
|
|
599
500
|
// Critical: agentMiddleware sets up agent context
|
|
@@ -626,8 +527,12 @@ await sessionProvider.initialize(appState);
|
|
|
626
527
|
|
|
627
528
|
${healthRoutes}
|
|
628
529
|
|
|
629
|
-
|
|
630
|
-
|
|
530
|
+
${
|
|
531
|
+
analyticsEnabled
|
|
532
|
+
? `// Register analytics routes
|
|
533
|
+
registerAnalyticsRoutes(app);`
|
|
534
|
+
: ''
|
|
535
|
+
}
|
|
631
536
|
|
|
632
537
|
${assetProxyRoutes}
|
|
633
538
|
${apiMount}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web analytics beacon code generator
|
|
3
|
+
* Generates src/generated/webanalytics.ts with the analytics beacon script
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import type { Logger, AnalyticsConfig } from '../../types';
|
|
8
|
+
|
|
9
|
+
interface GenerateWebAnalyticsOptions {
|
|
10
|
+
rootDir: string;
|
|
11
|
+
logger: Logger;
|
|
12
|
+
analytics?: boolean | AnalyticsConfig;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate the web analytics files (webanalytics.ts and analytics-config.ts)
|
|
17
|
+
*/
|
|
18
|
+
export async function generateWebAnalyticsFile(
|
|
19
|
+
options: GenerateWebAnalyticsOptions
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const { rootDir, logger, analytics } = options;
|
|
22
|
+
|
|
23
|
+
const srcDir = join(rootDir, 'src');
|
|
24
|
+
const generatedDir = join(srcDir, 'generated');
|
|
25
|
+
const analyticsPath = join(generatedDir, 'webanalytics.ts');
|
|
26
|
+
const configPath = join(generatedDir, 'analytics-config.ts');
|
|
27
|
+
|
|
28
|
+
logger.trace(`Generating web analytics files...`);
|
|
29
|
+
|
|
30
|
+
const analyticsEnabled = analytics !== false;
|
|
31
|
+
const analyticsConfig: AnalyticsConfig = typeof analytics === 'object' ? analytics : {};
|
|
32
|
+
|
|
33
|
+
// Generate the analytics config file with resolved values
|
|
34
|
+
const configCode = generateAnalyticsConfigCode(analyticsEnabled, analyticsConfig);
|
|
35
|
+
await Bun.write(configPath, configCode);
|
|
36
|
+
|
|
37
|
+
// Generate the webanalytics file
|
|
38
|
+
const code = analyticsEnabled ? getEnabledAnalyticsCode() : getDisabledAnalyticsCode();
|
|
39
|
+
await Bun.write(analyticsPath, code);
|
|
40
|
+
|
|
41
|
+
logger.trace(`Generated web analytics files at %s`, generatedDir);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function generateAnalyticsConfigCode(enabled: boolean, config: AnalyticsConfig): string {
|
|
45
|
+
return `// @generated
|
|
46
|
+
// Auto-generated by Agentuity
|
|
47
|
+
// DO NOT EDIT - This file is regenerated on every build
|
|
48
|
+
|
|
49
|
+
export interface AnalyticsConfig {
|
|
50
|
+
enabled: boolean;
|
|
51
|
+
requireConsent: boolean;
|
|
52
|
+
trackClicks: boolean;
|
|
53
|
+
trackScroll: boolean;
|
|
54
|
+
trackOutboundLinks: boolean;
|
|
55
|
+
trackForms: boolean;
|
|
56
|
+
trackWebVitals: boolean;
|
|
57
|
+
trackErrors: boolean;
|
|
58
|
+
trackSPANavigation: boolean;
|
|
59
|
+
sampleRate: number;
|
|
60
|
+
excludePatterns: string[];
|
|
61
|
+
globalProperties: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const analyticsConfig: AnalyticsConfig = {
|
|
65
|
+
enabled: ${enabled && config.enabled !== false},
|
|
66
|
+
requireConsent: ${config.requireConsent ?? false},
|
|
67
|
+
trackClicks: ${config.trackClicks ?? true},
|
|
68
|
+
trackScroll: ${config.trackScroll ?? true},
|
|
69
|
+
trackOutboundLinks: ${config.trackOutboundLinks ?? true},
|
|
70
|
+
trackForms: ${config.trackForms ?? false},
|
|
71
|
+
trackWebVitals: ${config.trackWebVitals ?? true},
|
|
72
|
+
trackErrors: ${config.trackErrors ?? true},
|
|
73
|
+
trackSPANavigation: ${config.trackSPANavigation ?? true},
|
|
74
|
+
sampleRate: ${config.sampleRate ?? 1},
|
|
75
|
+
excludePatterns: ${JSON.stringify(config.excludePatterns ?? [])},
|
|
76
|
+
globalProperties: ${JSON.stringify(config.globalProperties ?? {})},
|
|
77
|
+
};
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getDisabledAnalyticsCode(): string {
|
|
82
|
+
return `// @generated
|
|
83
|
+
// Auto-generated by Agentuity
|
|
84
|
+
// DO NOT EDIT - This file is regenerated on every build
|
|
85
|
+
|
|
86
|
+
import { createRouter } from '@agentuity/runtime';
|
|
87
|
+
import type { AnalyticsConfig } from './analytics-config';
|
|
88
|
+
|
|
89
|
+
// Analytics disabled
|
|
90
|
+
export function injectAnalytics(html: string, _config: AnalyticsConfig): string {
|
|
91
|
+
return html;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function registerAnalyticsRoutes(_app: ReturnType<typeof createRouter>): void {}
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getEnabledAnalyticsCode(): string {
|
|
99
|
+
return `// @generated
|
|
100
|
+
// Auto-generated by Agentuity
|
|
101
|
+
// DO NOT EDIT - This file is regenerated on every build
|
|
102
|
+
|
|
103
|
+
import type { Context } from 'hono';
|
|
104
|
+
import {
|
|
105
|
+
createRouter,
|
|
106
|
+
createWebSessionMiddleware,
|
|
107
|
+
getOrganizationId,
|
|
108
|
+
getProjectId,
|
|
109
|
+
isDevMode as runtimeIsDevMode,
|
|
110
|
+
} from '@agentuity/runtime';
|
|
111
|
+
import type { AnalyticsConfig } from './analytics-config';
|
|
112
|
+
|
|
113
|
+
// Inject analytics config and script into HTML
|
|
114
|
+
// Note: Only static config is injected (org, project, devmode, tracking options)
|
|
115
|
+
// Session and thread IDs are read from cookies by the beacon script
|
|
116
|
+
export function injectAnalytics(html: string, analyticsConfig: AnalyticsConfig): string {
|
|
117
|
+
if (!analyticsConfig.enabled) return html;
|
|
118
|
+
|
|
119
|
+
const orgId = getOrganizationId() || '';
|
|
120
|
+
const projectId = getProjectId() || '';
|
|
121
|
+
const isDevmode = runtimeIsDevMode();
|
|
122
|
+
|
|
123
|
+
// Only include static config - session/thread come from cookies
|
|
124
|
+
const pageConfig = {
|
|
125
|
+
...analyticsConfig,
|
|
126
|
+
orgId,
|
|
127
|
+
projectId,
|
|
128
|
+
isDevmode,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const configScript = \`<script>window.__AGENTUITY_ANALYTICS__=\${JSON.stringify(pageConfig)};</script>\`;
|
|
132
|
+
// Session script sets cookies and window.__AGENTUITY_SESSION__ (dynamic, not cached)
|
|
133
|
+
const sessionScript = '<script src="/_agentuity/webanalytics/session.js" async></script>';
|
|
134
|
+
// Beacon script reads from __AGENTUITY_SESSION__ and sends events (static, cached)
|
|
135
|
+
const beaconScript = '<script src="/_agentuity/webanalytics/analytics.js" async></script>';
|
|
136
|
+
const injection = configScript + sessionScript + beaconScript;
|
|
137
|
+
|
|
138
|
+
// Inject before </head> or at start of <body>
|
|
139
|
+
if (html.includes('</head>')) {
|
|
140
|
+
return html.replace('</head>', injection + '</head>');
|
|
141
|
+
}
|
|
142
|
+
if (html.includes('<body')) {
|
|
143
|
+
return html.replace(/<body([^>]*)>/, \`<body$1>\${injection}\`);
|
|
144
|
+
}
|
|
145
|
+
return injection + html;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// The beacon script - minified for production
|
|
149
|
+
export const BEACON_SCRIPT = \`(function(){
|
|
150
|
+
var w=window,d=document,c=w.__AGENTUITY_ANALYTICS__;
|
|
151
|
+
if(!c||!c.enabled)return;
|
|
152
|
+
var q=[],t=null,sr=false,E='/_agentuity/webanalytics/collect',geo=null;
|
|
153
|
+
function id(){return crypto.randomUUID?crypto.randomUUID():Date.now()+'-'+Math.random().toString(36).substr(2,9)}
|
|
154
|
+
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}
|
|
155
|
+
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){}
|
|
156
|
+
function getSession(){return w.__AGENTUITY_SESSION__}
|
|
157
|
+
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)}
|
|
158
|
+
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(){})}
|
|
159
|
+
function flush(){if(sr){doFlush(getSession())}else{waitForSession(function(s){sr=true;doFlush(s)})}}
|
|
160
|
+
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))}
|
|
161
|
+
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)}
|
|
162
|
+
w.addEventListener('visibilitychange',function(){d.visibilityState==='hidden'&&flush()});
|
|
163
|
+
w.addEventListener('pagehide',flush);
|
|
164
|
+
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)}
|
|
165
|
+
if(c.trackErrors){w.addEventListener('error',function(e){var ev=base('error');ev.event_name='js_error';ev.event_data=JSON.stringify({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=JSON.stringify({message:e.reason instanceof Error?e.reason.message:String(e.reason)});queue(ev)})}
|
|
166
|
+
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)}
|
|
167
|
+
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})}
|
|
168
|
+
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)}}
|
|
169
|
+
d.readyState==='complete'?pv():w.addEventListener('load',pv);
|
|
170
|
+
w.agentuityAnalytics={track:function(n,p){var e=base('custom');e.event_name=n;if(p)e.event_data=JSON.stringify(p);queue(e)},flush:flush};
|
|
171
|
+
})()\`;
|
|
172
|
+
|
|
173
|
+
// Serve analytics routes
|
|
174
|
+
export function registerAnalyticsRoutes(app: ReturnType<typeof createRouter>): void {
|
|
175
|
+
// Dynamic thread config script - sets cookie and returns thread ID
|
|
176
|
+
// Web analytics only tracks thread ID, not session ID (to avoid polluting sessions table)
|
|
177
|
+
// This endpoint is NOT cached - it generates unique data per request
|
|
178
|
+
app.get('/_agentuity/webanalytics/session.js', createWebSessionMiddleware(), async (c: Context) => {
|
|
179
|
+
// Read from context (cookies aren't readable until the next request)
|
|
180
|
+
const threadId = c.get('_webThreadId') || '';
|
|
181
|
+
|
|
182
|
+
// Use JSON.stringify to safely escape threadId and prevent XSS/injection
|
|
183
|
+
const sessionData = JSON.stringify({ threadId });
|
|
184
|
+
const sessionScript = \`window.__AGENTUITY_SESSION__=\${sessionData};\`;
|
|
185
|
+
|
|
186
|
+
return new Response(sessionScript, {
|
|
187
|
+
headers: {
|
|
188
|
+
'Content-Type': 'application/javascript; charset=utf-8',
|
|
189
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Static beacon script - can be cached
|
|
195
|
+
app.get('/_agentuity/webanalytics/analytics.js', async (c: Context) => {
|
|
196
|
+
return new Response(BEACON_SCRIPT, {
|
|
197
|
+
headers: {
|
|
198
|
+
'Content-Type': 'application/javascript; charset=utf-8',
|
|
199
|
+
'Cache-Control': 'public, max-age=3600',
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
`;
|
|
205
|
+
}
|
package/src/cmd/cloud/deploy.ts
CHANGED
|
@@ -370,25 +370,7 @@ export const deploySubcommand = createSubcommand({
|
|
|
370
370
|
}
|
|
371
371
|
},
|
|
372
372
|
},
|
|
373
|
-
|
|
374
|
-
label: 'Create Deployment',
|
|
375
|
-
run: async () => {
|
|
376
|
-
if (useExistingDeployment) {
|
|
377
|
-
return stepSkipped('using pre-created deployment');
|
|
378
|
-
}
|
|
379
|
-
try {
|
|
380
|
-
deployment = await projectDeploymentCreate(
|
|
381
|
-
apiClient,
|
|
382
|
-
project.projectId,
|
|
383
|
-
project.deployment
|
|
384
|
-
);
|
|
385
|
-
return stepSuccess();
|
|
386
|
-
} catch (ex) {
|
|
387
|
-
const _ex = ex as { message?: string };
|
|
388
|
-
return stepError(_ex.message ?? String(_ex), ex as Error);
|
|
389
|
-
}
|
|
390
|
-
},
|
|
391
|
-
},
|
|
373
|
+
|
|
392
374
|
{
|
|
393
375
|
label: 'Build, Verify and Package',
|
|
394
376
|
run: async () => {
|
package/src/tui.ts
CHANGED
|
@@ -67,14 +67,16 @@ export const ICONS = {
|
|
|
67
67
|
} as const;
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
|
-
* Check if we should treat
|
|
71
|
-
*
|
|
70
|
+
* Check if we should treat the terminal as TTY-like for interactive output
|
|
71
|
+
* (real TTY on stdout or stderr, or FORCE_COLOR set by fork wrapper).
|
|
72
|
+
* Returns false in CI environments since CI terminals often don't support
|
|
73
|
+
* cursor control sequences reliably.
|
|
72
74
|
*/
|
|
73
75
|
export function isTTYLike(): boolean {
|
|
74
76
|
if (process.env.CI) {
|
|
75
77
|
return false;
|
|
76
78
|
}
|
|
77
|
-
return process.stdout.isTTY || process.env.FORCE_COLOR === '1';
|
|
79
|
+
return !!process.stdout.isTTY || !!process.stderr.isTTY || process.env.FORCE_COLOR === '1';
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
/**
|
|
@@ -1120,8 +1122,9 @@ export async function spinner<T>(
|
|
|
1120
1122
|
const outputOptions = getOutputOptions();
|
|
1121
1123
|
const noProgress = outputOptions ? shouldDisableProgress(outputOptions) : false;
|
|
1122
1124
|
|
|
1123
|
-
// If no TTY or progress disabled, just execute
|
|
1124
|
-
|
|
1125
|
+
// If no interactive TTY-like environment or progress disabled, just execute
|
|
1126
|
+
// the callback without animation
|
|
1127
|
+
if (!isTTYLike() || noProgress) {
|
|
1125
1128
|
try {
|
|
1126
1129
|
const result =
|
|
1127
1130
|
options.type === 'progress'
|