@karan171996/metricflow 0.5.3 → 0.6.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 (127) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/fallback-build-manifest.json +3 -3
  4. package/.next/next-server.js.nft.json +1 -1
  5. package/.next/prerender-manifest.json +1 -1
  6. package/.next/server/app/_global-error.html +1 -1
  7. package/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  12. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/_not-found.html +2 -2
  14. package/.next/server/app/_not-found.rsc +9 -9
  15. package/.next/server/app/_not-found.segments/_full.segment.rsc +9 -9
  16. package/.next/server/app/_not-found.segments/_index.segment.rsc +8 -8
  17. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  19. package/.next/server/app/api/analyze/route.js +2 -2
  20. package/.next/server/app/api/analyze/route.js.nft.json +1 -1
  21. package/.next/server/app/api/connect/route.js +1 -1
  22. package/.next/server/app/api/connect/route.js.nft.json +1 -1
  23. package/.next/server/app/api/health/route.js.nft.json +1 -1
  24. package/.next/server/app/api/metrics/route.js +2 -2
  25. package/.next/server/app/api/metrics/route.js.nft.json +1 -1
  26. package/.next/server/app/api/settings/route.js.nft.json +1 -1
  27. package/.next/server/app/api/setup/ai/route.js.nft.json +1 -1
  28. package/.next/server/app/api/setup/route.js +3 -2
  29. package/.next/server/app/api/setup/route.js.nft.json +1 -1
  30. package/.next/server/app/connect/page.js.nft.json +1 -1
  31. package/.next/server/app/connect/page_client-reference-manifest.js +1 -1
  32. package/.next/server/app/connect.html +19 -66
  33. package/.next/server/app/connect.rsc +11 -11
  34. package/.next/server/app/connect.segments/_full.segment.rsc +11 -11
  35. package/.next/server/app/connect.segments/_index.segment.rsc +8 -8
  36. package/.next/server/app/connect.segments/_tree.segment.rsc +2 -2
  37. package/.next/server/app/connect.segments/connect/__PAGE__.segment.rsc +6 -6
  38. package/.next/server/app/index.html +2 -2
  39. package/.next/server/app/index.rsc +11 -11
  40. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  41. package/.next/server/app/index.segments/_full.segment.rsc +11 -11
  42. package/.next/server/app/index.segments/_index.segment.rsc +8 -8
  43. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  44. package/.next/server/app/page.js.nft.json +1 -1
  45. package/.next/server/app/page_client-reference-manifest.js +1 -1
  46. package/.next/server/app/performance/[slug]/page.js +2 -2
  47. package/.next/server/app/performance/[slug]/page.js.nft.json +1 -1
  48. package/.next/server/app/performance/[slug]/page_client-reference-manifest.js +1 -1
  49. package/.next/server/app/performance/page.js.nft.json +1 -1
  50. package/.next/server/app/performance/page_client-reference-manifest.js +1 -1
  51. package/.next/server/app/performance.html +2 -2
  52. package/.next/server/app/performance.rsc +12 -12
  53. package/.next/server/app/performance.segments/_full.segment.rsc +12 -12
  54. package/.next/server/app/performance.segments/_index.segment.rsc +8 -8
  55. package/.next/server/app/performance.segments/_tree.segment.rsc +2 -2
  56. package/.next/server/app/performance.segments/performance/__PAGE__.segment.rsc +7 -7
  57. package/.next/server/app/settings/page.js.nft.json +1 -1
  58. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  59. package/.next/server/app/settings.html +2 -2
  60. package/.next/server/app/settings.rsc +20 -20
  61. package/.next/server/app/settings.segments/_full.segment.rsc +20 -20
  62. package/.next/server/app/settings.segments/_index.segment.rsc +8 -8
  63. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  64. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +16 -16
  65. package/.next/server/app/setup/page.js.nft.json +1 -1
  66. package/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  67. package/.next/server/app/setup.html +2 -2
  68. package/.next/server/app/setup.rsc +12 -12
  69. package/.next/server/app/setup.segments/_full.segment.rsc +12 -12
  70. package/.next/server/app/setup.segments/_index.segment.rsc +8 -8
  71. package/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +7 -7
  73. package/.next/server/app/tools/[tool]/page.js.nft.json +1 -1
  74. package/.next/server/app/tools/[tool]/page_client-reference-manifest.js +1 -1
  75. package/.next/server/chunks/[root-of-the-server]__0fkj1-q._.js +1 -1
  76. package/.next/server/chunks/[root-of-the-server]__0o2ceqr._.js +18 -0
  77. package/.next/server/chunks/[root-of-the-server]__0pil1g6._.js +3 -0
  78. package/.next/server/chunks/[root-of-the-server]__0xmaf60._.js +18 -0
  79. package/.next/server/chunks/[root-of-the-server]__18s35pf._.js +1 -1
  80. package/.next/server/chunks/[root-of-the-server]__1f_fo0j._.js +1 -1
  81. package/.next/server/chunks/{[root-of-the-server]__1sf143y._.js → [root-of-the-server]__1naf7j4._.js} +2 -2
  82. package/.next/server/chunks/[root-of-the-server]__1o9k4jf._.js +1 -1
  83. package/.next/server/chunks/[root-of-the-server]__1rx5s6r._.js +3 -0
  84. package/.next/server/chunks/_08xo8nu._.js +1 -1
  85. package/.next/server/chunks/_0w8wvdn._.js +1 -1
  86. package/.next/server/chunks/_0wct7-r._.js +1 -16
  87. package/.next/server/chunks/_1l9d64o._.js +2 -2
  88. package/.next/server/chunks/ssr/0x0c_next_dist_1icgwks._.js +1 -1
  89. package/.next/server/chunks/ssr/{[root-of-the-server]__0ryyli3._.js → [root-of-the-server]__1xhr2zy._.js} +1 -1
  90. package/.next/server/chunks/ssr/_0bxxvzb._.js +1 -1
  91. package/.next/server/chunks/ssr/_0cq6qjs._.js +9 -63
  92. package/.next/server/chunks/ssr/_0htqt7_._.js +10 -5
  93. package/.next/server/chunks/ssr/_13m9nev._.js +1 -1
  94. package/.next/server/chunks/ssr/_14aoqb2._.js +1 -1
  95. package/.next/server/middleware-build-manifest.js +3 -3
  96. package/.next/server/middleware.js.nft.json +1 -1
  97. package/.next/server/pages/404.html +2 -2
  98. package/.next/server/pages/500.html +1 -1
  99. package/.next/static/chunks/{332-owz6utwp5.js → 05xbzyep2vlp0.js} +1 -1
  100. package/.next/static/chunks/0icg-e95qmpn-.css +1 -0
  101. package/.next/static/chunks/1wbaplet994dj.js +11 -0
  102. package/.next/static/chunks/2jgqio91axkic.js +1 -0
  103. package/.next/static/chunks/{0_xh0vh4uh0vk.js → 3dncuj6lhk80h.js} +1 -1
  104. package/.next/trace +1 -1
  105. package/.next/trace-build +1 -1
  106. package/README.md +48 -7
  107. package/bin/cli.mjs +2 -1
  108. package/dist/browser/index.d.mts +27 -0
  109. package/dist/browser/index.mjs +110 -0
  110. package/dist/browser/newrelic-agent.d.mts +4 -0
  111. package/dist/browser/newrelic-agent.mjs +32 -0
  112. package/dist/browser/newrelic.d.mts +2 -0
  113. package/dist/browser/newrelic.mjs +35 -0
  114. package/dist/browser/sentry-sdk.d.mts +2 -0
  115. package/dist/browser/sentry-sdk.mjs +22 -0
  116. package/dist/browser/sentry.d.mts +2 -0
  117. package/dist/browser/sentry.mjs +45 -0
  118. package/package.json +14 -3
  119. package/.next/server/chunks/[root-of-the-server]__12h8r83._.js +0 -3
  120. package/.next/server/chunks/[root-of-the-server]__19yigji._.js +0 -3
  121. package/.next/server/chunks/[root-of-the-server]__1zlr3w0._.js +0 -3
  122. package/.next/static/chunks/2bt9wtnw1g0g_.css +0 -1
  123. package/.next/static/chunks/39h0f3s2ddxz_.js +0 -65
  124. package/.next/static/chunks/3ab7oxjuh1asy.js +0 -1
  125. /package/.next/static/{fn1O5hprdyzrIbY1gapbZ → cTCo8pLtIDkS2Pw1mc2pP}/_buildManifest.js +0 -0
  126. /package/.next/static/{fn1O5hprdyzrIbY1gapbZ → cTCo8pLtIDkS2Pw1mc2pP}/_clientMiddlewareManifest.js +0 -0
  127. /package/.next/static/{fn1O5hprdyzrIbY1gapbZ → cTCo8pLtIDkS2Pw1mc2pP}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,55370,e=>{"use strict";var t=e.i(40798),a=e.i(96341),r=e.i(70065),s=e.i(67881),i=e.i(23750);let n=[{id:"gemini",label:"Google Gemini",help:"Create a key at aistudio.google.com/apikey."},{id:"claude",label:"Anthropic Claude",help:"Create a key at console.anthropic.com > API keys."},{id:"openai",label:"OpenAI",help:"Create a key at platform.openai.com/api-keys."}];e.s(["AiKeyForm",0,function(){let[e,o]=(0,a.useState)(n[0].id),[l,d]=(0,a.useState)(""),[c,u]=(0,a.useState)(null),[h,m]=(0,a.useState)({}),[x,p]=(0,a.useState)(null),[g,f]=(0,a.useState)(!1);async function v(t){t.preventDefault(),f(!0),p(null);try{let t=await fetch("/api/setup/ai",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({provider:e,key:l})}),a=await t.json();a.saved?(d(""),u(e),m({...h,[e]:!0}),p({ok:!0,text:"Saved. AI suggestions now use this provider. No restart needed."})):p({ok:!1,text:a.error??"Nothing was saved."})}catch{p({ok:!1,text:"Could not reach the dashboard server."})}finally{f(!1)}}(0,a.useEffect)(()=>{fetch("/api/setup/ai").then(e=>e.json()).then(e=>{e.keys&&(m(e.keys),u(e.provider),e.provider&&o(e.provider))}).catch(()=>{})},[]);let b=n.find(t=>t.id===e);return(0,t.jsxs)(r.Card,{className:"border-[#2d3748] bg-[#1a202c] shadow-md max-w-2xl mt-6",children:[(0,t.jsxs)(r.CardHeader,{children:[(0,t.jsx)(r.CardTitle,{className:"text-[18px] font-bold text-white tracking-tight",children:"AI suggestions (optional)"}),(0,t.jsx)(r.CardDescription,{className:"text-sm text-gray-400",children:"Add a Gemini, Claude or OpenAI key to get AI insights and recommendations for your pages. Only page names, load times and error counts are sent to the provider you pick. Without a key, the dashboard shows sample suggestions."})]}),(0,t.jsx)(r.CardContent,{children:(0,t.jsxs)("form",{onSubmit:v,className:"flex flex-col gap-5",children:[(0,t.jsxs)("div",{className:"flex flex-col gap-1.5",children:[(0,t.jsx)("label",{htmlFor:"ai-provider",className:"text-sm font-medium text-gray-300",children:"Provider"}),(0,t.jsx)("select",{id:"ai-provider",value:e,onChange:e=>o(e.target.value),className:"h-9 rounded-md border border-[#2d3748] bg-[#0f1419] px-3 text-sm text-gray-200",children:n.map(e=>(0,t.jsxs)("option",{value:e.id,children:[e.label,c===e.id?" (in use)":h[e.id]?" (key saved)":""]},e.id))})]}),(0,t.jsxs)("div",{className:"flex flex-col gap-1.5",children:[(0,t.jsxs)("label",{htmlFor:"ai-key",className:"text-sm font-medium text-gray-300",children:[b.label," API key ",h[e]&&(0,t.jsx)("span",{className:"ml-2 text-xs text-[#3ee0a1]",children:"✓ Set"})]}),(0,t.jsx)(i.Input,{id:"ai-key",type:"password",autoComplete:"off",spellCheck:!1,placeholder:h[e]?"•••••••• (type to replace)":"",value:l,onChange:e=>d(e.target.value),"aria-describedby":"ai-key-help",className:"bg-[#0f1419] border-[#2d3748] text-gray-200 font-mono text-xs"}),(0,t.jsx)("p",{id:"ai-key-help",className:"text-xs text-gray-400",children:b.help})]}),(0,t.jsxs)("div",{className:"flex items-center gap-4",children:[(0,t.jsx)(s.Button,{type:"submit",disabled:g||!l.trim(),children:g?"Checking…":"Check and save"}),(0,t.jsx)("span",{"aria-live":"polite",className:`text-sm ${x?.ok?"text-[#3ee0a1]":"text-[#f87171]"}`,children:x?.text})]})]})})]})}])},4603,e=>{"use strict";var t=e.i(40798),a=e.i(96341),r=e.i(3547),s=e.i(70065),i=e.i(67881),n=e.i(23750),o=e.i(94179),l=e.i(93834);let d=l.TOOL_IDS.flatMap(e=>l.TOOLS[e].keys.required.map(t=>({name:t,tool:e,...l.KEY_FIELDS[t]})));e.s(["SetupForm",0,function(){let[e,c]=(0,a.useState)({}),[u,h]=(0,a.useState)({}),[m,x]=(0,a.useState)({}),[p,g]=(0,a.useState)(null),[f,v]=(0,a.useState)(!1),[b,y]=(0,a.useState)(!1),[k,j]=(0,a.useState)(null),N=(0,a.useRef)(null);async function w(t){t.preventDefault(),v(!0),g(null);try{let t=await fetch("/api/setup",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),a=await t.json(),r=a.results??{};x(r);let s=d.find(e=>r[e.name]&&!r[e.name].ok);if(s&&setTimeout(()=>N.current?.querySelector(`#${s.name}`)?.focus(),0),a.saved){y(!0),c({});let t=new Set(d.filter(t=>e[t.name]?.trim()).map(e=>e.tool));h(e=>({...e,...Object.fromEntries(d.filter(e=>t.has(e.tool)).map(e=>[e.name,!0]))}));let a=l.TOOL_IDS.filter(e=>t.has(e)||k?.includes(e));j(a),g({ok:!0,text:`Saved. ${a.map(e=>l.TOOLS[e].label).join(" and ")} ${a.length>1?"are":"is"} connected. No restart needed.`})}else g({ok:!1,text:a.error??"Nothing was saved. Fix the fields marked below."})}catch{g({ok:!1,text:"Could not reach the dashboard server."})}finally{v(!1)}}return(0,a.useEffect)(()=>{fetch("/api/setup").then(e=>e.json()).then(e=>{e.keys&&h(e.keys),Array.isArray(e.tools)?j(e.tools):e.error&&g({ok:!1,text:e.error})}).catch(()=>{})},[]),(0,t.jsxs)(s.Card,{className:"border-[#2d3748] bg-[#1a202c] shadow-md max-w-2xl",children:[(0,t.jsxs)(s.CardHeader,{children:[(0,t.jsx)(s.CardTitle,{className:"text-[18px] font-bold text-white tracking-tight",children:"Connect your tools"}),(0,t.jsx)(s.CardDescription,{className:"text-sm text-gray-400",children:"Add keys for the tools you use. You only need one. Keys are checked once, saved to .env.local in this folder, and never shown again."})]}),(0,t.jsx)(s.CardContent,{children:(0,t.jsxs)("form",{ref:N,onSubmit:w,className:"flex flex-col gap-5",children:[l.TOOL_IDS.map(a=>(0,t.jsxs)("fieldset",{id:a,className:"flex flex-col gap-5 rounded-lg border border-[#2d3748] p-4",children:[(0,t.jsxs)("legend",{className:"flex items-center gap-2 px-2 text-sm font-semibold text-white",children:[l.TOOLS[a].label,k?.includes(a)?(0,t.jsx)(o.Badge,{variant:"outline",className:"border-[#3ee0a1] text-[#3ee0a1] bg-[#3ee0a1]/10",children:"✓ Connected"}):(0,t.jsx)(o.Badge,{variant:"outline",className:"border-[#4a5568] text-gray-400",children:"Optional"})]}),d.filter(e=>e.tool===a).map(a=>{let r=m[a.name];return(0,t.jsxs)("div",{className:"flex flex-col gap-1.5",children:[(0,t.jsxs)("label",{htmlFor:a.name,className:"text-sm font-medium text-gray-300",children:[a.label," ",u[a.name]&&(0,t.jsx)("span",{className:"ml-2 text-xs text-[#3ee0a1]",children:"✓ Set"})]}),(0,t.jsx)(n.Input,{id:a.name,name:a.name,type:a.secret?"password":"text",autoComplete:"off",spellCheck:!1,placeholder:u[a.name]?"•••••••• (leave blank to keep, type to replace)":"",value:e[a.name]??"",onChange:t=>c({...e,[a.name]:t.target.value}),"aria-invalid":!!r&&!r.ok||void 0,"aria-describedby":`${a.name}-help ${a.name}-result`,className:"bg-[#0f1419] border-[#2d3748] text-gray-200 font-mono text-xs"}),(0,t.jsx)("p",{id:`${a.name}-help`,className:"text-xs text-gray-400",children:a.help}),(0,t.jsxs)("div",{id:`${a.name}-result`,"aria-live":"polite",children:[r&&!r.ok&&(0,t.jsxs)("p",{className:"text-xs text-[#f87171]",children:["✗ ",r.error]}),r?.ok&&(0,t.jsx)("p",{className:"text-xs text-[#3ee0a1]",children:"✓ Checked"})]})]},a.name)}),l.TOOLS[a].setupNote&&(0,t.jsx)("p",{className:"text-xs text-gray-400",children:l.TOOLS[a].setupNote})]},a)),(0,t.jsxs)("div",{className:"flex items-center gap-4",children:[(0,t.jsx)(i.Button,{type:"submit",disabled:f||!d.some(t=>e[t.name]?.trim()),children:f?"Checking…":"Check and save"}),(0,t.jsx)("span",{"aria-live":"polite",className:`text-sm ${p?.ok?"text-[#3ee0a1]":"text-[#f87171]"}`,children:p?.text}),!d.some(t=>e[t.name]?.trim())&&!p&&!b&&(0,t.jsx)("span",{className:"text-sm text-gray-400",children:k?.length?"Type a new key to change what is saved.":"Fill in both fields for at least one tool."}),b&&(0,t.jsx)(r.default,{href:"/connect",className:(0,i.buttonVariants)({variant:"outline"}),children:"Next: connect your app"})]})]})})]})}])},94179,e=>{"use strict";var t=e.i(46987),a=e.i(10063),r=e.i(94237),s=e.i(28603);let i=(0,r.cva)("group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",{variants:{variant:{default:"bg-primary text-primary-foreground [a]:hover:bg-primary/80",secondary:"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",destructive:"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",outline:"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",ghost:"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",link:"text-primary underline-offset-4 hover:underline"}},defaultVariants:{variant:"default"}});e.s(["Badge",0,function({className:e,variant:r="default",render:n,...o}){return(0,a.useRender)({defaultTagName:"span",props:(0,t.mergeProps)({className:(0,s.cn)(i({variant:r}),e)},o),render:n,state:{slot:"badge",variant:r}})}])},70065,e=>{"use strict";var t=e.i(40798),a=e.i(28603);e.s(["Card",0,function({className:e,size:r="default",...s}){return(0,t.jsx)("div",{"data-slot":"card","data-size":r,className:(0,a.cn)("group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",e),...s})},"CardContent",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-content",className:(0,a.cn)("px-(--card-spacing)",e),...r})},"CardDescription",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-description",className:(0,a.cn)("text-sm text-muted-foreground",e),...r})},"CardHeader",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-header",className:(0,a.cn)("group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",e),...r})},"CardTitle",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-title",className:(0,a.cn)("font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",e),...r})}])},91153,e=>{"use strict";var t=e.i(96341);let a=(...e)=>e.filter((e,t,a)=>!!e&&""!==e.trim()&&a.indexOf(e)===t).join(" ").trim(),r={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"},s=(0,t.createContext)({}),i=(0,t.forwardRef)(({color:e,size:i,width:n,height:o,strokeWidth:l,absoluteStrokeWidth:d,nonScalingStroke:c,className:u="",children:h,iconNode:m=[],icon:x={node:m,aliases:[],size:24},...p},g)=>{let{size:f=24,strokeWidth:v=2,absoluteStrokeWidth:b=!1,nonScalingStroke:y=!1,color:k="currentColor",className:j=""}=(0,t.useContext)(s)??{},N=!!h||(e=>{for(let t in e)if(t.startsWith("aria-")||"role"===t||"title"===t)return!0;return!1})(p),[w,C,S=[]]=function(e,t={}){return function(e,t={}){let s=t.attributeNames??{},i=e=>s[e]??e,n=e.size??e.width??r.width,o=e.size??e.height??r.height,l=e.aliases?.filter(e=>"string"==typeof e&&""!==e.trim()).map(e=>`lucide-${e}`)??[],d=[...e.name?[`lucide-${e.name}`]:[],...l],c=t.className?.split(" ").filter(Boolean)??[],u=!1===t.includeDefaultClasses?a(...c):a("lucide",...d,...c),h=t.absoluteStrokeWidth?Number(t.strokeWidth??r["stroke-width"])*Number(e.size??e.width??r.width)/Number(t.size??t.width??r.width):t.strokeWidth??r["stroke-width"];return["svg",{...Object.entries(r).reduce((e,[t,a])=>(e[i(t)]=a,e),{}),..."color"in t&&t.color&&{[i("stroke")]:t.color},..."size"in t&&null!=t.size&&{[i("width")]:t.size,[i("height")]:t.size},..."width"in t&&null!=t.width&&{[i("width")]:t.width},..."height"in t&&null!=t.height&&{[i("height")]:t.height},[i("stroke-width")]:h,...u&&{[i("class")]:u},[i("viewBox")]:`0 0 ${n} ${o}`,...!1===t.hasA11yProp?{[i("aria-hidden")]:"true"}:{},..."attributes"in t&&t.attributes},e.node.map(e=>{let[a,r,s]=e,n=t.nonScalingStroke?{[i("vector-effect")]:"non-scaling-stroke",...r}:r;return s?[a,n,s]:[a,n]})]}(e,{...t,attributeNames:{...t.attributeNames,class:"className","stroke-width":"strokeWidth","stroke-linecap":"strokeLinecap","stroke-linejoin":"strokeLinejoin","vector-effect":"vectorEffect"}})}(x,{color:e??k,width:n??i??f,height:o??i??f,strokeWidth:l??v,absoluteStrokeWidth:d??b,nonScalingStroke:c??y,className:a(j,u),hasA11yProp:N,attributes:p});return(0,t.createElement)(w,{ref:g,...C},[...S.map(([e,a])=>(0,t.createElement)(e,a)),...Array.isArray(h)?h:[h]])});e.s(["default",0,i],91153)}]);
1
+ (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,55370,e=>{"use strict";var t=e.i(40798),a=e.i(96341),r=e.i(70065),s=e.i(67881),i=e.i(23750);let n=[{id:"gemini",label:"Google Gemini",help:"Create a key at aistudio.google.com/apikey."},{id:"claude",label:"Anthropic Claude",help:"Create a key at console.anthropic.com > API keys."},{id:"openai",label:"OpenAI",help:"Create a key at platform.openai.com/api-keys."}];e.s(["AiKeyForm",0,function(){let[e,o]=(0,a.useState)(n[0].id),[l,d]=(0,a.useState)(""),[c,u]=(0,a.useState)(null),[h,m]=(0,a.useState)({}),[x,p]=(0,a.useState)(null),[g,f]=(0,a.useState)(!1);async function v(t){t.preventDefault(),f(!0),p(null);try{let t=await fetch("/api/setup/ai",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({provider:e,key:l})}),a=await t.json();a.saved?(d(""),u(e),m({...h,[e]:!0}),p({ok:!0,text:"Saved. AI suggestions now use this provider. No restart needed."})):p({ok:!1,text:a.error??"Nothing was saved."})}catch{p({ok:!1,text:"Could not reach the dashboard server."})}finally{f(!1)}}(0,a.useEffect)(()=>{fetch("/api/setup/ai").then(e=>e.json()).then(e=>{e.keys&&(m(e.keys),u(e.provider),e.provider&&o(e.provider))}).catch(()=>{})},[]);let b=n.find(t=>t.id===e);return(0,t.jsxs)(r.Card,{className:"border-[#2d3748] bg-[#1a202c] shadow-md max-w-2xl mt-6",children:[(0,t.jsxs)(r.CardHeader,{children:[(0,t.jsx)(r.CardTitle,{className:"text-[18px] font-bold text-white tracking-tight",children:"AI suggestions (optional)"}),(0,t.jsx)(r.CardDescription,{className:"text-sm text-gray-400",children:"Add a Gemini, Claude or OpenAI key to get AI insights and recommendations for your pages. Only page names, load times and error counts are sent to the provider you pick. Without a key, the dashboard shows sample suggestions."})]}),(0,t.jsx)(r.CardContent,{children:(0,t.jsxs)("form",{onSubmit:v,className:"flex flex-col gap-5",children:[(0,t.jsxs)("div",{className:"flex flex-col gap-1.5",children:[(0,t.jsx)("label",{htmlFor:"ai-provider",className:"text-sm font-medium text-gray-300",children:"Provider"}),(0,t.jsx)("select",{id:"ai-provider",value:e,onChange:e=>o(e.target.value),className:"h-9 rounded-md border border-[#2d3748] bg-[#0f1419] px-3 text-sm text-gray-200",children:n.map(e=>(0,t.jsxs)("option",{value:e.id,children:[e.label,c===e.id?" (in use)":h[e.id]?" (key saved)":""]},e.id))})]}),(0,t.jsxs)("div",{className:"flex flex-col gap-1.5",children:[(0,t.jsxs)("label",{htmlFor:"ai-key",className:"text-sm font-medium text-gray-300",children:[b.label," API key ",h[e]&&(0,t.jsx)("span",{className:"ml-2 text-xs text-[#3ee0a1]",children:"✓ Set"})]}),(0,t.jsx)(i.Input,{id:"ai-key",type:"password",autoComplete:"off",spellCheck:!1,placeholder:h[e]?"•••••••• (type to replace)":"",value:l,onChange:e=>d(e.target.value),"aria-describedby":"ai-key-help",className:"bg-[#0f1419] border-[#2d3748] text-gray-200 font-mono text-xs"}),(0,t.jsx)("p",{id:"ai-key-help",className:"text-xs text-gray-400",children:b.help})]}),(0,t.jsxs)("div",{className:"flex items-center gap-4",children:[(0,t.jsx)(s.Button,{type:"submit",disabled:g||!l.trim(),children:g?"Checking…":"Check and save"}),(0,t.jsx)("span",{"aria-live":"polite",className:`text-sm ${x?.ok?"text-[#3ee0a1]":"text-[#f87171]"}`,children:x?.text})]})]})})]})}])},4603,e=>{"use strict";var t=e.i(40798),a=e.i(96341),r=e.i(3547),s=e.i(70065),i=e.i(67881),n=e.i(23750),o=e.i(94179),l=e.i(93834);let d=l.TOOL_IDS.flatMap(e=>l.TOOLS[e].keys.required.map(t=>({name:t,tool:e,...l.KEY_FIELDS[t]})));e.s(["SetupForm",0,function(){let[e,c]=(0,a.useState)({}),[u,h]=(0,a.useState)({}),[m,x]=(0,a.useState)({}),[p,g]=(0,a.useState)(null),[f,v]=(0,a.useState)(!1),[b,y]=(0,a.useState)(!1),[k,j]=(0,a.useState)(null),N=(0,a.useRef)(null);async function w(t){t.preventDefault(),v(!0),g(null);try{let t=await fetch("/api/setup",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),a=await t.json(),r=a.results??{};x(r);let s=d.find(e=>r[e.name]&&!r[e.name].ok);if(s&&setTimeout(()=>N.current?.querySelector(`#${s.name}`)?.focus(),0),a.saved){y(!0),c({});let t=new Set(d.filter(t=>e[t.name]?.trim()).map(e=>e.tool));h(e=>({...e,...Object.fromEntries(d.filter(e=>t.has(e.tool)).map(e=>[e.name,!0]))}));let a=l.TOOL_IDS.filter(e=>t.has(e)||k?.includes(e));j(a),g({ok:!0,text:`Saved. ${a.map(e=>l.TOOLS[e].label).join(" and ")} ${a.length>1?"are":"is"} connected. No restart needed.`})}else g({ok:!1,text:a.error??"Nothing was saved. Fix the fields marked below."})}catch{g({ok:!1,text:"Could not reach the dashboard server."})}finally{v(!1)}}return(0,a.useEffect)(()=>{fetch("/api/setup").then(e=>e.json()).then(e=>{e.keys&&h(e.keys),Array.isArray(e.tools)?j(e.tools):e.error&&g({ok:!1,text:e.error})}).catch(()=>{})},[]),(0,t.jsxs)(s.Card,{className:"border-[#2d3748] bg-[#1a202c] shadow-md max-w-2xl",children:[(0,t.jsxs)(s.CardHeader,{children:[(0,t.jsx)(s.CardTitle,{className:"text-[18px] font-bold text-white tracking-tight",children:"Connect your tools"}),(0,t.jsx)(s.CardDescription,{className:"text-sm text-gray-400",children:"Add keys for the tools you use. You only need one. Keys are checked once, saved to .env.local in this folder, and never shown again."})]}),(0,t.jsx)(s.CardContent,{children:(0,t.jsxs)("form",{ref:N,onSubmit:w,className:"flex flex-col gap-5",children:[l.TOOL_IDS.map(a=>(0,t.jsxs)("fieldset",{id:a,className:"flex flex-col gap-5 rounded-lg border border-[#2d3748] p-4",children:[(0,t.jsxs)("legend",{className:"flex items-center gap-2 px-2 text-sm font-semibold text-white",children:[l.TOOLS[a].label,k?.includes(a)?(0,t.jsx)(o.Badge,{variant:"outline",className:"border-[#3ee0a1] text-[#3ee0a1] bg-[#3ee0a1]/10",children:"✓ Connected"}):(0,t.jsx)(o.Badge,{variant:"outline",className:"border-[#4a5568] text-gray-400",children:"Optional"})]}),d.filter(e=>e.tool===a).map(a=>{let r=m[a.name];return(0,t.jsxs)("div",{className:"flex flex-col gap-1.5",children:[(0,t.jsxs)("label",{htmlFor:a.name,className:"text-sm font-medium text-gray-300",children:[a.label," ",u[a.name]&&(0,t.jsx)("span",{className:"ml-2 text-xs text-[#3ee0a1]",children:"✓ Set"})]}),(0,t.jsx)(n.Input,{id:a.name,name:a.name,type:a.secret?"password":"text",autoComplete:"off",spellCheck:!1,placeholder:u[a.name]?"•••••••• (leave blank to keep, type to replace)":"",value:e[a.name]??"",onChange:t=>c({...e,[a.name]:t.target.value}),"aria-invalid":!!r&&!r.ok||void 0,"aria-describedby":`${a.name}-help ${a.name}-result`,className:"bg-[#0f1419] border-[#2d3748] text-gray-200 font-mono text-xs"}),(0,t.jsx)("p",{id:`${a.name}-help`,className:"text-xs text-gray-400",children:a.help}),(0,t.jsxs)("div",{id:`${a.name}-result`,"aria-live":"polite",children:[r&&!r.ok&&(0,t.jsxs)("p",{className:"text-xs text-[#f87171]",children:["✗ ",r.error]}),r?.ok&&(0,t.jsx)("p",{className:"text-xs text-[#3ee0a1]",children:"✓ Checked"}),r?.notice&&(0,t.jsx)("p",{className:"text-xs text-[#fbbf24]",children:r.notice})]})]},a.name)}),l.TOOLS[a].setupNote&&(0,t.jsx)("p",{className:"text-xs text-gray-400",children:l.TOOLS[a].setupNote})]},a)),(0,t.jsxs)("div",{className:"flex items-center gap-4",children:[(0,t.jsx)(i.Button,{type:"submit",disabled:f||!d.some(t=>e[t.name]?.trim()),children:f?"Checking…":"Check and save"}),(0,t.jsx)("span",{"aria-live":"polite",className:`text-sm ${p?.ok?"text-[#3ee0a1]":"text-[#f87171]"}`,children:p?.text}),!d.some(t=>e[t.name]?.trim())&&!p&&!b&&(0,t.jsx)("span",{className:"text-sm text-gray-400",children:k?.length?"Type a new key to change what is saved.":"Fill in both fields for at least one tool."}),b&&(0,t.jsx)(r.default,{href:"/connect",className:(0,i.buttonVariants)({variant:"outline"}),children:"Next: connect your app"})]})]})})]})}])},94179,e=>{"use strict";var t=e.i(46987),a=e.i(10063),r=e.i(94237),s=e.i(28603);let i=(0,r.cva)("group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",{variants:{variant:{default:"bg-primary text-primary-foreground [a]:hover:bg-primary/80",secondary:"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",destructive:"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",outline:"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",ghost:"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",link:"text-primary underline-offset-4 hover:underline"}},defaultVariants:{variant:"default"}});e.s(["Badge",0,function({className:e,variant:r="default",render:n,...o}){return(0,a.useRender)({defaultTagName:"span",props:(0,t.mergeProps)({className:(0,s.cn)(i({variant:r}),e)},o),render:n,state:{slot:"badge",variant:r}})}])},70065,e=>{"use strict";var t=e.i(40798),a=e.i(28603);e.s(["Card",0,function({className:e,size:r="default",...s}){return(0,t.jsx)("div",{"data-slot":"card","data-size":r,className:(0,a.cn)("group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",e),...s})},"CardContent",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-content",className:(0,a.cn)("px-(--card-spacing)",e),...r})},"CardDescription",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-description",className:(0,a.cn)("text-sm text-muted-foreground",e),...r})},"CardHeader",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-header",className:(0,a.cn)("group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",e),...r})},"CardTitle",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-title",className:(0,a.cn)("font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",e),...r})}])},91153,e=>{"use strict";var t=e.i(96341);let a=(...e)=>e.filter((e,t,a)=>!!e&&""!==e.trim()&&a.indexOf(e)===t).join(" ").trim(),r={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"},s=(0,t.createContext)({}),i=(0,t.forwardRef)(({color:e,size:i,width:n,height:o,strokeWidth:l,absoluteStrokeWidth:d,nonScalingStroke:c,className:u="",children:h,iconNode:m=[],icon:x={node:m,aliases:[],size:24},...p},g)=>{let{size:f=24,strokeWidth:v=2,absoluteStrokeWidth:b=!1,nonScalingStroke:y=!1,color:k="currentColor",className:j=""}=(0,t.useContext)(s)??{},N=!!h||(e=>{for(let t in e)if(t.startsWith("aria-")||"role"===t||"title"===t)return!0;return!1})(p),[w,C,S=[]]=function(e,t={}){return function(e,t={}){let s=t.attributeNames??{},i=e=>s[e]??e,n=e.size??e.width??r.width,o=e.size??e.height??r.height,l=e.aliases?.filter(e=>"string"==typeof e&&""!==e.trim()).map(e=>`lucide-${e}`)??[],d=[...e.name?[`lucide-${e.name}`]:[],...l],c=t.className?.split(" ").filter(Boolean)??[],u=!1===t.includeDefaultClasses?a(...c):a("lucide",...d,...c),h=t.absoluteStrokeWidth?Number(t.strokeWidth??r["stroke-width"])*Number(e.size??e.width??r.width)/Number(t.size??t.width??r.width):t.strokeWidth??r["stroke-width"];return["svg",{...Object.entries(r).reduce((e,[t,a])=>(e[i(t)]=a,e),{}),..."color"in t&&t.color&&{[i("stroke")]:t.color},..."size"in t&&null!=t.size&&{[i("width")]:t.size,[i("height")]:t.size},..."width"in t&&null!=t.width&&{[i("width")]:t.width},..."height"in t&&null!=t.height&&{[i("height")]:t.height},[i("stroke-width")]:h,...u&&{[i("class")]:u},[i("viewBox")]:`0 0 ${n} ${o}`,...!1===t.hasA11yProp?{[i("aria-hidden")]:"true"}:{},..."attributes"in t&&t.attributes},e.node.map(e=>{let[a,r,s]=e,n=t.nonScalingStroke?{[i("vector-effect")]:"non-scaling-stroke",...r}:r;return s?[a,n,s]:[a,n]})]}(e,{...t,attributeNames:{...t.attributeNames,class:"className","stroke-width":"strokeWidth","stroke-linecap":"strokeLinecap","stroke-linejoin":"strokeLinejoin","vector-effect":"vectorEffect"}})}(x,{color:e??k,width:n??i??f,height:o??i??f,strokeWidth:l??v,absoluteStrokeWidth:d??b,nonScalingStroke:c??y,className:a(j,u),hasA11yProp:N,attributes:p});return(0,t.createElement)(w,{ref:g,...C},[...S.map(([e,a])=>(0,t.createElement)(e,a)),...Array.isArray(h)?h:[h]])});e.s(["default",0,i],91153)}]);
package/.next/trace CHANGED
@@ -1 +1 @@
1
- [{"name":"generate-buildid","duration":235,"timestamp":195875944125,"id":4,"parentId":1,"tags":{},"startTime":1791458912349,"traceId":"ccd7353a27ddd017"},{"name":"load-custom-routes","duration":378,"timestamp":195875944473,"id":5,"parentId":1,"tags":{},"startTime":1791458912349,"traceId":"ccd7353a27ddd017"},{"name":"create-dist-dir","duration":202,"timestamp":195875944873,"id":6,"parentId":1,"tags":{},"startTime":1791458912349,"traceId":"ccd7353a27ddd017"},{"name":"clean","duration":89620,"timestamp":195875946532,"id":7,"parentId":1,"tags":{},"startTime":1791458912351,"traceId":"ccd7353a27ddd017"},{"name":"discover-routes","duration":8620,"timestamp":195876049310,"id":8,"parentId":1,"tags":{},"startTime":1791458912454,"traceId":"ccd7353a27ddd017"},{"name":"create-root-mapping","duration":98,"timestamp":195876057969,"id":9,"parentId":1,"tags":{},"startTime":1791458912462,"traceId":"ccd7353a27ddd017"},{"name":"generate-route-types","duration":17375,"timestamp":195876059403,"id":10,"parentId":1,"tags":{},"startTime":1791458912464,"traceId":"ccd7353a27ddd017"},{"name":"public-dir-conflict-check","duration":90,"timestamp":195876076845,"id":11,"parentId":1,"tags":{},"startTime":1791458912481,"traceId":"ccd7353a27ddd017"},{"name":"generate-routes-manifest","duration":2441,"timestamp":195876077027,"id":12,"parentId":1,"tags":{},"startTime":1791458912481,"traceId":"ccd7353a27ddd017"},{"name":"run-turbopack","duration":4612388,"timestamp":195876091520,"id":14,"parentId":1,"tags":{},"startTime":1791458912496,"traceId":"ccd7353a27ddd017"},{"name":"turbopack-build-events","duration":78,"timestamp":195876584860,"id":15,"parentId":1,"tags":{},"startTime":1791458912989,"traceId":"ccd7353a27ddd017"},{"name":"turbopack-persistence","duration":514000,"timestamp":195880111518,"id":16,"parentId":15,"tags":{"reason":"stop","snapshot_duration_ms":11.285798999999999,"persist_duration_ms":502.00348699999995,"task_count":4096,"bytes_written":27777509,"bytes_deleted":15437900},"startTime":1791458917030,"traceId":"ccd7353a27ddd017"},{"name":"run-typescript","duration":3915607,"timestamp":195880707970,"id":16,"parentId":1,"tags":{},"startTime":1791458917113,"traceId":"ccd7353a27ddd017"},{"name":"generate-required-server-files","duration":1056,"timestamp":195884624563,"id":18,"parentId":1,"tags":{},"startTime":1791458921029,"traceId":"ccd7353a27ddd017"},{"name":"check-static-error-page","duration":15103,"timestamp":195884658339,"id":20,"parentId":19,"tags":{},"startTime":1791458921063,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":1439909,"timestamp":195884675705,"id":40,"parentId":22,"tags":{},"startTime":1791458921080,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":1452540,"timestamp":195884663251,"id":22,"parentId":19,"tags":{"page":"/_global-error"},"startTime":1791458921068,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2035434,"timestamp":195884675902,"id":43,"parentId":25,"tags":{},"startTime":1791458921081,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2046027,"timestamp":195884665375,"id":25,"parentId":19,"tags":{"page":"/api/health"},"startTime":1791458921070,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2066547,"timestamp":195884676025,"id":45,"parentId":27,"tags":{},"startTime":1791458921081,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2076951,"timestamp":195884665672,"id":27,"parentId":19,"tags":{"page":"/api/settings"},"startTime":1791458921070,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2103429,"timestamp":195884675381,"id":39,"parentId":21,"tags":{},"startTime":1791458921080,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2119297,"timestamp":195884659578,"id":21,"parentId":19,"tags":{"page":"/_not-found"},"startTime":1791458921064,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2106495,"timestamp":195884676246,"id":48,"parentId":30,"tags":{},"startTime":1791458921081,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2116347,"timestamp":195884666458,"id":30,"parentId":19,"tags":{"page":"/api/timings"},"startTime":1791458921071,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2131224,"timestamp":195884682260,"id":50,"parentId":31,"tags":{},"startTime":1791458921087,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2146878,"timestamp":195884666669,"id":31,"parentId":19,"tags":{"page":"/connect"},"startTime":1791458921071,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2162613,"timestamp":195884682309,"id":53,"parentId":36,"tags":{},"startTime":1791458921087,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2174125,"timestamp":195884670859,"id":36,"parentId":19,"tags":{"page":"/settings"},"startTime":1791458921076,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2287506,"timestamp":195884682300,"id":52,"parentId":35,"tags":{},"startTime":1791458921087,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2299594,"timestamp":195884670288,"id":35,"parentId":19,"tags":{"page":"/performance"},"startTime":1791458921075,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2304175,"timestamp":195884675782,"id":41,"parentId":23,"tags":{},"startTime":1791458921080,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2315342,"timestamp":195884664690,"id":23,"parentId":19,"tags":{"page":"/api/analyze"},"startTime":1791458921069,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2311156,"timestamp":195884682332,"id":56,"parentId":38,"tags":{},"startTime":1791458921087,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2321838,"timestamp":195884671716,"id":38,"parentId":19,"tags":{"page":"/tools/[tool]"},"startTime":1791458921076,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2320209,"timestamp":195884675955,"id":44,"parentId":26,"tags":{},"startTime":1791458921081,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2330709,"timestamp":195884665534,"id":26,"parentId":19,"tags":{"page":"/api/metrics"},"startTime":1791458921070,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2342219,"timestamp":195884675845,"id":42,"parentId":24,"tags":{},"startTime":1791458921081,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2352960,"timestamp":195884665144,"id":24,"parentId":19,"tags":{"page":"/api/connect"},"startTime":1791458921070,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2337515,"timestamp":195884682289,"id":51,"parentId":33,"tags":{},"startTime":1791458921087,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2352788,"timestamp":195884667054,"id":33,"parentId":19,"tags":{"page":"/"},"startTime":1791458921072,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2347370,"timestamp":195884682317,"id":54,"parentId":37,"tags":{},"startTime":1791458921087,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2358433,"timestamp":195884671303,"id":37,"parentId":19,"tags":{"page":"/setup"},"startTime":1791458921076,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2358389,"timestamp":195884676100,"id":46,"parentId":28,"tags":{},"startTime":1791458921081,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2368735,"timestamp":195884665795,"id":28,"parentId":19,"tags":{"page":"/api/setup/ai"},"startTime":1791458921071,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2389486,"timestamp":195884676179,"id":47,"parentId":29,"tags":{},"startTime":1791458921081,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2399791,"timestamp":195884665918,"id":29,"parentId":19,"tags":{"page":"/api/setup"},"startTime":1791458921071,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2389715,"timestamp":195884682325,"id":55,"parentId":34,"tags":{},"startTime":1791458921087,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2404805,"timestamp":195884667307,"id":34,"parentId":19,"tags":{"page":"/performance/[slug]"},"startTime":1791458921072,"traceId":"ccd7353a27ddd017"},{"name":"is-page-static","duration":2395883,"timestamp":195884676308,"id":49,"parentId":32,"tags":{},"startTime":1791458921081,"traceId":"ccd7353a27ddd017"},{"name":"check-page","duration":2405327,"timestamp":195884666882,"id":32,"parentId":19,"tags":{"page":"/icon.svg"},"startTime":1791458921072,"traceId":"ccd7353a27ddd017"},{"name":"static-check","duration":2414654,"timestamp":195884657581,"id":19,"parentId":1,"tags":{},"startTime":1791458921062,"traceId":"ccd7353a27ddd017"},{"name":"write-routes-manifest","duration":641,"timestamp":195887077521,"id":58,"parentId":1,"tags":{},"startTime":1791458923482,"traceId":"ccd7353a27ddd017"},{"name":"load-dotenv","duration":75,"timestamp":195887095391,"id":61,"parentId":60,"tags":{},"startTime":1791458923500,"traceId":"ccd7353a27ddd017"},{"name":"run-export-path-map","duration":635,"timestamp":195887097682,"id":62,"parentId":60,"tags":{},"startTime":1791458923502,"traceId":"ccd7353a27ddd017"},{"name":"next-export","duration":781031,"timestamp":195887094073,"id":60,"parentId":1,"tags":{},"startTime":1791458923499,"traceId":"ccd7353a27ddd017"},{"name":"move-exported-app-not-found-","duration":4769,"timestamp":195887877661,"id":63,"parentId":59,"tags":{},"startTime":1791458924282,"traceId":"ccd7353a27ddd017"},{"name":"move-exported-app-global-error-","duration":1023,"timestamp":195887882575,"id":64,"parentId":59,"tags":{},"startTime":1791458924287,"traceId":"ccd7353a27ddd017"},{"name":"static-generation","duration":803187,"timestamp":195887080865,"id":59,"parentId":1,"tags":{},"startTime":1791458923486,"traceId":"ccd7353a27ddd017"},{"name":"write-routes-manifest","duration":692,"timestamp":195887884316,"id":65,"parentId":1,"tags":{},"startTime":1791458924289,"traceId":"ccd7353a27ddd017"},{"name":"print-tree-view","duration":6196,"timestamp":195887949662,"id":66,"parentId":1,"tags":{},"startTime":1791458924354,"traceId":"ccd7353a27ddd017"},{"name":"write-route-bundle-stats","duration":9974,"timestamp":195887955918,"id":67,"parentId":1,"tags":{},"startTime":1791458924361,"traceId":"ccd7353a27ddd017"},{"name":"telemetry-flush","duration":449231,"timestamp":195887965916,"id":68,"parentId":1,"tags":{},"startTime":1791458924371,"traceId":"ccd7353a27ddd017"},{"name":"next-build","duration":12602693,"timestamp":195875812497,"id":1,"tags":{"buildMode":"default","version":"16.3.8","bundler":"turbopack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1791458912217,"traceId":"ccd7353a27ddd017"}]
1
+ [{"name":"generate-buildid","duration":195,"timestamp":225574729878,"id":4,"parentId":1,"tags":{},"startTime":1791488611532,"traceId":"d667950cb0b8d0fa"},{"name":"load-custom-routes","duration":358,"timestamp":225574730182,"id":5,"parentId":1,"tags":{},"startTime":1791488611532,"traceId":"d667950cb0b8d0fa"},{"name":"create-dist-dir","duration":197,"timestamp":225574730561,"id":6,"parentId":1,"tags":{},"startTime":1791488611533,"traceId":"d667950cb0b8d0fa"},{"name":"clean","duration":70352,"timestamp":225574731438,"id":7,"parentId":1,"tags":{},"startTime":1791488611534,"traceId":"d667950cb0b8d0fa"},{"name":"discover-routes","duration":6459,"timestamp":225574810483,"id":8,"parentId":1,"tags":{},"startTime":1791488611613,"traceId":"d667950cb0b8d0fa"},{"name":"create-root-mapping","duration":105,"timestamp":225574816980,"id":9,"parentId":1,"tags":{},"startTime":1791488611619,"traceId":"d667950cb0b8d0fa"},{"name":"generate-route-types","duration":18668,"timestamp":225574818636,"id":10,"parentId":1,"tags":{},"startTime":1791488611621,"traceId":"d667950cb0b8d0fa"},{"name":"public-dir-conflict-check","duration":91,"timestamp":225574837378,"id":11,"parentId":1,"tags":{},"startTime":1791488611639,"traceId":"d667950cb0b8d0fa"},{"name":"generate-routes-manifest","duration":2802,"timestamp":225574837580,"id":12,"parentId":1,"tags":{},"startTime":1791488611640,"traceId":"d667950cb0b8d0fa"},{"name":"run-turbopack","duration":2461637,"timestamp":225574844912,"id":14,"parentId":1,"tags":{},"startTime":1791488611647,"traceId":"d667950cb0b8d0fa"},{"name":"turbopack-build-events","duration":89,"timestamp":225575219629,"id":15,"parentId":1,"tags":{},"startTime":1791488612022,"traceId":"d667950cb0b8d0fa"},{"name":"turbopack-persistence","duration":132000,"timestamp":225576771967,"id":16,"parentId":15,"tags":{"reason":"stop","snapshot_duration_ms":3.495223,"persist_duration_ms":128.436506,"task_count":1010,"bytes_written":1061108,"bytes_deleted":0},"startTime":1791488613708,"traceId":"d667950cb0b8d0fa"},{"name":"run-typescript","duration":3376122,"timestamp":225577311004,"id":18,"parentId":1,"tags":{},"startTime":1791488614113,"traceId":"d667950cb0b8d0fa"},{"name":"generate-required-server-files","duration":903,"timestamp":225580687784,"id":20,"parentId":1,"tags":{},"startTime":1791488617490,"traceId":"d667950cb0b8d0fa"},{"name":"check-static-error-page","duration":12632,"timestamp":225580713918,"id":22,"parentId":21,"tags":{},"startTime":1791488617516,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":921749,"timestamp":225580734428,"id":42,"parentId":24,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":939692,"timestamp":225580716610,"id":24,"parentId":21,"tags":{"page":"/_global-error"},"startTime":1791488617519,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1159079,"timestamp":225580734709,"id":46,"parentId":28,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1176579,"timestamp":225580717251,"id":28,"parentId":21,"tags":{"page":"/api/metrics"},"startTime":1791488617520,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1218907,"timestamp":225580738566,"id":53,"parentId":35,"tags":{},"startTime":1791488617541,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1235827,"timestamp":225580721686,"id":35,"parentId":21,"tags":{"page":"/"},"startTime":1791488617524,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1266431,"timestamp":225580738553,"id":52,"parentId":33,"tags":{},"startTime":1791488617541,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1283563,"timestamp":225580721464,"id":33,"parentId":21,"tags":{"page":"/connect"},"startTime":1791488617524,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1299488,"timestamp":225580738585,"id":55,"parentId":38,"tags":{},"startTime":1791488617541,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1311929,"timestamp":225580726193,"id":38,"parentId":21,"tags":{"page":"/settings"},"startTime":1791488617528,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1306361,"timestamp":225580738591,"id":56,"parentId":39,"tags":{},"startTime":1791488617541,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1318714,"timestamp":225580726290,"id":39,"parentId":21,"tags":{"page":"/setup"},"startTime":1791488617529,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1326280,"timestamp":225580734654,"id":45,"parentId":27,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1343853,"timestamp":225580717133,"id":27,"parentId":21,"tags":{"page":"/api/health"},"startTime":1791488617519,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1334372,"timestamp":225580735054,"id":50,"parentId":32,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1348119,"timestamp":225580721348,"id":32,"parentId":21,"tags":{"page":"/api/timings"},"startTime":1791488617524,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1331153,"timestamp":225580738578,"id":54,"parentId":37,"tags":{},"startTime":1791488617541,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1343693,"timestamp":225580726076,"id":37,"parentId":21,"tags":{"page":"/performance"},"startTime":1791488617528,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1356377,"timestamp":225580734099,"id":41,"parentId":23,"tags":{},"startTime":1791488617536,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1375685,"timestamp":225580714852,"id":23,"parentId":21,"tags":{"page":"/_not-found"},"startTime":1791488617517,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1372132,"timestamp":225580734766,"id":47,"parentId":29,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1386016,"timestamp":225580720920,"id":29,"parentId":21,"tags":{"page":"/api/settings"},"startTime":1791488617523,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1379312,"timestamp":225580735108,"id":51,"parentId":34,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1392863,"timestamp":225580721586,"id":34,"parentId":21,"tags":{"page":"/icon.svg"},"startTime":1791488617524,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1391141,"timestamp":225580734919,"id":48,"parentId":30,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1404995,"timestamp":225580721094,"id":30,"parentId":21,"tags":{"page":"/api/setup/ai"},"startTime":1791488617523,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1394169,"timestamp":225580734494,"id":43,"parentId":25,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1411780,"timestamp":225580716910,"id":25,"parentId":21,"tags":{"page":"/api/analyze"},"startTime":1791488617519,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1397227,"timestamp":225580734541,"id":44,"parentId":26,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1414770,"timestamp":225580717028,"id":26,"parentId":21,"tags":{"page":"/api/connect"},"startTime":1791488617519,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1394265,"timestamp":225580739378,"id":57,"parentId":36,"tags":{},"startTime":1791488617542,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1411861,"timestamp":225580721809,"id":36,"parentId":21,"tags":{"page":"/performance/[slug]"},"startTime":1791488617524,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1403370,"timestamp":225580734992,"id":49,"parentId":31,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1417188,"timestamp":225580721213,"id":31,"parentId":21,"tags":{"page":"/api/setup"},"startTime":1791488617523,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1408600,"timestamp":225580739893,"id":58,"parentId":40,"tags":{},"startTime":1791488617542,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1422148,"timestamp":225580726375,"id":40,"parentId":21,"tags":{"page":"/tools/[tool]"},"startTime":1791488617529,"traceId":"d667950cb0b8d0fa"},{"name":"static-check","duration":1435105,"timestamp":225580713444,"id":21,"parentId":1,"tags":{},"startTime":1791488617516,"traceId":"d667950cb0b8d0fa"},{"name":"write-routes-manifest","duration":563,"timestamp":225582152538,"id":60,"parentId":1,"tags":{},"startTime":1791488618955,"traceId":"d667950cb0b8d0fa"},{"name":"load-dotenv","duration":36,"timestamp":225582163227,"id":63,"parentId":62,"tags":{},"startTime":1791488618966,"traceId":"d667950cb0b8d0fa"},{"name":"run-export-path-map","duration":763,"timestamp":225582173166,"id":64,"parentId":62,"tags":{},"startTime":1791488618975,"traceId":"d667950cb0b8d0fa"},{"name":"next-export","duration":565282,"timestamp":225582162201,"id":62,"parentId":1,"tags":{},"startTime":1791488618964,"traceId":"d667950cb0b8d0fa"},{"name":"move-exported-app-not-found-","duration":885,"timestamp":225582729509,"id":65,"parentId":61,"tags":{},"startTime":1791488619532,"traceId":"d667950cb0b8d0fa"},{"name":"move-exported-app-global-error-","duration":657,"timestamp":225582730465,"id":66,"parentId":61,"tags":{},"startTime":1791488619533,"traceId":"d667950cb0b8d0fa"},{"name":"static-generation","duration":576449,"timestamp":225582154941,"id":61,"parentId":1,"tags":{},"startTime":1791488618957,"traceId":"d667950cb0b8d0fa"},{"name":"write-routes-manifest","duration":263,"timestamp":225582731530,"id":67,"parentId":1,"tags":{},"startTime":1791488619534,"traceId":"d667950cb0b8d0fa"},{"name":"print-tree-view","duration":2766,"timestamp":225582793552,"id":68,"parentId":1,"tags":{},"startTime":1791488619596,"traceId":"d667950cb0b8d0fa"},{"name":"write-route-bundle-stats","duration":7697,"timestamp":225582796335,"id":69,"parentId":1,"tags":{},"startTime":1791488619599,"traceId":"d667950cb0b8d0fa"},{"name":"telemetry-flush","duration":468791,"timestamp":225582804048,"id":70,"parentId":1,"tags":{},"startTime":1791488619606,"traceId":"d667950cb0b8d0fa"},{"name":"next-build","duration":8658056,"timestamp":225574614817,"id":1,"tags":{"buildMode":"default","version":"16.3.8","bundler":"turbopack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1791488611417,"traceId":"d667950cb0b8d0fa"}]
package/.next/trace-build CHANGED
@@ -1 +1 @@
1
- [{"name":"run-turbopack","duration":4612388,"timestamp":195876091520,"id":14,"parentId":1,"tags":{},"startTime":1791458912496,"traceId":"ccd7353a27ddd017"},{"name":"turbopack-build-events","duration":78,"timestamp":195876584860,"id":15,"parentId":1,"tags":{},"startTime":1791458912989,"traceId":"ccd7353a27ddd017"},{"name":"turbopack-persistence","duration":514000,"timestamp":195880111518,"id":16,"parentId":15,"tags":{"reason":"stop","snapshot_duration_ms":11.285798999999999,"persist_duration_ms":502.00348699999995,"task_count":4096,"bytes_written":27777509,"bytes_deleted":15437900},"startTime":1791458917030,"traceId":"ccd7353a27ddd017"},{"name":"run-typescript","duration":3915607,"timestamp":195880707970,"id":16,"parentId":1,"tags":{},"startTime":1791458917113,"traceId":"ccd7353a27ddd017"},{"name":"static-check","duration":2414654,"timestamp":195884657581,"id":19,"parentId":1,"tags":{},"startTime":1791458921062,"traceId":"ccd7353a27ddd017"},{"name":"static-generation","duration":803187,"timestamp":195887080865,"id":59,"parentId":1,"tags":{},"startTime":1791458923486,"traceId":"ccd7353a27ddd017"},{"name":"telemetry-flush","duration":449231,"timestamp":195887965916,"id":68,"parentId":1,"tags":{},"startTime":1791458924371,"traceId":"ccd7353a27ddd017"},{"name":"next-build","duration":12602693,"timestamp":195875812497,"id":1,"tags":{"buildMode":"default","version":"16.3.8","bundler":"turbopack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1791458912217,"traceId":"ccd7353a27ddd017"}]
1
+ [{"name":"run-turbopack","duration":2461637,"timestamp":225574844912,"id":14,"parentId":1,"tags":{},"startTime":1791488611647,"traceId":"d667950cb0b8d0fa"},{"name":"turbopack-build-events","duration":89,"timestamp":225575219629,"id":15,"parentId":1,"tags":{},"startTime":1791488612022,"traceId":"d667950cb0b8d0fa"},{"name":"turbopack-persistence","duration":132000,"timestamp":225576771967,"id":16,"parentId":15,"tags":{"reason":"stop","snapshot_duration_ms":3.495223,"persist_duration_ms":128.436506,"task_count":1010,"bytes_written":1061108,"bytes_deleted":0},"startTime":1791488613708,"traceId":"d667950cb0b8d0fa"},{"name":"run-typescript","duration":3376122,"timestamp":225577311004,"id":18,"parentId":1,"tags":{},"startTime":1791488614113,"traceId":"d667950cb0b8d0fa"},{"name":"static-check","duration":1435105,"timestamp":225580713444,"id":21,"parentId":1,"tags":{},"startTime":1791488617516,"traceId":"d667950cb0b8d0fa"},{"name":"static-generation","duration":576449,"timestamp":225582154941,"id":61,"parentId":1,"tags":{},"startTime":1791488618957,"traceId":"d667950cb0b8d0fa"},{"name":"telemetry-flush","duration":468791,"timestamp":225582804048,"id":70,"parentId":1,"tags":{},"startTime":1791488619606,"traceId":"d667950cb0b8d0fa"},{"name":"next-build","duration":8658056,"timestamp":225574614817,"id":1,"tags":{"buildMode":"default","version":"16.3.8","bundler":"turbopack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1791488611417,"traceId":"d667950cb0b8d0fa"}]
package/README.md CHANGED
@@ -64,7 +64,7 @@ You need five values. Where to find each one:
64
64
  | Sentry **organization slug** | The name in your Sentry URL: `sentry.io/organizations/<slug>/`. |
65
65
  | Sentry **project slug** | Sentry → Project settings. |
66
66
 
67
- Two more values are needed only to send data *from* your site (the snippets on `/connect`):
67
+ Two more values are needed only to send data *from* your site (the `init` call shown on `/connect`, which fills them in when it can read them):
68
68
 
69
69
  | Value | Where to find it |
70
70
  | --- | --- |
@@ -91,20 +91,60 @@ Save them in one of two ways:
91
91
 
92
92
  ### 3. Send data from your site
93
93
 
94
- MetricFlow only reads data. Your website has to send it to New Relic and Sentry first. After saving your keys, click **Next: connect your app** (or open `/connect`). That page gives you copy-paste snippets:
94
+ MetricFlow only reads data. Your website has to send it to New Relic and Sentry first. After saving your keys, click **Next: connect your app** (or open `/connect`). That page gives you one snippet to copy: a single `init` call with a block for each tool you connected, already filled in with the public values the dashboard could read (your Sentry DSN, your New Relic Browser key, application ID and account ID). Anything it could not read is shown as an example marked `replace`.
95
95
 
96
- 1. **New Relic Browser agent**: records page views, load timing and Core Web Vitals.
97
- 2. **`emitMetric()` helper** (optional): sends your own numbers through the Browser agent.
98
- 3. **Sentry**: sends JavaScript errors. Use your project's DSN.
96
+ Add it to your site, deploy or run it, and open a few pages.
99
97
 
100
- Add them to your site, deploy or run it, and open a few pages.
98
+ #### One install, one call
99
+
100
+ Your site does not have to install `@sentry/browser` or `@newrelic/browser-agent`. MetricFlow ships both and loads each one only when you ask for it:
101
+
102
+ ```bash
103
+ npm i @karan171996/metricflow
104
+ ```
105
+
106
+ ```ts
107
+ // instrumentation-client.ts (Next.js 15.3 or newer; in any other app, a file that runs only in the browser)
108
+ import { init } from '@karan171996/metricflow/browser';
109
+
110
+ init({
111
+ sentry: { dsn: 'https://0123456789abcdef0123456789abcdef@o123.ingest.sentry.io/456' },
112
+ 'new-relic': { browserKey: 'NRJS-fake0000', applicationId: '123456789', accountId: '1234567' },
113
+ });
114
+ ```
115
+
116
+ Name only the tools you use. Call `init` once. It never throws, and it does nothing during server rendering or a build.
117
+
118
+ | Option | Value |
119
+ |---|---|
120
+ | `sentry.dsn` | Sentry > Project settings > Client Keys (DSN) |
121
+ | `sentry.tracesSampleRate` | Optional, 0 to 1. Default: `1` on `localhost` and `127.0.0.1`, `0.1` everywhere else |
122
+ | `'new-relic'.browserKey` | The **Ingest - Browser** key. It starts with `NRJS-` |
123
+ | `'new-relic'.applicationId` | The Browser app's application ID (a number, as a string) |
124
+ | `'new-relic'.accountId` | Your account ID (a number, as a string) |
125
+ | `'new-relic'.region` | Optional: `'us'` (default) or `'eu'` |
126
+
127
+ To record your own numbers (New Relic only): `import { send } from '@karan171996/metricflow/browser'; send('checkout_step', 2, { step: 'payment' });`. Before `init`, `send` does nothing.
128
+
129
+ Things to know:
130
+
131
+ - **Public identifiers only.** Everything you pass to `init` is published in your site's JavaScript. If `init` sees a secret (a New Relic `NRAK-` User key or any other non-browser New Relic key, a Sentry auth token, a legacy DSN with a secret in it), it starts nothing and prints one console warning. That key has already been published: revoke it and create a new one.
132
+ - **One bad value starts no tool.** The console warning names the tool and the reason, never the value.
133
+ - **You already run Sentry or New Relic.** MetricFlow leaves it alone and says so once in the console. For Sentry, call MetricFlow's `init` after your own Sentry has started; if yours starts later, MetricFlow cannot see it and the page reports twice.
134
+ - **Content Security Policy.** Add these hosts to `connect-src`: the host in your Sentry DSN (for example `o123.ingest.sentry.io`), and `bam.nr-data.net` for New Relic (`bam.eu01.nr-data.net` for an EU account). The vendor code itself is bundled with your site, so `script-src` needs nothing new.
135
+ - **Page names are real paths.** Sentry page loads are named by `location.pathname` (`/orders/8841`, not `/orders/[id]`), so the dashboard can match them. Any ID or token that sits in a path is sent to Sentry.
136
+ - **Privacy defaults, and what is still sent.** MetricFlow turns off what the dashboard does not read, but both tools still collect data about your visitors. Check it against your own privacy notice.
137
+ - **Sentry sends:** JavaScript errors with stack traces; breadcrumbs leading up to an error (console output, the selector of clicked elements, fetch/XHR URLs, page navigations); the page URL, referrer and user agent; the browser's language and time zone; one session record per page load; and performance spans for page loads, in-app navigations, web vitals, fetch/XHR requests and loaded resources. It adds `sentry-trace` and `baggage` headers to requests to your own origin. **Off:** `sendDefaultPii` is `false`, and session replay, the feedback widget and profiling are not included.
138
+ - **New Relic sends:** page views with the page URL (query string and hash removed); load timing and web vitals, with the selector of the element behind LCP and INP; JavaScript errors with stack traces; and for each fetch/XHR request the host, path, method, status, size and timing (no bodies, no headers, no query strings). **Off:** cookies and session storage, session replay and session trace (not included at all), click/keyboard "user action" events, Content-Security-Policy violation events, and tracing headers on your requests.
139
+ - **One audit finding you will see.** `npm audit` reports a moderate advisory, GHSA-px8p-9vwx-vf98, in `fflate`, which comes with New Relic's agent. It does not apply here: the affected function, `unzipSync`, is never called. The agent uses `fflate` only inside its session-replay feature, which MetricFlow's build leaves out. It clears when New Relic ships `fflate` 0.8.3 or later.
140
+ - **Bundler needed.** The entry is ESM only and uses `import()`, which webpack, Turbopack, Vite and Rollup split into separate files: a site that never calls `init` downloads no vendor code, and a New Relic-only site never downloads Sentry.
101
141
 
102
142
  ### 4. Check that metrics arrive
103
143
 
104
144
  On `/connect`, the **Events received** panel shows a row for each source:
105
145
 
106
146
  - **Browser agent (page views)**
107
- - **Custom events (emitMetric / test event)**
147
+ - **Custom events (send / test event)**
108
148
  - **Sentry errors**
109
149
 
110
150
  Reload a few pages of your site and watch the rows turn green. New Relic can take a minute or two to show new data.
@@ -228,6 +268,7 @@ pnpm send-test-data # one dummy event to Sentry and New Relic
228
268
  app/ Next.js App Router: pages and api/ route handlers
229
269
  components/ UI, one folder per feature; ui/ is generated shadcn code
230
270
  lib/ Data fetching (New Relic, Sentry), transforms, hooks
271
+ browser/ What a consumer site imports (init, send). Sealed: imports nothing from lib/
231
272
  bin/ CLI (cli.mjs) and output formatter (output.mjs)
232
273
  scripts/ send-test-data.mjs, test-env.mjs
233
274
  test/ node:test suites
package/bin/cli.mjs CHANGED
@@ -60,7 +60,8 @@ const server = spawn(process.execPath, [nextBin, "start", "-p", port, "-H", host
60
60
  METRICFLOW_PROJECT_NAME: projectName, // set it yourself to override the title
61
61
  METRICFLOW_PROJECT_DIR: process.cwd(), // server cwd is the package; writes (.env.local, settings) belong here
62
62
  ...process.env,
63
- ...(loopback ? {} : { METRICFLOW_EXPOSED: "1" }), // app's isLocalRequest honors this
63
+ // app's isLocalRequest honors this. On loopback it is emptied, so a METRICFLOW_EXPOSED left in the user's shell cannot switch the API guard off.
64
+ METRICFLOW_EXPOSED: loopback ? "" : "1",
64
65
  ...(process.argv.includes("--no-color") || wantsJson ? { FORCE_COLOR: undefined, NO_COLOR: "1" } : {}),
65
66
  },
66
67
  });
@@ -0,0 +1,27 @@
1
+ export type Attrs = Record<string, string | number | boolean>;
2
+ export interface SentryOptions {
3
+ dsn: string;
4
+ tracesSampleRate?: number;
5
+ }
6
+ export interface NewRelicOptions {
7
+ browserKey: string;
8
+ applicationId: string;
9
+ accountId: string;
10
+ region?: 'us' | 'eu';
11
+ }
12
+ /** Public identifiers only. A value that looks like a secret is refused and no tool starts. */
13
+ export interface InitOptions {
14
+ sentry?: SentryOptions;
15
+ 'new-relic'?: NewRelicOptions;
16
+ }
17
+ export interface BrowserTool<O> {
18
+ /** Allow-list. Returns a fixed reason string if a value is malformed. Runs before any vendor code loads. */
19
+ check(options: O): string | null;
20
+ /** May throw; index catches and warns once. */
21
+ init(options: O): Promise<void>;
22
+ send(name: string, value: number, attrs: Attrs): void;
23
+ }
24
+ /** Starts the named tools. Call once, in the browser. Never throws, never rejects. No-op without `window`. */
25
+ export declare function init(options: InitOptions): Promise<void>;
26
+ /** Records a custom event on every started tool that can take it. No-op before init. Never throws. */
27
+ export declare function send(name: string, value: number, attrs?: Attrs): void;
@@ -0,0 +1,110 @@
1
+ // Consumer entry point, published as the "./browser" export of this package (init and send).
2
+ // This tree is sealed (tsconfig.browser.json, test/browser-entry.test.mjs): it imports nothing
3
+ // outside browser/, reads no environment and has no static import of a vendor or tool module,
4
+ // so a site that never calls init downloads no vendor code.
5
+ const LABELS = { sentry: 'Sentry', 'new-relic': 'New Relic' };
6
+ const LOADERS = {
7
+ sentry: () => import('./sentry.mjs'),
8
+ 'new-relic': () => import('./newrelic.mjs'),
9
+ };
10
+ // Secrets that must never reach a browser. Tested against every string in the options and
11
+ // against each part of it split on URL delimiters, so a token in a DSN's user part is caught.
12
+ const DENY = [
13
+ /^NR(?!JS-)[A-Z]{2}-/i, // every New Relic key prefix except the public browser key
14
+ /^sntry[a-z0-9]?_/i, // every Sentry token prefix (sntrys_ org, sntryu_ user, and any later kind)
15
+ /NRAL$/i, // New Relic ingest licence key
16
+ /^[a-f0-9]{64}$/i, // legacy Sentry auth token
17
+ ];
18
+ // Not a known secret format, but shaped like a key: 40 or more key characters in one piece (a 40-hex
19
+ // legacy New Relic licence key, for one). Only consulted once a value has already failed a tool's
20
+ // allow-list, so a valid 32-hex DSN key or NRJS- key never gets here.
21
+ const KEY_SHAPED = [/^(?!NRJS-)[a-z0-9_-]{40,}$/i];
22
+ function hasSecret(value, rules = DENY, depth = 0) {
23
+ if (typeof value === 'string')
24
+ return [value, ...value.split(/[:/@?#&=\s]+/)].some(part => rules.some(rule => rule.test(part)));
25
+ if (value !== null && typeof value === 'object' && depth < 4)
26
+ return Object.values(value).some(v => hasSecret(v, rules, depth + 1));
27
+ return false;
28
+ }
29
+ // Every warning is a fixed string. Nothing caught and no option value is ever passed to console.
30
+ const warn = (text) => {
31
+ try {
32
+ console.warn(`MetricFlow: ${text}`);
33
+ }
34
+ catch { /* a broken console must not break the site */ }
35
+ };
36
+ let called = false;
37
+ let warnedAgain = false;
38
+ const started = [];
39
+ /** Starts the named tools. Call once, in the browser. Never throws, never rejects. No-op without `window`. */
40
+ export async function init(options) {
41
+ try {
42
+ if (typeof window === 'undefined')
43
+ return;
44
+ if (called) {
45
+ if (!warnedAgain)
46
+ warn('init was already called; this call did nothing.');
47
+ warnedAgain = true;
48
+ return;
49
+ }
50
+ called = true;
51
+ if (options === null || typeof options !== 'object')
52
+ return warn('init needs an options object. No tool was started.');
53
+ // 1. Deny rules, over everything that was passed, before any other module is requested.
54
+ for (const [key, value] of Object.entries(options)) {
55
+ if (hasSecret(value)) {
56
+ const label = Object.hasOwn(LABELS, key) ? LABELS[key] : 'an unknown tool'; // never the caller's own text
57
+ return warn(`a secret key was passed to init for ${label}. Nothing was sent. This key is already in your site's public JavaScript: revoke it now and create a new one.`);
58
+ }
59
+ }
60
+ // A mistyped tool name ("newrelic") must not fail silently. The key itself is the caller's text: never printed.
61
+ if (Object.keys(options).some(key => !Object.hasOwn(LABELS, key)))
62
+ return warn('init was given an option that is not a tool. The tools are "sentry" and "new-relic". No tool was started.');
63
+ // 2. Load only the named tools' own (small) modules. Vendor code is not requested yet.
64
+ const ids = Object.keys(LABELS).filter(id => options[id] !== undefined);
65
+ if (ids.length === 0)
66
+ return warn('init was not given a tool. Name "sentry", "new-relic" or both. No tool was started.');
67
+ const loaded = [];
68
+ await Promise.all(ids.map(async (id) => {
69
+ try {
70
+ loaded.push({ id, tool: (await LOADERS[id]()).tool });
71
+ }
72
+ catch {
73
+ warn(`${LABELS[id]} could not be loaded. Your site is not affected.`);
74
+ }
75
+ }));
76
+ // 3. Allow-list. One bad value starts no tool at all.
77
+ for (const { id, tool } of loaded) {
78
+ const reason = tool.check(options[id]);
79
+ if (!reason)
80
+ continue;
81
+ if (hasSecret(options[id], KEY_SHAPED))
82
+ return warn(`${LABELS[id]} was not started: a value passed to init looks like a secret key, not a public identifier. If it is one, it is already in your site's public JavaScript: revoke it now and create a new one. No tool was started.`);
83
+ return warn(`${LABELS[id]} was not started: ${reason} No tool was started.`);
84
+ }
85
+ // 4. Start in parallel; one failing does not stop the other or the page.
86
+ await Promise.all(loaded.map(async ({ id, tool }) => {
87
+ try {
88
+ await tool.init(options[id]);
89
+ started.push(tool);
90
+ }
91
+ catch {
92
+ warn(`${LABELS[id]} could not be started. Your site is not affected.`);
93
+ }
94
+ }));
95
+ }
96
+ catch {
97
+ warn('init failed. Your site is not affected.');
98
+ }
99
+ }
100
+ /** Records a custom event on every started tool that can take it. No-op before init. Never throws. */
101
+ export function send(name, value, attrs = {}) {
102
+ if (typeof name !== 'string' || typeof value !== 'number')
103
+ return;
104
+ for (const tool of started) {
105
+ try {
106
+ tool.send(name, value, attrs);
107
+ }
108
+ catch { /* never break the caller */ }
109
+ }
110
+ }
@@ -0,0 +1,4 @@
1
+ export interface Started {
2
+ recordCustomEvent(type: string, attributes: Record<string, unknown>): unknown;
3
+ }
4
+ export declare function start(browserKey: string, applicationId: string, accountId: string, beacon: string): Started;
@@ -0,0 +1,32 @@
1
+ // The only file that names @newrelic/browser-agent. newrelic.mts loads it lazily, after its own checks.
2
+ // The agent is composed from only the features the dashboard reads, instead of the full BrowserAgent:
3
+ // session replay, session trace, logging and soft navigation are never bundled.
4
+ // Static imports on purpose: one lazy chunk group. Six separate dynamic imports made the consumer's
5
+ // bundler repeat shared code and download more than the full agent.
6
+ import { Agent } from '@newrelic/browser-agent/loaders/agent';
7
+ import { Ajax } from '@newrelic/browser-agent/features/ajax';
8
+ import { GenericEvents } from '@newrelic/browser-agent/features/generic_events'; // recordCustomEvent
9
+ import { JSErrors } from '@newrelic/browser-agent/features/jserrors';
10
+ import { PageViewEvent } from '@newrelic/browser-agent/features/page_view_event';
11
+ import { PageViewTiming } from '@newrelic/browser-agent/features/page_view_timing'; // web vitals
12
+ export function start(browserKey, applicationId, accountId, beacon) {
13
+ return new Agent({
14
+ features: [Ajax, GenericEvents, JSErrors, PageViewEvent, PageViewTiming],
15
+ init: {
16
+ // Off: it adds newrelic/traceparent headers to the site's own requests, and the dashboard reads no traces.
17
+ // AJAX events are recorded either way; only their trace and span IDs go.
18
+ distributed_tracing: { enabled: false },
19
+ session_replay: { enabled: false }, // also absent from the bundle; stated for the day the feature list changes
20
+ privacy: { cookies_enabled: false },
21
+ // GenericEvents is here for recordCustomEvent only. Its two automatic sources are on by default
22
+ // and the dashboard reads neither, so both are turned off:
23
+ user_actions: { enabled: false }, // clicks, key presses, copy/paste, focus, with the target's selector, id and class
24
+ feature_flags: ['no_spv'], // Content-Security-Policy violation events (blocked and document URLs)
25
+ // Must stay on: with every source off the agent never loads the code behind recordCustomEvent.
26
+ // On its own it collects nothing; it only enables the addPageAction call.
27
+ page_action: { enabled: true },
28
+ },
29
+ info: { beacon, errorBeacon: beacon, licenseKey: browserKey, applicationID: applicationId, sa: 1 },
30
+ loader_config: { accountID: accountId, trustKey: accountId, agentID: applicationId, licenseKey: browserKey, applicationID: applicationId },
31
+ });
32
+ }
@@ -0,0 +1,2 @@
1
+ import type { BrowserTool, NewRelicOptions } from './index.mjs';
2
+ export declare const tool: BrowserTool<NewRelicOptions>;
@@ -0,0 +1,35 @@
1
+ const BAD_KEY = 'browserKey must be the "Ingest - Browser" key, which starts with NRJS-. Copy the key value, not the ID shown beside it.';
2
+ const BAD_ID = 'applicationId and accountId must be plain numbers, passed as strings.';
3
+ const BAD_REGION = 'region must be "us" or "eu".';
4
+ const BAD_OPTION = 'only "browserKey", "applicationId", "accountId" and "region" are accepted.';
5
+ const EXISTING = 'MetricFlow: New Relic is already running on this page; MetricFlow left it as it is.';
6
+ const ALLOWED = ['browserKey', 'applicationId', 'accountId', 'region'];
7
+ // The EU host is from New Relic's documentation; it has not been tried against an EU account.
8
+ const BEACONS = { us: 'bam.nr-data.net', eu: 'bam.eu01.nr-data.net' };
9
+ function check(options) {
10
+ if (options === null || typeof options !== 'object')
11
+ return BAD_OPTION;
12
+ if (Object.keys(options).some(k => !ALLOWED.includes(k)))
13
+ return BAD_OPTION;
14
+ const { browserKey, applicationId, accountId, region } = options;
15
+ if (typeof browserKey !== 'string' || !/^NRJS-[a-z0-9]+$/i.test(browserKey))
16
+ return BAD_KEY;
17
+ if (![applicationId, accountId].every(id => typeof id === 'string' && /^\d+$/.test(id)))
18
+ return BAD_ID;
19
+ if (region !== undefined && region !== 'us' && region !== 'eu')
20
+ return BAD_REGION;
21
+ return null;
22
+ }
23
+ let agent;
24
+ async function init({ browserKey, applicationId, accountId, region }) {
25
+ // Checked before the import, so a site with its own agent or copy-paste snippet gets no second agent.
26
+ if ('NREUM' in window || 'newrelic' in window)
27
+ return console.warn(EXISTING);
28
+ const { start } = await import('./newrelic-agent.mjs');
29
+ agent = start(browserKey, applicationId, accountId, BEACONS[region ?? 'us']);
30
+ }
31
+ function send(name, value, attrs) {
32
+ // attrs first: a caller's own "name", "value" or "page" must not replace the fields the dashboard reads.
33
+ agent?.recordCustomEvent('MetricFlowEvent', { ...attrs, name, value, page: window.location.pathname });
34
+ }
35
+ export const tool = { check, init, send };
@@ -0,0 +1,2 @@
1
+ /** Starts Sentry. Returns false, and changes nothing, if a client is already running. */
2
+ export declare function start(dsn: string, tracesSampleRate: number): boolean;
@@ -0,0 +1,22 @@
1
+ // The only file that names @sentry/browser. sentry.mts loads it lazily, after its own checks.
2
+ // Static, named imports on purpose: a bundler can then drop what is not named here. With
3
+ // `await import('@sentry/browser')` the whole package, session replay and feedback included,
4
+ // lands in the consumer's chunk (measured with Next 16 / Turbopack: 439 kB against 140 kB).
5
+ import { browserTracingIntegration, getClient, init } from '@sentry/browser';
6
+ /** Starts Sentry. Returns false, and changes nothing, if a client is already running. */
7
+ export function start(dsn, tracesSampleRate) {
8
+ if (getClient())
9
+ return false; // a de-duplicated copy that started while ours loaded
10
+ init({
11
+ dsn,
12
+ sendDefaultPii: false, // explicit, not left to the SDK default
13
+ tracesSampleRate,
14
+ // SDK defaults plus tracing only. No replay, feedback or profiling integration is imported.
15
+ integrations: [browserTracingIntegration({
16
+ // Pins the page-load name to the real path so the dashboard can match it. Any ID in the path is sent (see README).
17
+ // Page loads only: a navigation span starts before pushState is applied, so the path is still the page being left.
18
+ beforeStartSpan: ctx => (ctx.op === 'pageload' ? { ...ctx, name: window.location.pathname } : ctx),
19
+ })],
20
+ });
21
+ return true;
22
+ }
@@ -0,0 +1,2 @@
1
+ import type { BrowserTool, SentryOptions } from './index.mjs';
2
+ export declare const tool: BrowserTool<SentryOptions>;
@@ -0,0 +1,45 @@
1
+ const BAD_DSN = 'the DSN is not a public Sentry DSN. Copy it from Sentry > Project settings > Client Keys (DSN).';
2
+ const LEGACY_DSN = 'the DSN is a legacy one that contains a secret key after ":". Copy the DSN from Sentry > Project settings > Client Keys (DSN), and revoke the old key.';
3
+ const HTTP_DSN = 'the DSN must use https. http is accepted only for a Sentry on localhost.';
4
+ const LOCAL_HOSTS = ['localhost', '127.0.0.1', '[::1]']; // the same list as the dashboard's own DSN rule
5
+ const BAD_RATE = 'tracesSampleRate must be a number from 0 to 1.';
6
+ const BAD_OPTION = 'only "dsn" and "tracesSampleRate" are accepted.';
7
+ const EXISTING = 'MetricFlow: Sentry is already running on this page; MetricFlow left it as it is.';
8
+ function check(options) {
9
+ if (options === null || typeof options !== 'object')
10
+ return BAD_OPTION;
11
+ if (Object.keys(options).some(k => k !== 'dsn' && k !== 'tracesSampleRate'))
12
+ return BAD_OPTION;
13
+ const { dsn, tracesSampleRate: rate } = options;
14
+ if (typeof dsn !== 'string')
15
+ return BAD_DSN;
16
+ let url;
17
+ try {
18
+ url = new URL(dsn);
19
+ }
20
+ catch {
21
+ return BAD_DSN;
22
+ } // the caught error holds the input in some browsers: never log it
23
+ if (url.password !== '')
24
+ return LEGACY_DSN;
25
+ if (url.protocol === 'http:' && !LOCAL_HOSTS.includes(url.hostname))
26
+ return HTTP_DSN;
27
+ if (!/^https?:$/.test(url.protocol) || !/^[a-f0-9]{32}$/i.test(url.username) || !/^(\/[\w-]+)*\/\d+$/.test(url.pathname) || url.search !== '' || url.hash !== '')
28
+ return BAD_DSN;
29
+ if (rate !== undefined && !(typeof rate === 'number' && rate >= 0 && rate <= 1))
30
+ return BAD_RATE; // NaN fails both comparisons
31
+ return null;
32
+ }
33
+ // The dashboard lists local pages only, where sampling 1 in 10 looks like "nothing arrives".
34
+ const defaultRate = () => (['localhost', '127.0.0.1'].includes(window.location.hostname) ? 1 : 0.1);
35
+ async function init(options) {
36
+ // Checked before the import, so a site with its own Sentry downloads no second copy.
37
+ // Known false positive: an SDK that was loaded but never initialised is skipped too.
38
+ if ('__SENTRY__' in window)
39
+ return console.warn(EXISTING);
40
+ const { start } = await import('./sentry-sdk.mjs');
41
+ if (!start(options.dsn, options.tracesSampleRate ?? defaultRate()))
42
+ console.warn(EXISTING);
43
+ }
44
+ // The dashboard reads no custom events from Sentry, so send does nothing.
45
+ export const tool = { check, init, send() { } };