@agent-native/core 0.72.3 → 0.73.0

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 (223) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +38 -0
  3. package/corpus/core/docs/content/skills-guide.md +14 -0
  4. package/corpus/core/docs/content/template-clips.md +7 -1
  5. package/corpus/core/package.json +6 -6
  6. package/corpus/core/src/a2a/client.ts +7 -3
  7. package/corpus/core/src/agent/durable-background.ts +25 -12
  8. package/corpus/core/src/cli/create.ts +1 -1
  9. package/corpus/core/src/cli/skills.ts +450 -19
  10. package/corpus/core/src/client/api-path.ts +28 -0
  11. package/corpus/core/src/client/embed-auth.ts +26 -0
  12. package/corpus/core/src/client/mcp-app-host.ts +6 -0
  13. package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +227 -33
  14. package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
  15. package/corpus/core/src/client/settings/DemoModeSection.tsx +3 -1
  16. package/corpus/core/src/demo/fetch-interceptor.ts +1 -1
  17. package/corpus/core/src/embedding/agent.ts +6 -1
  18. package/corpus/core/src/embedding/bridge.ts +34 -1
  19. package/corpus/core/src/mcp/build-server.ts +38 -10
  20. package/corpus/core/src/mcp/embed-app.ts +120 -45
  21. package/corpus/core/src/mcp/index.ts +5 -0
  22. package/corpus/core/src/mcp/org-directory.ts +70 -11
  23. package/corpus/core/src/mcp/server.ts +14 -0
  24. package/corpus/core/src/mcp-client/config.ts +12 -0
  25. package/corpus/core/src/mcp-client/index.ts +2 -0
  26. package/corpus/core/src/mcp-client/manager.ts +268 -9
  27. package/corpus/core/src/mcp-client/remote-store.ts +154 -0
  28. package/corpus/core/src/mcp-client/routes.ts +2 -0
  29. package/corpus/core/src/server/agent-chat-plugin.ts +8 -0
  30. package/corpus/core/src/server/auth.ts +7 -0
  31. package/corpus/core/src/server/index.ts +1 -0
  32. package/corpus/core/src/server/onboarding-html.ts +95 -9
  33. package/corpus/core/src/shared/mcp-embed-headers.ts +44 -3
  34. package/corpus/core/src/templates/default/AGENTS.md +4 -0
  35. package/corpus/core/src/templates/default/package.json +2 -1
  36. package/corpus/core/src/templates/headless/AGENTS.md +4 -0
  37. package/corpus/core/src/templates/headless/package.json +1 -0
  38. package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -0
  39. package/corpus/core/src/templates/workspace-root/README.md +4 -0
  40. package/corpus/core/src/templates/workspace-root/package.json +1 -0
  41. package/corpus/core/src/vite/client.ts +53 -0
  42. package/corpus/templates/analytics/actions/update-dashboard.ts +3 -4
  43. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +144 -40
  44. package/corpus/templates/analytics/app/global.css +15 -13
  45. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +9 -32
  46. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +0 -26
  47. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +0 -23
  48. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/pivot.ts +65 -2
  49. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +3 -4
  50. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-charts-now-fill-sparse-template-time-series-suppor.md +6 -0
  51. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-rows-now-auto-fit-panels-so-one-two-or-three-chart.md +6 -0
  52. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-traffic-tables-now-show-top-urls-and-shared-clips-.md +6 -0
  53. package/corpus/templates/analytics/changelog/2026-06-24-first-party-signup-and-pageview-panels-now-honor-dashboard-filters.md +5 -0
  54. package/corpus/templates/analytics/changelog/2026-06-24-first-party-template-dashboards-now-include-repeat-users-ret.md +6 -0
  55. package/corpus/templates/analytics/changelog/2026-06-24-retention-charts-now-break-out-1-day-and-7-day-return-rates-.md +6 -0
  56. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +256 -44
  57. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +7 -2
  58. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +35 -8
  59. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +287 -42
  60. package/corpus/templates/assets/.agents/skills/a2a-assets/SKILL.md +17 -10
  61. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +15 -6
  62. package/corpus/templates/assets/AGENTS.md +7 -5
  63. package/corpus/templates/assets/actions/generate-asset.ts +161 -0
  64. package/corpus/templates/assets/actions/list-assets.ts +4 -2
  65. package/corpus/templates/assets/actions/open-asset-picker.ts +84 -3
  66. package/corpus/templates/assets/app/routes/library.tsx +243 -40
  67. package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +1 -1
  68. package/corpus/templates/calendar/server/plugins/auth.ts +12 -0
  69. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +7 -7
  70. package/corpus/templates/clips/AGENTS.md +6 -2
  71. package/corpus/templates/clips/app/components/capture-install-options.tsx +20 -4
  72. package/corpus/templates/clips/app/components/player/player-controls.tsx +62 -32
  73. package/corpus/templates/clips/app/lib/capture-install-options.ts +39 -5
  74. package/corpus/templates/clips/app/routes/download.tsx +2 -2
  75. package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -1
  76. package/corpus/templates/clips/changelog/2026-06-24-inline-slack-playback-controls-no-longer-shift-when-adjustin.md +6 -0
  77. package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-console-log-stream-all-le.md +6 -0
  78. package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-fetch-xhr-network-request.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-06-24-the-chrome-extension-option-now-only-appears-on-supported-hosts.md +5 -0
  80. package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +13 -8
  81. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  82. package/corpus/templates/clips/chrome-extension/scripts/package.ts +15 -2
  83. package/corpus/templates/clips/chrome-extension/src/overlay.css +4 -25
  84. package/corpus/templates/clips/chrome-extension/src/overlay.ts +1 -3
  85. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +9 -0
  86. package/corpus/templates/clips/server/lib/public-agent-context.ts +67 -24
  87. package/corpus/templates/design/AGENTS.md +10 -5
  88. package/corpus/templates/design/actions/connect-assets-mcp.ts +131 -0
  89. package/corpus/templates/design/actions/insert-asset.ts +219 -0
  90. package/corpus/templates/design/actions/view-screen.ts +3 -1
  91. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +1 -1
  92. package/corpus/templates/design/app/pages/DesignEditor.tsx +53 -0
  93. package/corpus/templates/design/server/plugins/agent-chat.ts +5 -1
  94. package/corpus/templates/design/server/plugins/assets-mcp.ts +60 -0
  95. package/corpus/templates/mail/server/plugins/auth.ts +4 -1
  96. package/corpus/templates/plan/app/pages/PlansPage.tsx +271 -197
  97. package/corpus/templates/plan/server/plan-content.ts +45 -2
  98. package/dist/a2a/client.d.ts +1 -0
  99. package/dist/a2a/client.d.ts.map +1 -1
  100. package/dist/a2a/client.js +5 -3
  101. package/dist/a2a/client.js.map +1 -1
  102. package/dist/agent/durable-background.d.ts +9 -4
  103. package/dist/agent/durable-background.d.ts.map +1 -1
  104. package/dist/agent/durable-background.js +24 -11
  105. package/dist/agent/durable-background.js.map +1 -1
  106. package/dist/cli/create.js +1 -1
  107. package/dist/cli/create.js.map +1 -1
  108. package/dist/cli/skills.d.ts.map +1 -1
  109. package/dist/cli/skills.js +361 -19
  110. package/dist/cli/skills.js.map +1 -1
  111. package/dist/client/api-path.d.ts.map +1 -1
  112. package/dist/client/api-path.js +27 -0
  113. package/dist/client/api-path.js.map +1 -1
  114. package/dist/client/embed-auth.d.ts.map +1 -1
  115. package/dist/client/embed-auth.js +27 -0
  116. package/dist/client/embed-auth.js.map +1 -1
  117. package/dist/client/mcp-app-host.d.ts.map +1 -1
  118. package/dist/client/mcp-app-host.js +5 -0
  119. package/dist/client/mcp-app-host.js.map +1 -1
  120. package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
  121. package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  122. package/dist/client/mcp-apps/McpAppRenderer.js +179 -33
  123. package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
  124. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  125. package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
  126. package/dist/client/resources/use-mcp-servers.js.map +1 -1
  127. package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
  128. package/dist/client/settings/DemoModeSection.js +3 -1
  129. package/dist/client/settings/DemoModeSection.js.map +1 -1
  130. package/dist/demo/fetch-interceptor.js +1 -1
  131. package/dist/demo/fetch-interceptor.js.map +1 -1
  132. package/dist/embedding/agent.d.ts +1 -1
  133. package/dist/embedding/agent.d.ts.map +1 -1
  134. package/dist/embedding/agent.js +1 -1
  135. package/dist/embedding/agent.js.map +1 -1
  136. package/dist/embedding/bridge.d.ts.map +1 -1
  137. package/dist/embedding/bridge.js +27 -1
  138. package/dist/embedding/bridge.js.map +1 -1
  139. package/dist/extensions/schema.d.ts +2 -2
  140. package/dist/mcp/build-server.d.ts +2 -0
  141. package/dist/mcp/build-server.d.ts.map +1 -1
  142. package/dist/mcp/build-server.js +32 -9
  143. package/dist/mcp/build-server.js.map +1 -1
  144. package/dist/mcp/embed-app.d.ts.map +1 -1
  145. package/dist/mcp/embed-app.js +120 -45
  146. package/dist/mcp/embed-app.js.map +1 -1
  147. package/dist/mcp/index.d.ts +1 -0
  148. package/dist/mcp/index.d.ts.map +1 -1
  149. package/dist/mcp/index.js +1 -0
  150. package/dist/mcp/index.js.map +1 -1
  151. package/dist/mcp/org-directory.d.ts +1 -0
  152. package/dist/mcp/org-directory.d.ts.map +1 -1
  153. package/dist/mcp/org-directory.js +59 -11
  154. package/dist/mcp/org-directory.js.map +1 -1
  155. package/dist/mcp/server.d.ts.map +1 -1
  156. package/dist/mcp/server.js +9 -0
  157. package/dist/mcp/server.js.map +1 -1
  158. package/dist/mcp-client/config.d.ts +12 -0
  159. package/dist/mcp-client/config.d.ts.map +1 -1
  160. package/dist/mcp-client/config.js.map +1 -1
  161. package/dist/mcp-client/index.d.ts +1 -1
  162. package/dist/mcp-client/index.d.ts.map +1 -1
  163. package/dist/mcp-client/index.js +1 -1
  164. package/dist/mcp-client/index.js.map +1 -1
  165. package/dist/mcp-client/manager.d.ts +2 -0
  166. package/dist/mcp-client/manager.d.ts.map +1 -1
  167. package/dist/mcp-client/manager.js +201 -9
  168. package/dist/mcp-client/manager.js.map +1 -1
  169. package/dist/mcp-client/remote-store.d.ts +27 -0
  170. package/dist/mcp-client/remote-store.d.ts.map +1 -1
  171. package/dist/mcp-client/remote-store.js +105 -0
  172. package/dist/mcp-client/remote-store.js.map +1 -1
  173. package/dist/mcp-client/routes.d.ts +1 -0
  174. package/dist/mcp-client/routes.d.ts.map +1 -1
  175. package/dist/mcp-client/routes.js +1 -0
  176. package/dist/mcp-client/routes.js.map +1 -1
  177. package/dist/provider-api/actions/github-repo-files.d.ts +2 -2
  178. package/dist/provider-api/actions/query-staged-dataset.d.ts +3 -3
  179. package/dist/provider-api/corpus-jobs.d.ts +8 -8
  180. package/dist/server/agent-chat-plugin.d.ts +2 -0
  181. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  182. package/dist/server/agent-chat-plugin.js +8 -0
  183. package/dist/server/agent-chat-plugin.js.map +1 -1
  184. package/dist/server/auth.d.ts +7 -0
  185. package/dist/server/auth.d.ts.map +1 -1
  186. package/dist/server/auth.js +1 -0
  187. package/dist/server/auth.js.map +1 -1
  188. package/dist/server/index.d.ts +1 -1
  189. package/dist/server/index.d.ts.map +1 -1
  190. package/dist/server/index.js +1 -1
  191. package/dist/server/index.js.map +1 -1
  192. package/dist/server/onboarding-html.d.ts +15 -0
  193. package/dist/server/onboarding-html.d.ts.map +1 -1
  194. package/dist/server/onboarding-html.js +73 -9
  195. package/dist/server/onboarding-html.js.map +1 -1
  196. package/dist/shared/mcp-embed-headers.d.ts +3 -1
  197. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  198. package/dist/shared/mcp-embed-headers.js +40 -3
  199. package/dist/shared/mcp-embed-headers.js.map +1 -1
  200. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  201. package/dist/sharing/actions/set-resource-visibility.d.ts +2 -2
  202. package/dist/sharing/actions/share-resource.d.ts +1 -1
  203. package/dist/sharing/schema.d.ts +1 -1
  204. package/dist/templates/default/AGENTS.md +4 -0
  205. package/dist/templates/default/package.json +2 -1
  206. package/dist/templates/headless/AGENTS.md +4 -0
  207. package/dist/templates/headless/package.json +1 -0
  208. package/dist/templates/workspace-root/AGENTS.md +4 -0
  209. package/dist/templates/workspace-root/README.md +4 -0
  210. package/dist/templates/workspace-root/package.json +1 -0
  211. package/dist/vite/client.d.ts.map +1 -1
  212. package/dist/vite/client.js +44 -0
  213. package/dist/vite/client.js.map +1 -1
  214. package/docs/content/skills-guide.md +14 -0
  215. package/docs/content/template-clips.md +7 -1
  216. package/package.json +6 -6
  217. package/src/templates/default/AGENTS.md +4 -0
  218. package/src/templates/default/package.json +2 -1
  219. package/src/templates/headless/AGENTS.md +4 -0
  220. package/src/templates/headless/package.json +1 -0
  221. package/src/templates/workspace-root/AGENTS.md +4 -0
  222. package/src/templates/workspace-root/README.md +4 -0
  223. package/src/templates/workspace-root/package.json +1 -0
@@ -88,6 +88,18 @@ function fixed(sql: string): (window?: MetricWindow) => string {
88
88
  return () => sql;
89
89
  }
90
90
 
91
+ const SIGNUP_TEMPLATE_EXPR =
92
+ "COALESCE(NULLIF(template, ''), NULLIF(properties::jsonb ->> 'templateId', ''), NULLIF(properties::jsonb ->> 'agent_native_template', ''), NULLIF(properties::jsonb ->> 'agentNativeTemplate', ''), NULLIF(app, ''), NULLIF(properties::jsonb ->> 'agent_native_app', ''), NULLIF(properties::jsonb ->> 'agentNativeApp', ''), 'unknown')";
93
+ const DASHBOARD_TIME_RANGE_FILTER =
94
+ "('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))";
95
+ const DASHBOARD_EMAIL_FILTER =
96
+ "('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))";
97
+ const TOTAL_SIGNUPS_SQL = `SELECT COUNT(*) AS signups FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}`;
98
+ const SIGNUPS_OVER_TIME_SQL = `WITH offsets AS (SELECT (ROW_NUMBER() OVER (ORDER BY timestamp) - 1)::int AS n FROM analytics_events LIMIT 800), signup_events AS (SELECT substr(timestamp, 1, 10) AS date, ${SIGNUP_TEMPLATE_EXPR} AS template FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), bounds AS (SELECT MIN(date::date) AS start_date, MAX(date::date) AS end_date FROM signup_events), dates AS (SELECT to_char(bounds.start_date + offsets.n, 'YYYY-MM-DD') AS date FROM bounds CROSS JOIN offsets WHERE bounds.start_date IS NOT NULL AND bounds.start_date + offsets.n <= bounds.end_date), templates AS (SELECT DISTINCT template FROM signup_events), daily AS (SELECT date, template, COUNT(*) AS count FROM signup_events GROUP BY date, template) SELECT dates.date, templates.template, COALESCE(daily.count, 0) AS count FROM dates CROSS JOIN templates LEFT JOIN daily ON daily.date = dates.date AND daily.template = templates.template ORDER BY dates.date, templates.template`;
99
+ const ONE_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, ${SIGNUP_TEMPLATE_EXPR} AS template, substr(timestamp, 1, 10) AS event_date, user_id FROM analytics_events WHERE COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) IS NOT NULL AND ${DASHBOARD_EMAIL_FILTER}), first_seen AS (SELECT user_key, template, MIN(event_date) AS cohort_date FROM base GROUP BY user_key, template), cohorts AS (SELECT user_key, template, cohort_date FROM first_seen WHERE cohort_date <= to_char(CURRENT_DATE - INTERVAL '1 day', 'YYYY-MM-DD') AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))) SELECT c.cohort_date AS date, c.template, COALESCE(COUNT(DISTINCT c.user_key) FILTER (WHERE b.event_date = to_char(c.cohort_date::date + INTERVAL '1 day', 'YYYY-MM-DD'))::float / NULLIF(COUNT(DISTINCT c.user_key), 0), 0) AS rate FROM cohorts c LEFT JOIN base b ON b.user_key = c.user_key AND b.template = c.template GROUP BY c.cohort_date, c.template ORDER BY c.cohort_date, c.template`;
100
+ const SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, ${SIGNUP_TEMPLATE_EXPR} AS template, substr(timestamp, 1, 10) AS event_date, user_id FROM analytics_events WHERE COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) IS NOT NULL AND ${DASHBOARD_EMAIL_FILTER}), first_seen AS (SELECT user_key, template, MIN(event_date) AS cohort_date FROM base GROUP BY user_key, template), cohorts AS (SELECT user_key, template, cohort_date FROM first_seen WHERE cohort_date <= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD') AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))) SELECT c.cohort_date AS date, c.template, COALESCE(COUNT(DISTINCT c.user_key) FILTER (WHERE b.event_date = to_char(c.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD'))::float / NULLIF(COUNT(DISTINCT c.user_key), 0), 0) AS rate FROM cohorts c LEFT JOIN base b ON b.user_key = c.user_key AND b.template = c.template GROUP BY c.cohort_date, c.template ORDER BY c.cohort_date, c.template`;
101
+ const SIGNUPS_BY_TEMPLATE_SQL = `SELECT ${SIGNUP_TEMPLATE_EXPR} AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${SIGNUP_TEMPLATE_EXPR} ORDER BY count DESC`;
102
+
91
103
  /**
92
104
  * Catalog entries. Order here is the default panel order when a caller passes
93
105
  * metrics; callers can reorder by listing keys in the order they want.
@@ -96,19 +108,16 @@ const ENTRIES: FirstPartyMetric[] = [
96
108
  // --- Signups -------------------------------------------------------------
97
109
  {
98
110
  key: "total-signups",
99
- title: "Signups",
111
+ title: "Total Signups",
100
112
  chartType: "metric",
101
113
  source: "first-party",
102
114
  width: 1,
103
115
  windowed: false,
104
- buildSql: fixed(
105
- "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup'",
106
- ),
116
+ buildSql: fixed(TOTAL_SIGNUPS_SQL),
107
117
  config: {
108
- yKey: "count",
118
+ yKey: "signups",
109
119
  yFormatter: "number",
110
- description:
111
- "Better Auth user creation events from all Agent Native templates",
120
+ description: "Total signup events in the selected time range.",
112
121
  },
113
122
  },
114
123
  {
@@ -118,14 +127,18 @@ const ENTRIES: FirstPartyMetric[] = [
118
127
  source: "first-party",
119
128
  width: 2,
120
129
  windowed: false,
121
- buildSql: fixed(
122
- "SELECT substr(timestamp, 1, 10) AS date, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' GROUP BY substr(timestamp, 1, 10) ORDER BY date",
123
- ),
130
+ buildSql: fixed(SIGNUPS_OVER_TIME_SQL),
124
131
  config: {
125
132
  xKey: "date",
126
133
  yKey: "count",
127
- color: "#8b5cf6",
128
- description: "Daily signup events across all Agent Native templates",
134
+ yFormatter: "number",
135
+ pivot: {
136
+ xKey: "date",
137
+ seriesKey: "template",
138
+ valueKey: "count",
139
+ },
140
+ stacked: true,
141
+ description: "Daily signup events stacked by inferred template/app.",
129
142
  },
130
143
  },
131
144
  {
@@ -133,16 +146,15 @@ const ENTRIES: FirstPartyMetric[] = [
133
146
  title: "Signups by Template",
134
147
  chartType: "bar",
135
148
  source: "first-party",
136
- width: 1,
149
+ width: 2,
137
150
  windowed: false,
138
- buildSql: fixed(
139
- "SELECT COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' GROUP BY COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') ORDER BY count DESC LIMIT 20",
140
- ),
151
+ buildSql: fixed(SIGNUPS_BY_TEMPLATE_SQL),
141
152
  config: {
142
153
  xKey: "template",
143
154
  yKey: "count",
144
- color: "#8b5cf6",
145
- description: "Signup events grouped by inferred template/app",
155
+ yFormatter: "number",
156
+ color: "var(--brand-purple)",
157
+ description: "Signup events grouped by inferred template/app.",
146
158
  },
147
159
  },
148
160
 
@@ -253,45 +265,85 @@ const ENTRIES: FirstPartyMetric[] = [
253
265
  width: 2,
254
266
  windowed: false,
255
267
  buildSql: fixed(
256
- "SELECT substr(timestamp, 1, 10) AS date, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' GROUP BY substr(timestamp, 1, 10) ORDER BY date",
268
+ "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
257
269
  ),
258
- config: { xKey: "date", yKey: "count", color: "var(--brand-blue)" },
270
+ config: {
271
+ xKey: "date",
272
+ yKey: "count",
273
+ yFormatter: "number",
274
+ pivot: {
275
+ xKey: "date",
276
+ seriesKey: "template",
277
+ valueKey: "count",
278
+ },
279
+ stacked: true,
280
+ },
259
281
  },
260
282
  {
261
283
  key: "clicks-by-template",
262
- title: "Clicks by Template",
263
- chartType: "bar",
284
+ title: "Clicks by Template Over Time",
285
+ chartType: "area",
264
286
  source: "first-party",
265
- width: 1,
287
+ width: 2,
266
288
  windowed: false,
267
289
  buildSql: fixed(
268
- "SELECT COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' GROUP BY COALESCE(NULLIF(template, ''), 'unknown') ORDER BY count DESC LIMIT 20",
290
+ "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
269
291
  ),
270
- config: { xKey: "template", yKey: "count", color: "var(--brand-blue)" },
292
+ config: {
293
+ xKey: "date",
294
+ yKey: "count",
295
+ yFormatter: "number",
296
+ pivot: {
297
+ xKey: "date",
298
+ seriesKey: "template",
299
+ valueKey: "count",
300
+ },
301
+ stacked: true,
302
+ },
271
303
  },
272
304
  {
273
305
  key: "demo-clicks-by-template",
274
- title: "Try-Demo Clicks by Template",
275
- chartType: "bar",
306
+ title: "Try-Demo Clicks Over Time",
307
+ chartType: "area",
276
308
  source: "first-party",
277
- width: 1,
309
+ width: 2,
278
310
  windowed: false,
279
311
  buildSql: fixed(
280
- "SELECT COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo' GROUP BY COALESCE(NULLIF(template, ''), 'unknown') ORDER BY count DESC LIMIT 20",
312
+ "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
281
313
  ),
282
- config: { xKey: "template", yKey: "count", color: "var(--brand-teal)" },
314
+ config: {
315
+ xKey: "date",
316
+ yKey: "count",
317
+ yFormatter: "number",
318
+ pivot: {
319
+ xKey: "date",
320
+ seriesKey: "template",
321
+ valueKey: "count",
322
+ },
323
+ stacked: true,
324
+ },
283
325
  },
284
326
  {
285
327
  key: "cli-copies-by-template",
286
- title: "CLI Copies by Template",
287
- chartType: "bar",
328
+ title: "CLI Copies by Template Over Time",
329
+ chartType: "area",
288
330
  source: "first-party",
289
- width: 1,
331
+ width: 2,
290
332
  windowed: false,
291
333
  buildSql: fixed(
292
- "SELECT COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' GROUP BY COALESCE(NULLIF(template, ''), 'unknown') ORDER BY count DESC LIMIT 20",
334
+ "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
293
335
  ),
294
- config: { xKey: "template", yKey: "count", color: "#06b6d4" },
336
+ config: {
337
+ xKey: "date",
338
+ yKey: "count",
339
+ yFormatter: "number",
340
+ pivot: {
341
+ xKey: "date",
342
+ seriesKey: "template",
343
+ valueKey: "count",
344
+ },
345
+ stacked: true,
346
+ },
295
347
  },
296
348
  {
297
349
  key: "cli-copies-over-time",
@@ -301,9 +353,19 @@ const ENTRIES: FirstPartyMetric[] = [
301
353
  width: 2,
302
354
  windowed: false,
303
355
  buildSql: fixed(
304
- "SELECT substr(timestamp, 1, 10) AS date, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' GROUP BY substr(timestamp, 1, 10) ORDER BY date",
356
+ "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
305
357
  ),
306
- config: { xKey: "date", yKey: "count", color: "#06b6d4" },
358
+ config: {
359
+ xKey: "date",
360
+ yKey: "count",
361
+ yFormatter: "number",
362
+ pivot: {
363
+ xKey: "date",
364
+ seriesKey: "template",
365
+ valueKey: "count",
366
+ },
367
+ stacked: true,
368
+ },
307
369
  },
308
370
 
309
371
  // --- Activity / pageviews ------------------------------------------------
@@ -314,17 +376,200 @@ const ENTRIES: FirstPartyMetric[] = [
314
376
  source: "first-party",
315
377
  width: 2,
316
378
  windowed: false,
317
- // First-party tracking has no distinct `pageview` event; total tracked
318
- // events per day is the honest activity/pageviews proxy over analytics_events.
379
+ // Browser telemetry emits explicit `pageview` events with URL context, so
380
+ // keep pageview panels scoped to that event instead of all tracked events.
319
381
  buildSql: fixed(
320
- "SELECT substr(timestamp, 1, 10) AS date, COUNT(*) AS count FROM analytics_events GROUP BY substr(timestamp, 1, 10) ORDER BY date",
382
+ `SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'pageview' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') ORDER BY date, template`,
321
383
  ),
322
384
  config: {
323
385
  xKey: "date",
324
386
  yKey: "count",
325
- color: "var(--brand-blue)",
387
+ yFormatter: "number",
388
+ pivot: {
389
+ xKey: "date",
390
+ seriesKey: "template",
391
+ valueKey: "count",
392
+ },
393
+ stacked: true,
394
+ description: "Pageview events per day stacked by inferred template/app.",
395
+ },
396
+ },
397
+ {
398
+ key: "top-visited-urls",
399
+ title: "Top Visited URLs",
400
+ chartType: "table",
401
+ source: "first-party",
402
+ width: 2,
403
+ windowed: false,
404
+ buildSql: fixed(
405
+ `WITH pageviews AS (SELECT COALESCE(CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') END, NULLIF(url, ''), NULLIF(path, ''), 'unknown') AS url, CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN NULLIF(url, '') LIKE 'http%' THEN url ELSE COALESCE(NULLIF(url, ''), NULLIF(path, ''), '') END AS href, COUNT(*) AS views, COUNT(DISTINCT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))) AS users, MAX(substr(timestamp, 1, 10)) AS last_seen FROM analytics_events WHERE event_name = 'pageview' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY 1, 2) SELECT url, views, users, last_seen, href FROM pageviews ORDER BY views DESC LIMIT 25`,
406
+ ),
407
+ config: {
408
+ description:
409
+ "Most-viewed URLs in the selected time range. Links open in a new tab.",
410
+ sortable: true,
411
+ limit: 25,
412
+ columns: [
413
+ { key: "url", label: "URL", format: "link", linkKey: "href" },
414
+ { key: "views", label: "Views", format: "number" },
415
+ { key: "users", label: "Users", format: "number" },
416
+ { key: "last_seen", label: "Last seen", format: "date" },
417
+ { key: "href", hidden: true },
418
+ ],
419
+ },
420
+ },
421
+ {
422
+ key: "top-visited-clips",
423
+ title: "Top Visited Clips",
424
+ chartType: "table",
425
+ source: "first-party",
426
+ width: 2,
427
+ windowed: false,
428
+ buildSql: fixed(
429
+ `WITH clip_events AS (SELECT COALESCE(NULLIF(properties::jsonb ->> 'recording_id', ''), NULLIF(path, ''), NULLIF(url, ''), 'unknown') AS clip_key, CASE WHEN NULLIF(url, '') LIKE 'http%' THEN url WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN NULLIF(path, '') LIKE '/%' THEN 'https://clips.agent-native.com' || path ELSE COALESCE(NULLIF(url, ''), '') END AS href, user_id, anonymous_id, timestamp FROM analytics_events WHERE event_name = 'share_view' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND COALESCE(NULLIF(properties::jsonb ->> 'surface', ''), 'clip') = 'clip'), clip_views AS (SELECT clip_key, COALESCE(MAX(NULLIF(href, '')), clip_key) AS href, COUNT(*) AS views, COUNT(DISTINCT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))) AS users, MAX(substr(timestamp, 1, 10)) AS last_seen FROM clip_events GROUP BY clip_key) SELECT CASE WHEN href LIKE 'http%' THEN href ELSE clip_key END AS clip, views, users, last_seen, href FROM clip_views ORDER BY views DESC LIMIT 25`,
430
+ ),
431
+ config: {
432
+ description: "Most-viewed shared Clips pages in the selected time range.",
433
+ sortable: true,
434
+ limit: 25,
435
+ columns: [
436
+ { key: "clip", label: "Clip URL", format: "link", linkKey: "href" },
437
+ { key: "views", label: "Views", format: "number" },
438
+ { key: "users", label: "Users", format: "number" },
439
+ { key: "last_seen", label: "Last seen", format: "date" },
440
+ { key: "href", hidden: true },
441
+ ],
442
+ },
443
+ },
444
+ {
445
+ key: "repeat-users",
446
+ title: "Repeat Users",
447
+ chartType: "metric",
448
+ source: "first-party",
449
+ width: 1,
450
+ windowed: false,
451
+ buildSql: fixed(
452
+ "WITH user_days AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, COUNT(DISTINCT substr(timestamp, 1, 10)) AS active_days FROM analytics_events WHERE COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) IS NOT NULL AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io')) GROUP BY COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))) SELECT COUNT(*) AS count FROM user_days WHERE active_days >= 2",
453
+ ),
454
+ config: {
455
+ yKey: "count",
456
+ yFormatter: "number",
457
+ description:
458
+ "Distinct users with activity on at least two days in the selected time range. Uses user_id first, then anonymous_id.",
459
+ },
460
+ },
461
+ {
462
+ key: "retention-over-time",
463
+ title: "1d / 7d Retention Over Time",
464
+ chartType: "line",
465
+ source: "first-party",
466
+ width: 3,
467
+ windowed: false,
468
+ buildSql: fixed(
469
+ "WITH base AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, substr(timestamp, 1, 10) AS event_date, user_id FROM analytics_events WHERE COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) IS NOT NULL AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))), first_seen AS (SELECT user_key, MIN(event_date) AS cohort_date FROM base GROUP BY user_key), cohorts AS (SELECT user_key, cohort_date FROM first_seen WHERE cohort_date <= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD') AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))), retention AS (SELECT c.cohort_date AS date, '1d retention' AS period, COALESCE(COUNT(DISTINCT c.user_key) FILTER (WHERE b.event_date = to_char(c.cohort_date::date + INTERVAL '1 day', 'YYYY-MM-DD'))::float / NULLIF(COUNT(DISTINCT c.user_key), 0), 0) AS rate FROM cohorts c LEFT JOIN base b ON b.user_key = c.user_key GROUP BY c.cohort_date UNION ALL SELECT c.cohort_date AS date, '7d retention' AS period, COALESCE(COUNT(DISTINCT c.user_key) FILTER (WHERE b.event_date = to_char(c.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD'))::float / NULLIF(COUNT(DISTINCT c.user_key), 0), 0) AS rate FROM cohorts c LEFT JOIN base b ON b.user_key = c.user_key GROUP BY c.cohort_date) SELECT date, period, rate FROM retention ORDER BY date, period",
470
+ ),
471
+ config: {
472
+ xKey: "date",
473
+ yKey: "rate",
474
+ yFormatter: "percent",
475
+ pivot: {
476
+ xKey: "date",
477
+ seriesKey: "period",
478
+ valueKey: "rate",
479
+ },
480
+ colors: ["#10b981", "#8b5cf6"],
481
+ description:
482
+ "First-seen daily cohorts in the selected range. Cohorts must be at least seven days old so both lines are mature.",
483
+ },
484
+ },
485
+ {
486
+ key: "one-day-retention-by-template",
487
+ title: "1d Retention by Template",
488
+ chartType: "line",
489
+ source: "first-party",
490
+ width: 2,
491
+ windowed: false,
492
+ buildSql: fixed(ONE_DAY_RETENTION_BY_TEMPLATE_SQL),
493
+ config: {
494
+ xKey: "date",
495
+ yKey: "rate",
496
+ yFormatter: "percent",
497
+ pivot: {
498
+ xKey: "date",
499
+ seriesKey: "template",
500
+ valueKey: "rate",
501
+ },
502
+ description:
503
+ "Per-template first-seen cohorts in the selected range. A user is retained when they return to the same template the next day.",
504
+ },
505
+ },
506
+ {
507
+ key: "seven-day-retention-by-template",
508
+ title: "7d Retention by Template",
509
+ chartType: "line",
510
+ source: "first-party",
511
+ width: 2,
512
+ windowed: false,
513
+ buildSql: fixed(SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL),
514
+ config: {
515
+ xKey: "date",
516
+ yKey: "rate",
517
+ yFormatter: "percent",
518
+ pivot: {
519
+ xKey: "date",
520
+ seriesKey: "template",
521
+ valueKey: "rate",
522
+ },
523
+ description:
524
+ "Per-template first-seen cohorts in the selected range. A user is retained when they return to the same template seven days later.",
525
+ },
526
+ },
527
+ {
528
+ key: "dau-over-time",
529
+ title: "DAU by Template",
530
+ chartType: "area",
531
+ source: "first-party",
532
+ width: 2,
533
+ windowed: false,
534
+ buildSql: fixed(
535
+ "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') AS template, COUNT(DISTINCT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))) AS users FROM analytics_events WHERE COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) IS NOT NULL AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io')) GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') ORDER BY date, template",
536
+ ),
537
+ config: {
538
+ xKey: "date",
539
+ yKey: "users",
540
+ yFormatter: "number",
541
+ pivot: {
542
+ xKey: "date",
543
+ seriesKey: "template",
544
+ valueKey: "users",
545
+ },
546
+ stacked: true,
547
+ description:
548
+ "Distinct active users per day in the selected time range, stacked by template. Uses user_id first, then anonymous_id.",
549
+ },
550
+ },
551
+ {
552
+ key: "wau-over-time",
553
+ title: "WAU by Template",
554
+ chartType: "area",
555
+ source: "first-party",
556
+ width: 2,
557
+ windowed: false,
558
+ buildSql: fixed(
559
+ "WITH base AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, COALESCE(NULLIF(template, ''), NULLIF(app, ''), 'unknown') AS template, substr(timestamp, 1, 10) AS event_date, user_id FROM analytics_events WHERE COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) IS NOT NULL AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))), days AS (SELECT DISTINCT event_date AS date FROM base WHERE ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))) SELECT d.date, b.template, COUNT(DISTINCT b.user_key) AS users FROM days d JOIN base b ON b.event_date >= to_char(d.date::date - INTERVAL '6 days', 'YYYY-MM-DD') AND b.event_date <= d.date GROUP BY d.date, b.template ORDER BY d.date, b.template",
560
+ ),
561
+ config: {
562
+ xKey: "date",
563
+ yKey: "users",
564
+ yFormatter: "number",
565
+ pivot: {
566
+ xKey: "date",
567
+ seriesKey: "template",
568
+ valueKey: "users",
569
+ },
570
+ stacked: true,
326
571
  description:
327
- "Total first-party tracked events per day (activity proxy there is no distinct pageview event in /track data).",
572
+ "Trailing 7-day distinct active users for each active date in the selected time range, stacked by template.",
328
573
  },
329
574
  },
330
575
 
@@ -1,20 +1,26 @@
1
1
  ---
2
2
  name: a2a-assets
3
- description: Call the Assets agent from other agent-native apps to generate, refine, export, and insert brand images or videos.
3
+ description: Use Assets from other apps or MCP hosts to generate, refine, export, and insert brand images or videos.
4
4
  ---
5
5
 
6
- # A2A Assets
6
+ # Assets MCP Tool
7
7
 
8
- Use A2A when another app needs brand imagery, video, or reusable source media and Assets owns the library.
8
+ Use the Assets MCP tool surface when another app or external host needs brand
9
+ imagery, video, or reusable source media and Assets owns the library. Prefer the
10
+ pure MCP `generate-asset` flow for human-in-the-loop generation because it can
11
+ return the inline picker. A2A remains useful for unattended cross-app
12
+ delegation, but A2A replies cannot render MCP App pickers.
9
13
 
10
14
  ## Caller Flow
11
15
 
12
- 1. Call `match-library` or `list-libraries` on the Assets agent when the library
13
- is ambiguous.
14
- 2. Call `generate-image-batch` with one slot per destination, such as one hero
15
- per slide. Always pass `source: "a2a"` and `callerAppId` with the calling
16
- app id (`slides`, `design`, `content`, `mail`) so the Assets audit log can
17
- group cross-agent generations.
16
+ 1. For human selection, call `generate-asset` with the brief, `callerAppId`, and
17
+ `libraryId` when known. It will match the library, generate candidates, and
18
+ return the Assets picker filtered to the new run IDs.
19
+ 2. For unattended A2A-style work, call `match-library` or `list-libraries` when
20
+ the library is ambiguous, then call `generate-image-batch` with one slot per
21
+ destination, such as one hero per slide. Pass `source: "a2a"` and
22
+ `callerAppId` with the calling app id (`slides`, `design`, `content`,
23
+ `mail`) so the Assets audit log can group cross-agent generations.
18
24
  3. Treat image batches as complete when the action returns. Use returned
19
25
  successful `images` entries directly; only regenerate slots that returned
20
26
  `ok: false`.
@@ -25,7 +31,8 @@ Use A2A when another app needs brand imagery, video, or reusable source media an
25
31
  6. For video, call `generate-video` and then `refresh-generation-run` until the run completes.
26
32
  7. Preserve returned `assetId`, `runId`, `previewUrl`, `downloadUrl`, and
27
33
  `embedPath` exactly.
28
- 8. Insert exported URLs into the caller's artifact.
34
+ 8. Insert chosen/exported URLs into the caller's artifact. Design callers should
35
+ call `insert-asset` after the picker returns a selected asset.
29
36
  9. On feedback, call `refine-image` with the prior `assetId`, `source: "a2a"`,
30
37
  and the same `callerAppId`, then replace only the affected destination.
31
38
 
@@ -16,9 +16,14 @@ or generated image/video assets that another app can reference by ID and URL.
16
16
 
17
17
  ## Choose The Path
18
18
 
19
- - Use `open-asset-picker` when a person should browse, search, generate, and
20
- select an asset in UI. Pass `mediaType: "image"` by default, or
21
- `mediaType: "video"` for video libraries.
19
+ - Use `generate-asset` when a person should get newly generated, on-brand image
20
+ candidates and choose the winner in the inline picker. It matches a library
21
+ when `libraryId` is omitted, generates candidates, returns the picker filtered
22
+ to those run IDs, and works in in-app chat plus external MCP hosts.
23
+ - Use `open-asset-picker` when a person should browse, search, or select an
24
+ existing asset in UI, or when you want the picker to handle generation itself.
25
+ Pass `mediaType: "image"` by default, or `mediaType: "video"` for video
26
+ libraries.
22
27
  - Use unattended actions when the agent already knows what to do:
23
28
  `search-assets`, `list-assets`, `generate-image`, `generate-image-batch`,
24
29
  `generate-video`, `refresh-generation-run`, and `export-asset`.
@@ -41,7 +46,9 @@ or generated image/video assets that another app can reference by ID and URL.
41
46
 
42
47
  ## Image Workflows
43
48
 
44
- 1. Pick or match the library with `list-libraries` or `match-library`.
49
+ 1. For human-in-the-loop generation, call `generate-asset` first and preserve
50
+ the returned picker/candidate metadata. For unattended generation, pick or
51
+ match the library with `list-libraries` or `match-library`.
45
52
  If the user wants a default look rather than a brand library, call
46
53
  `list-library-presets` and then `create-library-from-preset`; the resulting
47
54
  library is editable and reusable like any other library.
@@ -85,8 +92,10 @@ deferred.
85
92
  Do not put shared secrets in skill files.
86
93
  - Local customization: run `npx @agent-native/core@latest app-skill launch --local` from the
87
94
  Assets app-skill manifest, or pass `--into <path>` for editable source.
88
- - For A2A or MCP callers, include exact `assetId`, `runId`, media type, and
89
- URLs in the final response so the caller can attach or embed the media.
95
+ - For MCP callers, `generate-asset` is the portable first choice because the
96
+ same MCP App picker renders inline in Agent-Native chat, ChatGPT, and Claude
97
+ when the host supports MCP Apps. Include exact `assetId`, `runId`, media type,
98
+ and URLs in the final response so the caller can attach or embed the media.
90
99
  Include `presetId` and `sessionId` when present.
91
100
 
92
101
  ## Don't
@@ -1,8 +1,9 @@
1
1
  # Assets — Agent Guide
2
2
 
3
3
  Assets is an agent-native asset library and generation workspace. The agent
4
- manages libraries, images, generated assets, inline embeds, notifications,
5
- collaboration, and A2A asset requests through actions and SQL state.
4
+ manages libraries, images, generated assets, inline MCP App pickers,
5
+ notifications, collaboration, and portable asset requests through actions and
6
+ SQL state.
6
7
 
7
8
  Detailed library, generation, image, embed, and engine rules live in
8
9
  `.agents/skills/`.
@@ -61,9 +62,10 @@ Read the relevant skill before deeper work:
61
62
  plain image-generation requests. Keep `npx @agent-native/core@latest connect` running until
62
63
  browser authorization finishes, restart the client if tools are not visible,
63
64
  and redact any MCP auth headers or tokens when debugging local config.
64
- - For human-in-the-loop image creation, call `open-asset-picker` with `prompt`,
65
- `autoGenerate: true`, and `count: 3` so the picker opens with candidates to
66
- preview, tweak by preset/aspect/count, and choose.
65
+ - For human-in-the-loop image creation, prefer `generate-asset` so Assets
66
+ matches the library, generates candidates, and returns the inline picker
67
+ filtered to those candidates. Use `open-asset-picker` when the user only needs
68
+ to browse/search/pick or when you want the picker to handle generation itself.
67
69
  - If the picker opens as a browser fallback instead of inline, selecting an
68
70
  asset copies a handoff summary; ask the caller to paste it back into chat.
69
71
  - Treat Codex, Claude Code, and Claude Desktop Code as link-out hosts for MCP