@agentuity/cli 0.1.0 → 0.1.2

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 (38) hide show
  1. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  2. package/dist/cmd/build/entry-generator.js +32 -133
  3. package/dist/cmd/build/entry-generator.js.map +1 -1
  4. package/dist/cmd/build/webanalytics-generator.d.ts +16 -0
  5. package/dist/cmd/build/webanalytics-generator.d.ts.map +1 -0
  6. package/dist/cmd/build/webanalytics-generator.js +186 -0
  7. package/dist/cmd/build/webanalytics-generator.js.map +1 -0
  8. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  9. package/dist/cmd/cloud/deploy.js +0 -16
  10. package/dist/cmd/cloud/deploy.js.map +1 -1
  11. package/dist/cmd/dev/index.d.ts.map +1 -1
  12. package/dist/cmd/dev/index.js +8 -0
  13. package/dist/cmd/dev/index.js.map +1 -1
  14. package/dist/cmd/project/auth/shared.d.ts +2 -1
  15. package/dist/cmd/project/auth/shared.d.ts.map +1 -1
  16. package/dist/cmd/project/auth/shared.js +2 -1
  17. package/dist/cmd/project/auth/shared.js.map +1 -1
  18. package/dist/cmd/project/template-flow.js +3 -3
  19. package/dist/cmd/project/template-flow.js.map +1 -1
  20. package/dist/tui.d.ts +4 -2
  21. package/dist/tui.d.ts.map +1 -1
  22. package/dist/tui.js +8 -5
  23. package/dist/tui.js.map +1 -1
  24. package/dist/utils/dependency-checker.d.ts.map +1 -1
  25. package/dist/utils/dependency-checker.js +0 -5
  26. package/dist/utils/dependency-checker.js.map +1 -1
  27. package/dist/version-check.js +2 -2
  28. package/dist/version-check.js.map +1 -1
  29. package/package.json +5 -5
  30. package/src/cmd/build/entry-generator.ts +40 -135
  31. package/src/cmd/build/webanalytics-generator.ts +205 -0
  32. package/src/cmd/cloud/deploy.ts +1 -19
  33. package/src/cmd/dev/index.ts +12 -0
  34. package/src/cmd/project/auth/shared.ts +2 -1
  35. package/src/cmd/project/template-flow.ts +3 -3
  36. package/src/tui.ts +8 -5
  37. package/src/utils/dependency-checker.ts +0 -6
  38. package/src/version-check.ts +2 -2
@@ -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
+ }
@@ -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 () => {
@@ -18,6 +18,8 @@ import { typecheck } from '../build/typecheck';
18
18
  import { createFileWatcher } from './file-watcher';
19
19
  import { regenerateSkillsAsync } from './skills';
20
20
  import { prepareDevLock, releaseLockSync } from './dev-lock';
21
+ import { checkAndUpgradeDependencies } from '../../utils/dependency-checker';
22
+ import { ErrorCode } from '../../errors';
21
23
 
22
24
  const DEFAULT_PORT = 3500;
23
25
  const MIN_PORT = 1024;
@@ -214,6 +216,16 @@ export const command = createCommand({
214
216
  // This is a fallback for cases where the lockfile was corrupted
215
217
  await killLingeringGravityProcesses(logger);
216
218
 
219
+ // Check and upgrade @agentuity/* dependencies if needed
220
+ const upgradeResult = await checkAndUpgradeDependencies(rootDir, logger);
221
+ if (upgradeResult.failed.length > 0) {
222
+ devLock.release();
223
+ tui.fatal(
224
+ `Failed to upgrade dependencies: ${upgradeResult.failed.join(', ')}`,
225
+ ErrorCode.BUILD_FAILED
226
+ );
227
+ }
228
+
217
229
  try {
218
230
  // Setup devmode and gravity (if using public URL)
219
231
  const useMockService = process.env.DEVMODE_SYNC_SERVICE_MOCK === 'true';
@@ -122,7 +122,8 @@ export async function selectOrCreateDatabase(options: {
122
122
  export const AUTH_DEPENDENCIES = {
123
123
  '@agentuity/auth': 'latest',
124
124
  'better-auth': '^1.4.9',
125
- 'drizzle-orm': '^0.44.0',
125
+ 'drizzle-orm': '^0.45.0',
126
+ 'drizzle-kit': '^0.31.0',
126
127
  } as const;
127
128
 
128
129
  /**
@@ -196,7 +196,7 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
196
196
  return;
197
197
  }
198
198
  selectedTemplate = found;
199
- } else if (skipPrompts) {
199
+ } else if (skipPrompts || templates.length === 1) {
200
200
  selectedTemplate = templates[0];
201
201
  } else {
202
202
  let maxLength = 15;
@@ -246,8 +246,8 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
246
246
  logger,
247
247
  });
248
248
 
249
- // Re-display template selection after spinners clear it
250
- if (!skipPrompts) {
249
+ // Re-display template selection after spinners clear it (only if user actually selected)
250
+ if (!skipPrompts && templates.length > 1) {
251
251
  const { symbols, tuiColors } = tui;
252
252
  console.log(`${tuiColors.completed(symbols.completed)} Select a template:`);
253
253
  console.log(`${tuiColors.secondary(symbols.bar)} ${tuiColors.muted(selectedTemplate.name)}`);
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 stdout as a TTY (real TTY or FORCE_COLOR set by fork wrapper)
71
- * Returns false in CI environments since CI terminals don't support cursor control sequences
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 the callback without animation
1124
- if (!process.stderr.isTTY || noProgress) {
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'
@@ -57,12 +57,6 @@ export async function checkAndUpgradeDependencies(
57
57
  failed: [],
58
58
  };
59
59
 
60
- // Skip in CI/non-interactive environments
61
- if (!process.stdin.isTTY) {
62
- logger.debug('Skipping dependency check in non-interactive environment');
63
- return result;
64
- }
65
-
66
60
  const packageJsonPath = join(projectDir, 'package.json');
67
61
  const cliVersion = getVersion();
68
62
 
@@ -139,10 +139,10 @@ async function updateCheckTimestamp(config: Config | null, logger: Logger): Prom
139
139
  */
140
140
  async function performUpgrade(logger: Logger): Promise<void> {
141
141
  try {
142
- // Run upgrade command (it will validate, download, install, etc.)
142
+ // Run upgrade command with --force since user already confirmed via prompt
143
143
  // Use process.execPath to get the actual binary path (not Bun.main which is virtual)
144
144
  logger.info('Starting upgrade...');
145
- await $`${process.execPath} upgrade`.quiet();
145
+ await $`${process.execPath} upgrade --force`.quiet();
146
146
 
147
147
  // If we got here, the upgrade succeeded
148
148
  // Re-run the original command with the new binary