@uncaughtdev/core 0.1.1 → 0.2.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 (52) hide show
  1. package/README.md +44 -3
  2. package/dist/chunk-2YXXFGBV.js +2 -0
  3. package/dist/chunk-2YXXFGBV.js.map +1 -0
  4. package/dist/chunk-3FCDO7OR.mjs +23 -0
  5. package/dist/chunk-3FCDO7OR.mjs.map +1 -0
  6. package/dist/chunk-A6GKDPT3.mjs +2 -0
  7. package/dist/chunk-A6GKDPT3.mjs.map +1 -0
  8. package/dist/chunk-BXMN7NW4.mjs +2 -0
  9. package/dist/chunk-BXMN7NW4.mjs.map +1 -0
  10. package/dist/chunk-HANXURHX.mjs +59 -0
  11. package/dist/chunk-HANXURHX.mjs.map +1 -0
  12. package/dist/chunk-MSUAXLMV.js +2 -0
  13. package/dist/chunk-MSUAXLMV.js.map +1 -0
  14. package/dist/chunk-VQXSHR3C.js +59 -0
  15. package/dist/chunk-VQXSHR3C.js.map +1 -0
  16. package/dist/chunk-WZBG5VLB.js +23 -0
  17. package/dist/chunk-WZBG5VLB.js.map +1 -0
  18. package/dist/index.d.mts +64 -3
  19. package/dist/index.d.ts +64 -3
  20. package/dist/index.js +7 -26
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +7 -26
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/local-api-handler-pages.js +1 -1
  25. package/dist/local-api-handler-pages.js.map +1 -1
  26. package/dist/local-api-handler-pages.mjs +1 -1
  27. package/dist/local-api-handler-pages.mjs.map +1 -1
  28. package/dist/local-api-handler.d.mts +1 -1
  29. package/dist/local-api-handler.d.ts +1 -1
  30. package/dist/local-api-handler.js +1 -1
  31. package/dist/local-api-handler.mjs +1 -1
  32. package/dist/local-viewer.js +432 -46
  33. package/dist/local-viewer.js.map +1 -1
  34. package/dist/local-viewer.mjs +432 -46
  35. package/dist/local-viewer.mjs.map +1 -1
  36. package/dist/mcp-server.d.mts +1 -0
  37. package/dist/mcp-server.d.ts +1 -0
  38. package/dist/mcp-server.js +22 -0
  39. package/dist/mcp-server.js.map +1 -0
  40. package/dist/mcp-server.mjs +22 -0
  41. package/dist/mcp-server.mjs.map +1 -0
  42. package/dist/sqlite-store-4FTNST7O.js +2 -0
  43. package/dist/sqlite-store-4FTNST7O.js.map +1 -0
  44. package/dist/sqlite-store-TEXDAAOM.mjs +2 -0
  45. package/dist/sqlite-store-TEXDAAOM.mjs.map +1 -0
  46. package/dist/{types-CjgYXVc_.d.mts → types-D1Fw4k-D.d.mts} +12 -1
  47. package/dist/{types-CjgYXVc_.d.ts → types-D1Fw4k-D.d.ts} +12 -1
  48. package/package.json +9 -2
  49. package/dist/chunk-FFHQ452Q.js +0 -2
  50. package/dist/chunk-FFHQ452Q.js.map +0 -1
  51. package/dist/chunk-JALIO2BZ.mjs +0 -2
  52. package/dist/chunk-JALIO2BZ.mjs.map +0 -1
@@ -1,96 +1,482 @@
1
1
  #!/usr/bin/env node
2
- import*as c from'fs/promises';import*as r from'path';import {spawn}from'child_process';var N="\x1B[0m",y="\x1B[1m",h="\x1B[2m",b="\x1B[31m",x="\x1B[32m",m="\x1B[33m",f="\x1B[36m",j="\x1B[37m",w="\x1B[90m",A="\x1B[41m",M="\x1B[42m",O="\x1B[43m";function n(e,t){return `${e}${t}${N}`}function E(){return r.resolve(process.cwd(),".uncaught")}function F(){return r.join(E(),"issues.json")}async function D(){try{let e=await c.readFile(F(),"utf-8");return JSON.parse(e)}catch{return []}}async function T(e){let t=F(),o=t+".tmp";await c.writeFile(o,JSON.stringify(e,null,2),"utf-8"),await c.rename(o,t);}async function L(){let e=await D();if(e.length===0){console.log(n(h,`
2
+ import {a}from'./chunk-HANXURHX.mjs';import'./chunk-A6GKDPT3.mjs';import*as l from'fs/promises';import*as i from'path';import*as L from'http';import {spawn}from'child_process';var O="\x1B[0m",$="\x1B[1m",h="\x1B[2m",k="\x1B[31m",x="\x1B[32m",v="\x1B[33m",b="\x1B[36m",I="\x1B[37m",w="\x1B[90m",N="\x1B[41m",z="\x1B[42m",B="\x1B[43m";function t(e,n){return `${e}${n}${O}`}function P(){return i.resolve(process.cwd(),".uncaught")}function M(){return i.join(P(),"issues.json")}function J(){return i.join(P(),"uncaught.db")}function E(){let e=a(J());return e.importFromFiles(P()),e}async function D(){try{let e=E(),n=e.getIssues();return e.close(),n}catch{try{let e=await l.readFile(M(),"utf-8");return JSON.parse(e)}catch{return []}}}async function G(e){let n=M(),o=n+".tmp";await l.writeFile(o,JSON.stringify(e,null,2),"utf-8"),await l.rename(o,n);try{let s=E();for(let a of e)s.upsertIssue(a);s.close();}catch{}}async function _(){let e=await D();if(e.length===0){console.log(t(h,`
3
3
  No issues found in .uncaught/
4
- `)),console.log(n(w,` Capture errors with initUncaught() to see them here.
5
- `));return}console.log(""),console.log(n(y," Uncaught Issues")),console.log(n(h," \u2500".repeat(35))),console.log("");let t=B("#","Status","Count","Error","Last Seen");console.log(n(h,` ${t}`)),console.log(n(h," "+"\u2500".repeat(90)));for(let o=0;o<e.length;o++){let s=e[o],i=String(o+1).padStart(3),a=V(s.status),p=String(s.count).padStart(5),u=I(s.title,45),d=I(s.errorType,15),g=Y(s.lastSeen),l=`${n(b,d)} ${n(j,u)}`;console.log(` ${n(f,i)} ${a} ${n(m,p)} ${l} ${n(w,g)}`);}console.log(""),console.log(n(h,` ${e.length} issue(s) total`)),console.log(n(w," Run: uncaught show <n> to view fix prompt, --open to open in editor")),console.log("");}async function _(e,t){let o=await D(),s=parseInt(e,10)-1;if(isNaN(s)||s<0||s>=o.length){console.error(n(b,`
6
- Invalid issue number: ${e}`)),console.error(n(w,` Valid range: 1-${o.length}
7
- `)),process.exitCode=1;return}let i=o[s],a=r.join(E(),"fix-prompts",i.fixPromptFile),p;try{p=await c.readFile(a,"utf-8");}catch{console.error(n(b,`
8
- Fix prompt file not found: ${a}
9
- `)),process.exitCode=1;return}if(t){let u=process.env.EDITOR||"code";console.log(n(h,` Opening ${a} in ${u}...`));try{spawn(u,[a],{detached:!0,stdio:"ignore"}).unref();}catch{let d=process.platform==="darwin"?"open":"xdg-open";try{spawn(d,[a],{detached:!0,stdio:"ignore"}).unref();}catch{console.error(n(b,` Could not open editor. File is at: ${a}`));}}return}console.log(""),console.log(n(y,` Fix Prompt for Issue #${s+1}`)),console.log(n(h,` Fingerprint: ${i.fingerprint}`)),console.log(n(h,` Count: ${i.count} | Users: ${i.affectedUsers.length}`)),console.log(n(h," \u2500".repeat(35))),console.log(""),console.log(p),console.log(""),console.log(n(w,` File: ${a}`)),console.log(n(w," Tip: run with --open to open in your editor")),console.log("");}async function W(){let e=E();try{await c.access(e);}catch{console.log(n(h,`
4
+ `)),console.log(t(w,` Capture errors with initUncaught() to see them here.
5
+ `));return}console.log(""),console.log(t($," Uncaught Issues")),console.log(t(h," \u2500".repeat(35))),console.log("");let n=K("#","Status","Count","Error","Last Seen");console.log(t(h,` ${n}`)),console.log(t(h," "+"\u2500".repeat(90)));for(let o=0;o<e.length;o++){let s=e[o],a=String(o+1).padStart(3),r=Q(s.status),u=String(s.count).padStart(5),d=F(s.title,45),g=F(s.errorType,15),f=X(s.lastSeen),c=`${t(k,g)} ${t(I,d)}`;console.log(` ${t(b,a)} ${r} ${t(v,u)} ${c} ${t(w,f)}`);}console.log(""),console.log(t(h,` ${e.length} issue(s) total`)),console.log(t(w," Run: uncaught show <n> to view fix prompt, --open to open in editor")),console.log(t(b," Run: npx @uncaughtdev/core dashboard \u2014 for a web UI")),console.log("");}async function V(e,n){let o=await D(),s=parseInt(e,10)-1;if(isNaN(s)||s<0||s>=o.length){console.error(t(k,`
6
+ Invalid issue number: ${e}`)),console.error(t(w,` Valid range: 1-${o.length}
7
+ `)),process.exitCode=1;return}let a=o[s],r=i.join(P(),"fix-prompts",a.fixPromptFile),u;try{u=await l.readFile(r,"utf-8");}catch{console.error(t(k,`
8
+ Fix prompt file not found: ${r}
9
+ `)),process.exitCode=1;return}if(n){let d=process.env.EDITOR||"code";console.log(t(h,` Opening ${r} in ${d}...`));try{spawn(d,[r],{detached:!0,stdio:"ignore"}).unref();}catch{let g=process.platform==="darwin"?"open":"xdg-open";try{spawn(g,[r],{detached:!0,stdio:"ignore"}).unref();}catch{console.error(t(k,` Could not open editor. File is at: ${r}`));}}return}console.log(""),console.log(t($,` Fix Prompt for Issue #${s+1}`)),console.log(t(h,` Fingerprint: ${a.fingerprint}`)),console.log(t(h,` Count: ${a.count} | Users: ${a.affectedUsers.length}`)),console.log(t(h," \u2500".repeat(35))),console.log(""),console.log(u),console.log(""),console.log(t(w,` File: ${r}`)),console.log(t(w," Tip: run with --open to open in your editor")),console.log("");}async function W(){let e=P();try{await l.access(e);}catch{console.log(t(h,`
10
10
  Nothing to clear \u2014 .uncaught/ does not exist.
11
- `));return}let t=await c.readdir(e);for(let o of t){let s=r.join(e,o);await c.rm(s,{recursive:true,force:true});}console.log(n(x,`
11
+ `));return}try{let o=E();o.deleteAllIssues(),o.close();}catch{}let n=await l.readdir(e);for(let o of n){let s=i.join(e,o);await l.rm(s,{recursive:true,force:true});}console.log(t(x,`
12
12
  Cleared all issues in .uncaught/
13
- `));}async function G(e){let t=await D(),o=parseInt(e,10)-1;if(isNaN(o)||o<0||o>=t.length){console.error(n(b,`
14
- Invalid issue number: ${e}`)),console.error(n(w,` Valid range: 1-${t.length}
15
- `)),process.exitCode=1;return}let s=t[o];if(s.status==="resolved"){console.log(n(m,`
13
+ `));}async function Y(e){let n=await D(),o=parseInt(e,10)-1;if(isNaN(o)||o<0||o>=n.length){console.error(t(k,`
14
+ Invalid issue number: ${e}`)),console.error(t(w,` Valid range: 1-${n.length}
15
+ `)),process.exitCode=1;return}let s=n[o];if(s.status==="resolved"){console.log(t(v,`
16
16
  Issue #${o+1} is already resolved.
17
- `));return}s.status="resolved",await T(t),console.log(n(x,`
18
- Issue #${o+1} marked as resolved.`)),console.log(n(h,` ${s.errorType}: ${I(s.title,60)}
19
- `));}function B(e,t,o,s,i){return `${e.padStart(3)} ${t.padEnd(10)} ${o.padStart(5)} ${s.padEnd(60)} ${i}`}function V(e){switch(e){case "open":return n(`${A}${j}${y}`," OPEN ");case "resolved":return n(`${M}${j}${y}`," DONE ");case "ignored":return n(`${O}${j}${y}`," SKIP ");default:return n(h,String(e).padEnd(6))}}function Y(e){try{let t=new Date(e).getTime(),s=Date.now()-t;if(s<0)return "just now";let i=Math.floor(s/1e3);if(i<60)return `${i}s ago`;let a=Math.floor(i/60);if(a<60)return `${a}m ago`;let p=Math.floor(a/60);if(p<24)return `${p}h ago`;let u=Math.floor(p/24);return u<30?`${u}d ago`:new Date(e).toLocaleDateString()}catch{return e}}function I(e,t){return e.length<=t?e:e.slice(0,t-3)+"..."}function R(){console.log(`
20
- ${n(y," uncaught")} \u2014 error monitoring for vibe coders
17
+ `));return}s.status="resolved",await G(n);try{let a=E();a.updateIssueStatus(s.fingerprint,"resolved"),a.close();}catch{}console.log(t(x,`
18
+ Issue #${o+1} marked as resolved.`)),console.log(t(h,` ${s.errorType}: ${F(s.title,60)}
19
+ `));}function K(e,n,o,s,a){return `${e.padStart(3)} ${n.padEnd(10)} ${o.padStart(5)} ${s.padEnd(60)} ${a}`}function Q(e){switch(e){case "open":return t(`${N}${I}${$}`," OPEN ");case "resolved":return t(`${z}${I}${$}`," DONE ");case "ignored":return t(`${B}${I}${$}`," SKIP ");default:return t(h,String(e).padEnd(6))}}function X(e){try{let n=new Date(e).getTime(),s=Date.now()-n;if(s<0)return "just now";let a=Math.floor(s/1e3);if(a<60)return `${a}s ago`;let r=Math.floor(a/60);if(r<60)return `${r}m ago`;let u=Math.floor(r/60);if(u<24)return `${u}h ago`;let d=Math.floor(u/24);return d<30?`${d}d ago`:new Date(e).toLocaleDateString()}catch{return e}}function F(e,n){return e.length<=n?e:e.slice(0,n-3)+"..."}function U(){console.log(`
20
+ ${t($," uncaught")} \u2014 error monitoring for vibe coders
21
21
 
22
- ${n(y," Setup:")}
23
- ${n(f,"npx uncaughtdev init")} Auto-detect framework, install, and patch \u2014 one command
22
+ ${t($," Setup:")}
23
+ ${t(b,"npx uncaughtdev init")} Auto-detect framework, install, and patch \u2014 one command
24
24
 
25
- ${n(y," Viewer:")}
25
+ ${t($," Viewer:")}
26
26
  npx uncaughtdev List all captured issues
27
27
  npx uncaughtdev list List all captured issues
28
28
  npx uncaughtdev show <n> Display fix prompt for issue #n
29
29
  npx uncaughtdev show <n> --open Open fix prompt in $EDITOR
30
30
  npx uncaughtdev resolve <n> Mark issue #n as resolved
31
31
  npx uncaughtdev clear Remove all captured issues
32
+ npx uncaughtdev dashboard Open web dashboard (--port 3300)
32
33
 
33
- ${n(y," Examples:")}
34
+ ${t($," Examples:")}
34
35
  npx uncaughtdev init Setup Uncaught in your project
35
36
  npx uncaughtdev show 1 Print fix prompt for issue #1
36
37
  npx uncaughtdev show 3 --open Open issue #3's prompt in editor
37
- `);}async function v(e){try{return await c.access(e),!0}catch{return false}}async function P(e){try{return (await c.stat(e)).isDirectory()}catch{return false}}function J(e,t){return new Promise(o=>{let s=spawn(e,t,{stdio:"inherit",shell:true});s.on("close",i=>o(i===0)),s.on("error",()=>o(false));})}async function K(){let e=process.cwd(),t={framework:"unknown",packageManager:"npm",hasSupabase:false,hasTypescript:false,rootDir:e};await v(r.join(e,"pnpm-lock.yaml"))?t.packageManager="pnpm":await v(r.join(e,"yarn.lock"))?t.packageManager="yarn":(await v(r.join(e,"bun.lockb"))||await v(r.join(e,"bun.lock")))&&(t.packageManager="bun");let o={};try{let i=await c.readFile(r.join(e,"package.json"),"utf-8");o=JSON.parse(i);}catch{return t}let s={...o.dependencies??{},...o.devDependencies??{}};return t.hasTypescript="typescript"in s||await v(r.join(e,"tsconfig.json")),t.hasSupabase="@supabase/supabase-js"in s,"next"in s?await P(r.join(e,"app"))||await P(r.join(e,"src","app"))?t.framework="nextjs-app":await P(r.join(e,"pages"))||await P(r.join(e,"src","pages"))?t.framework="nextjs-pages":t.framework="nextjs-app":"vite"in s&&("react"in s||"react-dom"in s)?t.framework="vite-react":"react-scripts"in s&&(t.framework="cra"),t}function H(e,t){switch(e){case "pnpm":return {cmd:"pnpm",args:["add",...t]};case "yarn":return {cmd:"yarn",args:["add",...t]};case "bun":return {cmd:"bun",args:["add",...t]};default:return {cmd:"npm",args:["install",...t]}}}function q(e){let t=-1,o=/^import\s/gm,s;for(;(s=o.exec(e))!==null;)t=s.index;return t}function z(e,t){let o=q(e);if(o>=0){let i=e.indexOf(`
38
- `,o)+1;return e.slice(0,i)+t+e.slice(i)}let s=e.indexOf(`
38
+ `);}async function y(e){try{return await l.access(e),!0}catch{return false}}async function S(e){try{return (await l.stat(e)).isDirectory()}catch{return false}}function Z(e,n){return new Promise(o=>{let s=spawn(e,n,{stdio:"inherit",shell:true});s.on("close",a=>o(a===0)),s.on("error",()=>o(false));})}async function q(){let e=process.cwd(),n={framework:"unknown",packageManager:"npm",hasSupabase:false,hasTypescript:false,rootDir:e};await y(i.join(e,"pnpm-lock.yaml"))?n.packageManager="pnpm":await y(i.join(e,"yarn.lock"))?n.packageManager="yarn":(await y(i.join(e,"bun.lockb"))||await y(i.join(e,"bun.lock")))&&(n.packageManager="bun");let o={};try{let a=await l.readFile(i.join(e,"package.json"),"utf-8");o=JSON.parse(a);}catch{return n}let s={...o.dependencies??{},...o.devDependencies??{}};return n.hasTypescript="typescript"in s||await y(i.join(e,"tsconfig.json")),n.hasSupabase="@supabase/supabase-js"in s,"next"in s?await S(i.join(e,"app"))||await S(i.join(e,"src","app"))?n.framework="nextjs-app":await S(i.join(e,"pages"))||await S(i.join(e,"src","pages"))?n.framework="nextjs-pages":n.framework="nextjs-app":"vite"in s&&("react"in s||"react-dom"in s)?n.framework="vite-react":"react-scripts"in s&&(n.framework="cra"),n}function ee(e,n){switch(e){case "pnpm":return {cmd:"pnpm",args:["add",...n]};case "yarn":return {cmd:"yarn",args:["add",...n]};case "bun":return {cmd:"bun",args:["add",...n]};default:return {cmd:"npm",args:["install",...n]}}}function te(e){let n=-1,o=/^import\s/gm,s;for(;(s=o.exec(e))!==null;)n=s.index;return n}function T(e,n){let o=te(e);if(o>=0){let a=e.indexOf(`
39
+ `,o)+1;return e.slice(0,a)+n+e.slice(a)}let s=e.indexOf(`
39
40
  `);return s>=0&&e.slice(0,s).includes("use client")?e.slice(0,s+1)+`
40
- `+t+e.slice(s+1):t+e}async function Q(){let e=process.cwd();console.log(""),console.log(n(y," \u{1F9EA} uncaught init")),console.log(n(h," \u2500".repeat(35))),console.log(""),console.log(n(f," \u25B8 Detecting framework..."));let t=await K();if(console.log(` ${n(x,{"nextjs-app":"Next.js (App Router)","nextjs-pages":"Next.js (Pages Router)","vite-react":"Vite + React",cra:"Create React App",unknown:"Unknown"}[t.framework])} \xB7 ${t.packageManager} \xB7 TS=${t.hasTypescript} \xB7 Supabase=${t.hasSupabase}`),console.log(""),t.framework==="unknown"){console.log(n(m," \u26A0 Could not detect framework. Supported: Next.js, Vite+React, CRA")),process.exitCode=1;return}console.log(n(f," \u25B8 Installing packages..."));let s=["@uncaughtdev/core","@uncaughtdev/react"];t.hasSupabase&&s.push("@uncaughtdev/supabase");let i=H(t.packageManager,s);console.log(n(w,` ${i.cmd} ${i.args.join(" ")}`)),await J(i.cmd,i.args)?(console.log(n(x," \u2713 Packages installed")),console.log("")):(console.log(n(m,`
41
- \u26A0 Install failed \u2014 packages may not be published yet.`)),console.log(n(w," Continuing with file patching. Install manually if needed.")),console.log(""));let p=t.hasTypescript?"tsx":"jsx",u=t.hasTypescript?"ts":"js",d=r.basename(e);if(t.framework==="nextjs-app"){let g=await P(r.join(e,"app"))?r.join(e,"app"):r.join(e,"src","app");console.log(n(f," \u25B8 Patching layout..."));let l=r.join(g,`layout.${p}`);await S(l,d,"layout"),console.log(n(f," \u25B8 Creating API route..."));let k=r.join(g,"api","uncaught","local");await c.mkdir(k,{recursive:true});let $=r.join(k,`route.${u}`);await v($)?console.log(n(m," \u2298 Already exists, skipping")):(await c.writeFile($,`export { POST } from '@uncaughtdev/core/local-api-handler';
42
- `),console.log(n(x,` \u2713 Created ${r.relative(e,$)}`))),console.log(n(f," \u25B8 Patching next.config...")),await U(e);}else if(t.framework==="nextjs-pages"){let g=await P(r.join(e,"pages"))?r.join(e,"pages"):r.join(e,"src","pages");console.log(n(f," \u25B8 Patching _app..."));let l=r.join(g,`_app.${p}`);await S(l,d,"pages-app"),console.log(n(f," \u25B8 Creating API route..."));let k=r.join(g,"api","uncaught");await c.mkdir(k,{recursive:true});let $=r.join(k,`local.${u}`);await v($)?console.log(n(m," \u2298 Already exists, skipping")):(await c.writeFile($,`export { default } from '@uncaughtdev/core/local-api-handler/pages';
43
- `),console.log(n(x,` \u2713 Created ${r.relative(e,$)}`))),console.log(n(f," \u25B8 Patching next.config...")),await U(e);}else if(t.framework==="vite-react"||t.framework==="cra"){let g=t.framework==="vite-react"?["src/main.tsx","src/main.jsx","main.tsx","main.jsx"]:["src/index.tsx","src/index.jsx"],l=null;for(let k of g){let $=r.join(e,k);if(await v($)){l=$;break}}l?(console.log(n(f," \u25B8 Patching entry file...")),await S(l,d,"entry")):console.log(n(m," \u26A0 Could not find entry file. Wrap your root with <UncaughtProvider> manually."));}t.hasSupabase&&(console.log(""),console.log(n(f," \u25B8 Supabase detected! Wrap your client:")),console.log(n(w," import { wrapSupabase } from '@uncaughtdev/supabase';")),console.log(n(w," const supabase = wrapSupabase(createClient(url, key));"))),console.log(""),console.log(n(x," \u2713 Done! Uncaught is now tracking errors.")),console.log(""),console.log(n(j," Start your dev server, trigger an error, then:")),console.log(n(f," npx uncaughtdev")),console.log("");}async function S(e,t,o){let s=process.cwd(),i=r.relative(s,e);if(!await v(e)){let l="";o==="layout"?l=`'use client';
41
+ `+n+e.slice(s+1):n+e}async function ne(){let e=process.cwd();console.log(""),console.log(t($," \u{1F9EA} uncaught init")),console.log(t(h," \u2500".repeat(35))),console.log(""),console.log(t(b," \u25B8 Detecting framework..."));let n=await q();if(console.log(` ${t(x,{"nextjs-app":"Next.js (App Router)","nextjs-pages":"Next.js (Pages Router)","vite-react":"Vite + React",cra:"Create React App",unknown:"Unknown"}[n.framework])} \xB7 ${n.packageManager} \xB7 TS=${n.hasTypescript} \xB7 Supabase=${n.hasSupabase}`),console.log(""),n.framework==="unknown"){console.log(t(v," \u26A0 Could not detect framework. Supported: Next.js, Vite+React, CRA")),process.exitCode=1;return}console.log(t(b," \u25B8 Installing packages..."));let s=["@uncaughtdev/core","@uncaughtdev/react"];n.hasSupabase&&s.push("@uncaughtdev/supabase");let a=ee(n.packageManager,s);console.log(t(w,` ${a.cmd} ${a.args.join(" ")}`)),await Z(a.cmd,a.args)?(console.log(t(x," \u2713 Packages installed")),console.log("")):(console.log(t(v,`
42
+ \u26A0 Install failed \u2014 packages may not be published yet.`)),console.log(t(w," Continuing with file patching. Install manually if needed.")),console.log(""));let u=n.hasTypescript?"tsx":"jsx",d=n.hasTypescript?"ts":"js",g=i.basename(e);if(n.framework==="nextjs-app"){let f=await S(i.join(e,"app"))?i.join(e,"app"):i.join(e,"src","app");console.log(t(b," \u25B8 Setting up providers..."));let c=i.join(f,`layout.${u}`);await oe(f,c,g,u),console.log(t(b," \u25B8 Creating API route..."));let m=i.join(f,"api","uncaught","local");await l.mkdir(m,{recursive:true});let p=i.join(m,`route.${d}`);await y(p)?console.log(t(v," \u2298 Already exists, skipping")):(await l.writeFile(p,`export { POST } from '@uncaughtdev/core/local-api-handler';
43
+ `),console.log(t(x,` \u2713 Created ${i.relative(e,p)}`))),console.log(t(b," \u25B8 Patching next.config...")),await A(e);}else if(n.framework==="nextjs-pages"){let f=await S(i.join(e,"pages"))?i.join(e,"pages"):i.join(e,"src","pages");console.log(t(b," \u25B8 Patching _app..."));let c=i.join(f,`_app.${u}`);await R(c,g,"pages-app"),console.log(t(b," \u25B8 Creating API route..."));let m=i.join(f,"api","uncaught");await l.mkdir(m,{recursive:true});let p=i.join(m,`local.${d}`);await y(p)?console.log(t(v," \u2298 Already exists, skipping")):(await l.writeFile(p,`export { default } from '@uncaughtdev/core/local-api-handler/pages';
44
+ `),console.log(t(x,` \u2713 Created ${i.relative(e,p)}`))),console.log(t(b," \u25B8 Patching next.config...")),await A(e);}else if(n.framework==="vite-react"||n.framework==="cra"){let f=n.framework==="vite-react"?["src/main.tsx","src/main.jsx","main.tsx","main.jsx"]:["src/index.tsx","src/index.jsx"],c=null;for(let m of f){let p=i.join(e,m);if(await y(p)){c=p;break}}c?(console.log(t(b," \u25B8 Patching entry file...")),await R(c,g,"entry")):console.log(t(v," \u26A0 Could not find entry file. Wrap your root with <UncaughtProvider> manually."));}n.hasSupabase&&(console.log(""),console.log(t(b," \u25B8 Supabase detected! Wrap your client:")),console.log(t(w," import { wrapSupabase } from '@uncaughtdev/supabase';")),console.log(t(w," const supabase = wrapSupabase(createClient(url, key));"))),console.log(""),console.log(t(x," \u2713 Done! Uncaught is now tracking errors.")),console.log(""),console.log(t(I," Start your dev server, trigger an error, then:")),console.log(t(b," npx uncaughtdev")),console.log("");}async function oe(e,n,o,s){let a=process.cwd(),r=i.join(e,`providers.${s}`),u=i.relative(a,r),d=i.relative(a,n);if(await y(r)){let m=await l.readFile(r,"utf-8");if(m.includes("UncaughtProvider")||m.includes("@uncaughtdev/react"))console.log(t(v,` \u2298 ${u} already has UncaughtProvider, skipping`));else {let p=m;!p.includes("'use client'")&&!p.includes('"use client"')&&(p=`'use client';
45
+
46
+ ${p}`),p=T(p,`import { UncaughtProvider } from '@uncaughtdev/react';
47
+ `),await l.writeFile(r,p),console.log(t(v,` \u26A0 ${u} exists \u2014 added import but you may need to manually wrap with <UncaughtProvider>.`));}}else {let m=`'use client';
44
48
 
45
49
  import { UncaughtProvider } from '@uncaughtdev/react';
46
50
 
51
+ export function Providers({ children }: { children: React.ReactNode }) {
52
+ return (
53
+ <UncaughtProvider projectKey="${o}" transport="local">
54
+ {children}
55
+ </UncaughtProvider>
56
+ );
57
+ }
58
+ `;await l.writeFile(r,m),console.log(t(x,` \u2713 Created ${u}`));}if(!await y(n)){await l.writeFile(n,`import { Providers } from './providers';
59
+
47
60
  export default function RootLayout({ children }: { children: React.ReactNode }) {
48
61
  return (
49
62
  <html lang="en">
50
63
  <body>
51
- <UncaughtProvider projectKey="${t}" transport="local">
52
- {children}
53
- </UncaughtProvider>
64
+ <Providers>{children}</Providers>
54
65
  </body>
55
66
  </html>
56
67
  );
57
68
  }
58
- `:o==="pages-app"&&(l=`import { UncaughtProvider } from '@uncaughtdev/react';
69
+ `),console.log(t(x,` \u2713 Created ${d}`));return}let g=await l.readFile(n,"utf-8");if(g.includes("UncaughtProvider")||g.includes("@uncaughtdev/react")||g.includes("./providers")){console.log(t(v,` \u2298 ${d} already has providers, skipping`));return}g=T(g,`import { Providers } from './providers';
70
+ `);let f=false,c=g.match(/(\n(\s*))\{children\}/);c&&(g=g.replace(`${c[1]}{children}`,`${c[1]}<Providers>{children}</Providers>`),f=true),f?(await l.writeFile(n,g),console.log(t(x,` \u2713 Patched ${d}`))):console.log(t(v,` \u26A0 Could not auto-patch ${d}. Import Providers from './providers' and wrap {children}.`));}async function R(e,n,o){let s=process.cwd(),a=i.relative(s,e);if(!await y(e)){let c="";o==="pages-app"&&(c=`import { UncaughtProvider } from '@uncaughtdev/react';
59
71
  import type { AppProps } from 'next/app';
60
72
 
61
73
  export default function App({ Component, pageProps }: AppProps) {
62
74
  return (
63
- <UncaughtProvider projectKey="${t}" transport="local">
75
+ <UncaughtProvider projectKey="${n}" transport="local">
64
76
  <Component {...pageProps} />
65
77
  </UncaughtProvider>
66
78
  );
67
79
  }
68
- `),await c.writeFile(e,l),console.log(n(x,` \u2713 Created ${i}`));return}let a=await c.readFile(e,"utf-8");if(a.includes("UncaughtProvider")||a.includes("@uncaughtdev/react")){console.log(n(m,` \u2298 ${i} already has UncaughtProvider, skipping`));return}o==="layout"&&!a.includes("'use client'")&&!a.includes('"use client"')&&(a=`'use client';
69
-
70
- ${a}`),a=z(a,`import { UncaughtProvider } from '@uncaughtdev/react';
71
- `);let u=`<UncaughtProvider projectKey="${t}" transport="${o==="entry"?"console":"local"}">`,d="</UncaughtProvider>",g=false;if(o==="layout"&&a.includes("{children}")){let l=a.match(/(\n(\s*))\{children\}/);if(l){l[2];a=a.replace(`${l[1]}{children}`,`${l[1]}${u}${l[1]} {children}${l[1]}${d}`);}else a=a.replace("{children}",`
72
- ${u}
73
- {children}
74
- ${d}
75
- `);g=true;}else if(o==="pages-app"){let l=a.match(/<Component\s[^>]*\/>/);l&&(a=a.replace(l[0],`${u}
76
- ${l[0]}
77
- ${d}`),g=true);}else if(o==="entry"){let l=a.match(/<App\s*\/>/);l&&(a=a.replace(l[0],`${u}
80
+ `),await l.writeFile(e,c),console.log(t(x,` \u2713 Created ${a}`));return}let r=await l.readFile(e,"utf-8");if(r.includes("UncaughtProvider")||r.includes("@uncaughtdev/react")){console.log(t(v,` \u2298 ${a} already has UncaughtProvider, skipping`));return}r=T(r,`import { UncaughtProvider } from '@uncaughtdev/react';
81
+ `);let d=`<UncaughtProvider projectKey="${n}" transport="${o==="entry"?"console":"local"}">`,g="</UncaughtProvider>",f=false;if(o==="pages-app"){let c=r.match(/<Component\s[^>]*\/>/);c&&(r=r.replace(c[0],`${d}
82
+ ${c[0]}
83
+ ${g}`),f=true);}else if(o==="entry"){let c=r.match(/<App\s*\/>/);c&&(r=r.replace(c[0],`${d}
78
84
  <App />
79
- ${d}`),g=true);}g?(await c.writeFile(e,a),console.log(n(x,` \u2713 Patched ${i}`))):(console.log(n(m,` \u26A0 Could not auto-patch ${i}. Wrap your root component with:`)),console.log(n(w,` <UncaughtProvider projectKey="${t}" transport="local">{children}</UncaughtProvider>`)));}async function U(e){let t=["next.config.ts","next.config.mjs","next.config.js"],o=null;for(let p of t){let u=r.join(e,p);if(await v(u)){o=u;break}}let s=`
85
+ ${g}`),f=true);}f?(await l.writeFile(e,r),console.log(t(x,` \u2713 Patched ${a}`))):(console.log(t(v,` \u26A0 Could not auto-patch ${a}. Wrap your root component with:`)),console.log(t(w,` <UncaughtProvider projectKey="${n}" transport="local">{children}</UncaughtProvider>`)));}async function A(e){let n=["next.config.ts","next.config.mjs","next.config.js"],o=null;for(let u of n){let d=i.join(e,u);if(await y(d)){o=d;break}}let s=`
80
86
  webpack: (config, { isServer }) => {
81
87
  if (!isServer) {
82
88
  config.resolve.fallback = { ...config.resolve.fallback, fs: false, path: false, child_process: false };
83
89
  }
84
90
  return config;
85
- },`;if(!o){o=r.join(e,"next.config.js"),await c.writeFile(o,`/** @type {import('next').NextConfig} */
91
+ },`;if(!o){o=i.join(e,"next.config.js"),await l.writeFile(o,`/** @type {import('next').NextConfig} */
86
92
  const nextConfig = {${s}
87
93
  };
88
94
  module.exports = nextConfig;
89
- `),console.log(n(x," \u2713 Created next.config.js"));return}let i=await c.readFile(o,"utf-8");if(i.includes("fs: false")){console.log(n(m," \u2298 Already has webpack fallback, skipping"));return}if(i.includes("webpack")){console.log(n(m," \u2298 Has custom webpack \u2014 add manually: config.resolve.fallback = { fs: false, path: false, child_process: false }"));return}let a=i.match(/(const\s+\w+\s*=\s*\{|module\.exports\s*=\s*\{|export\s+default\s*\{)/);if(a&&a.index!==void 0){let p=a.index+a[0].length;i=i.slice(0,p)+s+i.slice(p),await c.writeFile(o,i),console.log(n(x,` \u2713 Patched ${r.relative(e,o)}`));}else console.log(n(m," \u26A0 Could not auto-patch. Add webpack fallback for fs/path/child_process manually."));}async function X(){let e=process.argv.slice(2),t=e[0]??"list";switch(t){case "init":case "setup":await Q();break;case "list":case "ls":await L();break;case "show":case "view":{let o=e[1];if(!o){console.error(n(b,`
95
+ `),console.log(t(x," \u2713 Created next.config.js"));return}let a=await l.readFile(o,"utf-8");if(a.includes("fs: false")){console.log(t(v," \u2298 Already has webpack fallback, skipping"));return}if(a.includes("webpack")){console.log(t(v," \u2298 Has custom webpack \u2014 add manually: config.resolve.fallback = { fs: false, path: false, child_process: false }"));return}let r=a.match(/(const\s+\w+\s*=\s*\{|module\.exports\s*=\s*\{|export\s+default\s*\{)/);if(r&&r.index!==void 0){let u=r.index+r[0].length;a=a.slice(0,u)+s+a.slice(u),await l.writeFile(o,a),console.log(t(x,` \u2713 Patched ${i.relative(e,o)}`));}else console.log(t(v," \u26A0 Could not auto-patch. Add webpack fallback for fs/path/child_process manually."));}var se=`<!DOCTYPE html>
96
+ <html lang="en">
97
+ <head>
98
+ <meta charset="utf-8">
99
+ <meta name="viewport" content="width=device-width, initial-scale=1">
100
+ <title>Uncaught Dashboard</title>
101
+ <style>
102
+ * { margin: 0; padding: 0; box-sizing: border-box; }
103
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: #0a0a0a; color: #e0e0e0; min-height: 100vh; }
104
+ a { color: #60a5fa; text-decoration: none; }
105
+ code { background: #1e1e2e; padding: 2px 6px; border-radius: 4px; font-size: 0.85em; }
106
+ pre { background: #1e1e2e; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 0.85em; line-height: 1.5; }
107
+ header { background: #111; border-bottom: 1px solid #222; padding: 16px 24px; display: flex; align-items: center; gap: 16px; }
108
+ header h1 { font-size: 1.2rem; font-weight: 600; }
109
+ header h1 span { color: #f87171; }
110
+ .stats { display: flex; gap: 24px; margin-left: auto; font-size: 0.85rem; color: #888; }
111
+ .stats .stat-val { color: #e0e0e0; font-weight: 600; }
112
+ .filters { display: flex; gap: 8px; padding: 16px 24px; border-bottom: 1px solid #1a1a1a; }
113
+ .filter-btn { background: #1a1a1a; border: 1px solid #333; color: #aaa; padding: 6px 16px; border-radius: 6px; cursor: pointer; font-size: 0.85rem; transition: all 0.15s; }
114
+ .filter-btn:hover { border-color: #555; color: #e0e0e0; }
115
+ .filter-btn.active { background: #1e3a5f; border-color: #60a5fa; color: #60a5fa; }
116
+ .container { max-width: 1200px; margin: 0 auto; padding: 0 24px; }
117
+ table { width: 100%; border-collapse: collapse; margin-top: 8px; }
118
+ th { text-align: left; padding: 12px 16px; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: #666; border-bottom: 1px solid #222; }
119
+ td { padding: 12px 16px; border-bottom: 1px solid #1a1a1a; font-size: 0.9rem; vertical-align: top; }
120
+ tr { cursor: pointer; transition: background 0.1s; }
121
+ tr:hover { background: #141414; }
122
+ tr.selected { background: #1a1a2e; }
123
+ .badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; }
124
+ .badge-open { background: #3b1111; color: #f87171; }
125
+ .badge-resolved { background: #0f2f1a; color: #4ade80; }
126
+ .badge-ignored { background: #2d2305; color: #facc15; }
127
+ .badge-release { background: #1e1e3e; color: #a78bfa; font-size: 0.7rem; margin-left: 6px; }
128
+ .badge-env { background: #1a2e1a; color: #86efac; font-size: 0.7rem; margin-left: 4px; }
129
+ .env-filter { background: #1a1a1a; border: 1px solid #333; color: #aaa; padding: 6px 12px; border-radius: 6px; font-size: 0.85rem; margin-left: 8px; }
130
+ .feedback-box { background: #1a1a2e; border: 1px solid #2e2e5c; border-radius: 8px; padding: 12px 16px; margin-top: 8px; font-style: italic; color: #c4b5fd; }
131
+ .count { background: #1e1e2e; padding: 2px 8px; border-radius: 10px; font-size: 0.8rem; font-weight: 600; }
132
+ .error-type { color: #f87171; font-weight: 500; font-size: 0.8rem; }
133
+ .error-title { color: #e0e0e0; }
134
+ .time-ago { color: #666; font-size: 0.8rem; }
135
+ .empty { text-align: center; padding: 80px 24px; color: #555; }
136
+ .empty h2 { font-size: 1.2rem; margin-bottom: 8px; color: #666; }
137
+
138
+ /* Detail panel */
139
+ .detail-overlay { display: none; position: fixed; top: 0; right: 0; bottom: 0; width: 55%; background: #111; border-left: 1px solid #222; overflow-y: auto; z-index: 100; box-shadow: -4px 0 24px rgba(0,0,0,0.5); }
140
+ .detail-overlay.open { display: block; }
141
+ .detail-header { padding: 20px 24px; border-bottom: 1px solid #222; display: flex; align-items: center; gap: 12px; }
142
+ .detail-header h2 { font-size: 1rem; flex: 1; }
143
+ .close-btn { background: none; border: 1px solid #333; color: #aaa; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 0.85rem; }
144
+ .close-btn:hover { border-color: #666; color: #fff; }
145
+ .detail-body { padding: 24px; }
146
+ .detail-section { margin-bottom: 24px; }
147
+ .detail-section h3 { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: #666; margin-bottom: 8px; }
148
+ .detail-meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
149
+ .meta-item { background: #1a1a1a; padding: 12px; border-radius: 6px; }
150
+ .meta-label { font-size: 0.7rem; text-transform: uppercase; color: #666; margin-bottom: 4px; }
151
+ .meta-value { font-size: 0.9rem; }
152
+ .breadcrumbs { list-style: none; }
153
+ .breadcrumbs li { padding: 6px 0; border-bottom: 1px solid #1a1a1a; font-size: 0.85rem; display: flex; gap: 8px; }
154
+ .bc-time { color: #666; font-family: monospace; font-size: 0.8rem; min-width: 60px; }
155
+ .bc-type { color: #60a5fa; font-size: 0.75rem; font-weight: 600; min-width: 70px; }
156
+ .action-bar { display: flex; gap: 8px; padding: 16px 24px; border-top: 1px solid #222; position: sticky; bottom: 0; background: #111; }
157
+ .action-btn { padding: 8px 16px; border-radius: 6px; border: 1px solid #333; cursor: pointer; font-size: 0.85rem; transition: all 0.15s; }
158
+ .btn-resolve { background: #0f2f1a; color: #4ade80; border-color: #1a5c2e; }
159
+ .btn-resolve:hover { background: #1a5c2e; }
160
+ .btn-ignore { background: #2d2305; color: #facc15; border-color: #5c4a0a; }
161
+ .btn-ignore:hover { background: #5c4a0a; }
162
+ .btn-copy { background: #1a1a2e; color: #818cf8; border-color: #2e2e5c; }
163
+ .btn-copy:hover { background: #2e2e5c; }
164
+ .btn-open { background: #1e3a5f; color: #60a5fa; border-color: #2563eb; }
165
+ .btn-open:hover { background: #2563eb; }
166
+
167
+ /* Fix prompt box */
168
+ .prompt-box { position: relative; background: #0d0d0d; border: 1px solid #222; border-radius: 8px; margin-bottom: 24px; }
169
+ .prompt-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; border-bottom: 1px solid #222; }
170
+ .prompt-header h3 { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: #666; margin: 0; }
171
+ .prompt-copy-btn { background: #1a1a2e; border: 1px solid #2e2e5c; color: #818cf8; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 0.75rem; display: flex; align-items: center; gap: 4px; transition: all 0.15s; }
172
+ .prompt-copy-btn:hover { background: #2e2e5c; }
173
+ .prompt-copy-btn svg { width: 14px; height: 14px; }
174
+ .prompt-content { padding: 16px; max-height: 300px; overflow-y: auto; font-size: 0.85rem; line-height: 1.5; white-space: pre-wrap; font-family: 'SF Mono', 'Fira Code', monospace; color: #ccc; }
175
+
176
+ /* Fix prompt markdown (expanded view) */
177
+ .fix-prompt { background: #0d0d0d; border: 1px solid #222; border-radius: 8px; padding: 20px; line-height: 1.6; }
178
+ .fix-prompt h1, .fix-prompt h2, .fix-prompt h3 { color: #e0e0e0; margin: 16px 0 8px; }
179
+ .fix-prompt h1 { font-size: 1.2rem; }
180
+ .fix-prompt h2 { font-size: 1rem; border-bottom: 1px solid #222; padding-bottom: 6px; }
181
+ .fix-prompt h3 { font-size: 0.9rem; }
182
+ .fix-prompt ul, .fix-prompt ol { padding-left: 20px; margin: 8px 0; }
183
+ .fix-prompt li { margin: 4px 0; }
184
+ .fix-prompt strong { color: #f0f0f0; }
185
+ .fix-prompt p { margin: 8px 0; }
186
+ </style>
187
+ </head>
188
+ <body>
189
+ <header>
190
+ <h1><span>uncaught</span> dashboard</h1>
191
+ <div class="stats" id="stats"></div>
192
+ </header>
193
+ <div class="filters" id="filters">
194
+ <button class="filter-btn active" data-filter="all">All</button>
195
+ <button class="filter-btn" data-filter="open">Open</button>
196
+ <button class="filter-btn" data-filter="resolved">Resolved</button>
197
+ <button class="filter-btn" data-filter="ignored">Ignored</button>
198
+ <select class="env-filter" id="env-filter">
199
+ <option value="">All Environments</option>
200
+ </select>
201
+ </div>
202
+ <div class="container">
203
+ <table>
204
+ <thead>
205
+ <tr><th>#</th><th>Status</th><th>Error</th><th>Release</th><th>Count</th><th>Users</th><th>Last Seen</th></tr>
206
+ </thead>
207
+ <tbody id="issues-body"></tbody>
208
+ </table>
209
+ <div class="empty" id="empty" style="display:none;">
210
+ <h2>No issues found</h2>
211
+ <p>Trigger some errors in your app and they will appear here.</p>
212
+ </div>
213
+ </div>
214
+ <div class="detail-overlay" id="detail">
215
+ <div class="detail-header">
216
+ <h2 id="detail-title"></h2>
217
+ <button class="close-btn" id="close-detail">Close</button>
218
+ </div>
219
+ <div class="detail-body" id="detail-body"></div>
220
+ <div class="action-bar" id="action-bar"></div>
221
+ </div>
222
+ <script>
223
+ let allIssues = [];
224
+ let currentFilter = 'all';
225
+ let currentEnv = '';
226
+ let selectedFp = null;
227
+
228
+ async function fetchIssues() {
229
+ try {
230
+ var params = [];
231
+ if (currentFilter !== 'all') params.push('status=' + currentFilter);
232
+ if (currentEnv) params.push('environment=' + encodeURIComponent(currentEnv));
233
+ var url = '/api/issues' + (params.length ? '?' + params.join('&') : '');
234
+ const res = await fetch(url);
235
+ allIssues = await res.json();
236
+ renderIssues();
237
+ fetchStats();
238
+ updateEnvFilter();
239
+ } catch(e) { console.error('Failed to fetch issues', e); }
240
+ }
241
+
242
+ function updateEnvFilter() {
243
+ var sel = document.getElementById('env-filter');
244
+ var envs = new Set();
245
+ allIssues.forEach(function(i) { if (i.environment) envs.add(i.environment); });
246
+ var opts = '<option value="">All Environments</option>';
247
+ envs.forEach(function(e) { opts += '<option value="' + escHtml(e) + '"' + (e === currentEnv ? ' selected' : '') + '>' + escHtml(e) + '</option>'; });
248
+ sel.innerHTML = opts;
249
+ }
250
+
251
+ async function fetchStats() {
252
+ try {
253
+ const res = await fetch('/api/stats');
254
+ const s = await res.json();
255
+ document.getElementById('stats').innerHTML =
256
+ '<div><span class="stat-val">' + s.total + '</span> issues</div>' +
257
+ '<div><span class="stat-val">' + s.open + '</span> open</div>' +
258
+ '<div><span class="stat-val">' + s.totalEvents + '</span> events</div>';
259
+ } catch(e) {}
260
+ }
261
+
262
+ function timeAgo(iso) {
263
+ const ms = Date.now() - new Date(iso).getTime();
264
+ if (ms < 0) return 'just now';
265
+ const s = Math.floor(ms/1000);
266
+ if (s < 60) return s + 's ago';
267
+ const m = Math.floor(s/60);
268
+ if (m < 60) return m + 'm ago';
269
+ const h = Math.floor(m/60);
270
+ if (h < 24) return h + 'h ago';
271
+ const d = Math.floor(h/24);
272
+ if (d < 30) return d + 'd ago';
273
+ return new Date(iso).toLocaleDateString();
274
+ }
275
+
276
+ function escHtml(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
277
+
278
+ function renderIssues() {
279
+ const body = document.getElementById('issues-body');
280
+ const empty = document.getElementById('empty');
281
+ if (allIssues.length === 0) { body.innerHTML = ''; empty.style.display = 'block'; return; }
282
+ empty.style.display = 'none';
283
+ body.innerHTML = allIssues.map(function(issue, i) {
284
+ const cls = issue.status === 'open' ? 'badge-open' : issue.status === 'resolved' ? 'badge-resolved' : 'badge-ignored';
285
+ var relBadge = issue.release ? '<span class="badge badge-release">' + escHtml(issue.release) + '</span>' : '<span style="color:#555">\u2014</span>';
286
+ var envBadge = issue.environment ? '<span class="badge badge-env">' + escHtml(issue.environment) + '</span>' : '';
287
+ return '<tr data-fp="' + issue.fingerprint + '" class="' + (issue.fingerprint === selectedFp ? 'selected' : '') + '">' +
288
+ '<td>' + (i+1) + '</td>' +
289
+ '<td><span class="badge ' + cls + '">' + issue.status + '</span>' + envBadge + '</td>' +
290
+ '<td><span class="error-type">' + escHtml(issue.errorType) + '</span> <span class="error-title">' + escHtml(issue.title.length > 60 ? issue.title.slice(0,57) + '...' : issue.title) + '</span></td>' +
291
+ '<td>' + relBadge + '</td>' +
292
+ '<td><span class="count">' + issue.count + '</span></td>' +
293
+ '<td>' + issue.affectedUsers.length + '</td>' +
294
+ '<td class="time-ago">' + timeAgo(issue.lastSeen) + '</td>' +
295
+ '</tr>';
296
+ }).join('');
297
+ }
298
+
299
+ function renderMd(md) {
300
+ return md
301
+ .replace(/^### (.+)$/gm, '<h3>$1</h3>')
302
+ .replace(/^## (.+)$/gm, '<h2>$1</h2>')
303
+ .replace(/^# (.+)$/gm, '<h1>$1</h1>')
304
+ .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')
305
+ .replace(/\`\`\`[\\s\\S]*?\`\`\`/g, function(m) {
306
+ var code = m.replace(/\`\`\`\\w*\\n?/g, '').replace(/\`\`\`$/g, '');
307
+ return '<pre><code>' + escHtml(code) + '</code></pre>';
308
+ })
309
+ .replace(/\`([^\`]+)\`/g, '<code>$1</code>')
310
+ .replace(/^- \\*\\*(.+?)\\*\\* ?\u2014 ?(.+)$/gm, '<li><strong>$1</strong> \u2014 $2</li>')
311
+ .replace(/^- (.+)$/gm, '<li>$1</li>')
312
+ .replace(/^(\\d+)\\. (.+)$/gm, '<li>$2</li>')
313
+ .replace(/(<li>.*<\\/li>)/s, '<ul>$1</ul>')
314
+ .replace(/\\n\\n/g, '</p><p>')
315
+ .replace(/\\n/g, '<br>');
316
+ }
317
+
318
+ async function showDetail(fp) {
319
+ selectedFp = fp;
320
+ renderIssues();
321
+ try {
322
+ const res = await fetch('/api/issues/' + fp);
323
+ const data = await res.json();
324
+ const issue = data.issue;
325
+ const event = data.event;
326
+
327
+ document.getElementById('detail-title').textContent = issue.errorType + ': ' + issue.title;
328
+
329
+ let html = '';
330
+
331
+ if (event) {
332
+ // Fix prompt \u2014 first, compact box with copy icon
333
+ if (event.fixPrompt) {
334
+ html += '<div class="prompt-box">';
335
+ html += '<div class="prompt-header"><h3>Fix Prompt</h3>';
336
+ html += '<button class="prompt-copy-btn" onclick="copyPrompt()">';
337
+ html += '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
338
+ html += '<span id="copy-label">Copy</span></button></div>';
339
+ html += '<div class="prompt-content">' + escHtml(event.fixPrompt) + '</div>';
340
+ html += '</div>';
341
+ }
342
+ }
343
+
344
+ // Meta
345
+ html += '<div class="detail-section"><h3>Details</h3><div class="detail-meta">';
346
+ html += '<div class="meta-item"><div class="meta-label">Fingerprint</div><div class="meta-value"><code>' + issue.fingerprint + '</code></div></div>';
347
+ html += '<div class="meta-item"><div class="meta-label">Occurrences</div><div class="meta-value">' + issue.count + '</div></div>';
348
+ html += '<div class="meta-item"><div class="meta-label">Users Affected</div><div class="meta-value">' + issue.affectedUsers.length + '</div></div>';
349
+ html += '<div class="meta-item"><div class="meta-label">First Seen</div><div class="meta-value">' + timeAgo(issue.firstSeen) + '</div></div>';
350
+ html += '<div class="meta-item"><div class="meta-label">Last Seen</div><div class="meta-value">' + timeAgo(issue.lastSeen) + '</div></div>';
351
+ html += '<div class="meta-item"><div class="meta-label">Status</div><div class="meta-value"><span class="badge badge-' + issue.status + '">' + issue.status + '</span></div></div>';
352
+ if (issue.release) html += '<div class="meta-item"><div class="meta-label">Release</div><div class="meta-value"><span class="badge badge-release">' + escHtml(issue.release) + '</span></div></div>';
353
+ if (issue.environment) html += '<div class="meta-item"><div class="meta-label">Environment</div><div class="meta-value"><span class="badge badge-env">' + escHtml(issue.environment) + '</span></div></div>';
354
+ html += '</div></div>';
355
+
356
+ if (event) {
357
+ // User feedback
358
+ if (event.userFeedback) {
359
+ html += '<div class="detail-section"><h3>User Feedback</h3><div class="feedback-box">"' + escHtml(event.userFeedback) + '"</div></div>';
360
+ }
361
+
362
+ // Stack trace (prefer resolved)
363
+ var stackLabel = 'Stack Trace';
364
+ var stackContent = '';
365
+ if (event.error && event.error.resolvedStack) {
366
+ stackLabel = 'Stack Trace (Source Mapped)';
367
+ stackContent = event.error.resolvedStack;
368
+ } else if (event.error && event.error.stack) {
369
+ stackContent = event.error.stack;
370
+ }
371
+ if (stackContent) {
372
+ html += '<div class="detail-section"><h3>' + stackLabel + '</h3><pre><code>' + escHtml(stackContent) + '</code></pre></div>';
373
+ }
374
+
375
+ // Environment
376
+ if (event.environment) {
377
+ const env = event.environment;
378
+ html += '<div class="detail-section"><h3>Environment</h3><div class="detail-meta">';
379
+ if (env.deploy) html += '<div class="meta-item"><div class="meta-label">Deploy Env</div><div class="meta-value">' + escHtml(env.deploy) + '</div></div>';
380
+ if (env.browser) html += '<div class="meta-item"><div class="meta-label">Browser</div><div class="meta-value">' + escHtml(env.browser + (env.browserVersion ? ' ' + env.browserVersion : '')) + '</div></div>';
381
+ if (env.os) html += '<div class="meta-item"><div class="meta-label">OS</div><div class="meta-value">' + escHtml(env.os) + '</div></div>';
382
+ if (env.runtime) html += '<div class="meta-item"><div class="meta-label">Runtime</div><div class="meta-value">' + escHtml(env.runtime) + '</div></div>';
383
+ if (env.url) html += '<div class="meta-item"><div class="meta-label">URL</div><div class="meta-value">' + escHtml(env.url) + '</div></div>';
384
+ if (env.deviceType) html += '<div class="meta-item"><div class="meta-label">Device</div><div class="meta-value">' + escHtml(env.deviceType) + '</div></div>';
385
+ html += '</div></div>';
386
+ }
387
+
388
+ // Breadcrumbs
389
+ if (event.breadcrumbs && event.breadcrumbs.length > 0) {
390
+ html += '<div class="detail-section"><h3>Breadcrumbs</h3><ul class="breadcrumbs">';
391
+ event.breadcrumbs.forEach(function(bc) {
392
+ var t = bc.timestamp ? new Date(bc.timestamp).toLocaleTimeString() : '';
393
+ html += '<li><span class="bc-time">' + t + '</span><span class="bc-type">[' + escHtml(bc.type) + ']</span><span>' + escHtml(bc.message) + '</span></li>';
394
+ });
395
+ html += '</ul></div>';
396
+ }
397
+ }
398
+
399
+ document.getElementById('detail-body').innerHTML = html;
400
+
401
+ // Action bar
402
+ let actions = '';
403
+ if (issue.status !== 'resolved') actions += '<button class="action-btn btn-resolve" onclick="updateStatus(\\'' + fp + '\\', \\'resolved\\')">Mark Resolved</button>';
404
+ if (issue.status !== 'ignored') actions += '<button class="action-btn btn-ignore" onclick="updateStatus(\\'' + fp + '\\', \\'ignored\\')">Mark Ignored</button>';
405
+ if (issue.status !== 'open') actions += '<button class="action-btn btn-open" onclick="updateStatus(\\'' + fp + '\\', \\'open\\')">Reopen</button>';
406
+ document.getElementById('action-bar').innerHTML = actions;
407
+
408
+ document.getElementById('detail').classList.add('open');
409
+ window._currentFixPrompt = event ? event.fixPrompt : '';
410
+ } catch(e) { console.error('Failed to load detail', e); }
411
+ }
412
+
413
+ async function updateStatus(fp, status) {
414
+ try {
415
+ await fetch('/api/issues/' + fp, { method: 'PATCH', headers: {'Content-Type':'application/json'}, body: JSON.stringify({status:status}) });
416
+ fetchIssues();
417
+ showDetail(fp);
418
+ } catch(e) { console.error('Failed to update', e); }
419
+ }
420
+
421
+ function copyPrompt() {
422
+ if (window._currentFixPrompt) {
423
+ navigator.clipboard.writeText(window._currentFixPrompt).then(function() {
424
+ var label = document.getElementById('copy-label');
425
+ if (label) { label.textContent = 'Copied!'; setTimeout(function(){ label.textContent = 'Copy'; }, 2000); }
426
+ });
427
+ }
428
+ }
429
+
430
+ // Event delegation
431
+ document.getElementById('issues-body').addEventListener('click', function(e) {
432
+ var tr = e.target.closest('tr');
433
+ if (tr && tr.dataset.fp) showDetail(tr.dataset.fp);
434
+ });
435
+
436
+ document.getElementById('close-detail').addEventListener('click', function() {
437
+ document.getElementById('detail').classList.remove('open');
438
+ selectedFp = null;
439
+ renderIssues();
440
+ });
441
+
442
+ document.getElementById('filters').addEventListener('click', function(e) {
443
+ var btn = e.target.closest('.filter-btn');
444
+ if (!btn) return;
445
+ document.querySelectorAll('.filter-btn').forEach(function(b){ b.classList.remove('active'); });
446
+ btn.classList.add('active');
447
+ currentFilter = btn.dataset.filter;
448
+ fetchIssues();
449
+ });
450
+
451
+ document.getElementById('env-filter').addEventListener('change', function(e) {
452
+ currentEnv = e.target.value;
453
+ fetchIssues();
454
+ });
455
+
456
+ // Close on Escape
457
+ document.addEventListener('keydown', function(e) {
458
+ if (e.key === 'Escape') {
459
+ document.getElementById('detail').classList.remove('open');
460
+ selectedFp = null;
461
+ renderIssues();
462
+ }
463
+ });
464
+
465
+ // Initial load + auto-refresh
466
+ fetchIssues();
467
+ setInterval(fetchIssues, 5000);
468
+ </script>
469
+ </body>
470
+ </html>`;async function ae(e){let n=P();await l.mkdir(n,{recursive:true});let o;try{o=E();}catch(a){console.error(t(k,`
471
+ Failed to open database:`),a),process.exitCode=1;return}let s=L.createServer(async(a,r)=>{let u=new URL(a.url,`http://localhost:${e}`),d=u.pathname;if(r.setHeader("Access-Control-Allow-Origin","*"),r.setHeader("Access-Control-Allow-Methods","GET, PATCH, DELETE, OPTIONS"),r.setHeader("Access-Control-Allow-Headers","Content-Type"),a.method==="OPTIONS"){r.writeHead(204),r.end();return}if(d==="/"||d==="/index.html"){r.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),r.end(se);return}try{if(d==="/api/issues"&&a.method==="GET"){let c=u.searchParams.get("status"),m=u.searchParams.get("environment")||void 0,p={};c&&(p.status=c),m&&(p.environment=m);let j=Object.keys(p).length>0?o.getIssues(p):o.getIssues();r.writeHead(200,{"Content-Type":"application/json"}),r.end(JSON.stringify(j));return}if(d==="/api/stats"&&a.method==="GET"){let c=o.getStats();r.writeHead(200,{"Content-Type":"application/json"}),r.end(JSON.stringify(c));return}let g=d.match(/^\/api\/issues\/([a-f0-9]+)$/),f=d.match(/^\/api\/issues\/([a-f0-9]+)\/events$/);if(f&&a.method==="GET"){let c=f[1],m=parseInt(u.searchParams.get("limit")??"20",10),p=parseInt(u.searchParams.get("offset")??"0",10),j=o.getEvents(c,{limit:m,offset:p});r.writeHead(200,{"Content-Type":"application/json"}),r.end(JSON.stringify(j));return}if(g&&a.method==="GET"){let c=g[1],m=o.getIssue(c);if(!m){r.writeHead(404,{"Content-Type":"application/json"}),r.end(JSON.stringify({error:"Issue not found"}));return}let p=o.getLatestEvent(c);r.writeHead(200,{"Content-Type":"application/json"}),r.end(JSON.stringify({issue:m,event:p??null}));return}if(g&&a.method==="PATCH"){let c=g[1],m=await re(a),{status:p}=JSON.parse(m);o.updateIssueStatus(c,p),r.writeHead(200,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!0}));return}if(d==="/api/issues"&&a.method==="DELETE"){o.deleteAllIssues(),r.writeHead(200,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!0}));return}}catch(g){r.writeHead(500,{"Content-Type":"application/json"}),r.end(JSON.stringify({error:String(g)}));return}r.writeHead(404),r.end("Not found");});s.on("error",a=>{a.code==="EADDRINUSE"?(console.error(t(k,`
472
+ Port ${e} is already in use. Try: npx @uncaughtdev/core dashboard --port ${e+1}
473
+ `)),process.exitCode=1):(console.error(t(k,`
474
+ Server error:`),a),process.exitCode=1);}),s.listen(e,()=>{console.log(""),console.log(t($," uncaught dashboard")),console.log(t(h," \u2500".repeat(35))),console.log(""),console.log(` ${t(x,"\u25CF")} Running at ${t(b,`http://localhost:${e}`)}`),console.log(""),console.log(t(h," Press Ctrl+C to stop")),console.log("");let a=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";try{spawn(a,[`http://localhost:${e}`],{detached:!0,stdio:"ignore"}).unref();}catch{}}),process.on("SIGINT",()=>{console.log(t(h,`
475
+ Shutting down...`)),o.close(),s.close(),process.exit(0);});}function re(e){return new Promise((n,o)=>{let s="";e.on("data",a=>{s+=a;}),e.on("end",()=>n(s)),e.on("error",o);})}async function ie(){let e=process.argv.slice(2),n=e[0]??"list";switch(n){case "init":case "setup":await ne();break;case "list":case "ls":await _();break;case "show":case "view":{let o=e[1];if(!o){console.error(t(k,`
90
476
  Missing issue number. Usage: uncaught show <n>
91
- `)),process.exitCode=1;return}let s=e.includes("--open")||e.includes("-o");await _(o,s);break}case "resolve":{let o=e[1];if(!o){console.error(n(b,`
477
+ `)),process.exitCode=1;return}let s=e.includes("--open")||e.includes("-o");await V(o,s);break}case "resolve":{let o=e[1];if(!o){console.error(t(k,`
92
478
  Missing issue number. Usage: uncaught resolve <n>
93
- `)),process.exitCode=1;return}await G(o);break}case "clear":case "clean":await W();break;case "help":case "--help":case "-h":R();break;default:console.error(n(b,`
94
- Unknown command: ${t}`)),R(),process.exitCode=1;break}}X().catch(e=>{console.error(n(b,`
479
+ `)),process.exitCode=1;return}await Y(o);break}case "clear":case "clean":await W();break;case "dashboard":case "dash":case "ui":{let o=e.indexOf("--port"),s=o>=0&&parseInt(e[o+1],10)||3300;await ae(s);break}case "help":case "--help":case "-h":U();break;default:console.error(t(k,`
480
+ Unknown command: ${n}`)),U(),process.exitCode=1;break}}ie().catch(e=>{console.error(t(k,`
95
481
  Unexpected error:`),e),process.exitCode=1;});//# sourceMappingURL=local-viewer.mjs.map
96
482
  //# sourceMappingURL=local-viewer.mjs.map