@qiaolei81/copilot-session-viewer 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qiaolei81/copilot-session-viewer",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Web UI for viewing GitHub Copilot CLI session logs",
5
5
  "author": "Lei Qiao <qiaolei81@gmail.com>",
6
6
  "license": "MIT",
@@ -31,6 +31,8 @@
31
31
  "copilot-session-viewer": "./bin/copilot-session-viewer"
32
32
  },
33
33
  "scripts": {
34
+ "build": "node scripts/build.mjs",
35
+ "build:watch": "node scripts/build.mjs --watch",
34
36
  "start": "node server.js",
35
37
  "dev": "nodemon server.js",
36
38
  "test": "jest",
@@ -47,9 +49,12 @@
47
49
  "release": "./scripts/release.sh",
48
50
  "release:patch": "./scripts/release.sh patch",
49
51
  "release:minor": "./scripts/release.sh minor",
50
- "release:major": "./scripts/release.sh major"
52
+ "release:major": "./scripts/release.sh major",
53
+ "build:prod": "node scripts/build.mjs --prod",
54
+ "prepublishOnly": "npm run build:prod"
51
55
  },
52
56
  "dependencies": {
57
+ "applicationinsights": "^2.9.8",
53
58
  "compression": "^1.8.1",
54
59
  "dompurify": "^3.3.1",
55
60
  "ejs": "^4.0.1",
@@ -57,7 +62,8 @@
57
62
  "express-rate-limit": "^8.2.1",
58
63
  "helmet": "^8.1.0",
59
64
  "multer": "^2.0.2",
60
- "zod": "^4.3.6"
65
+ "zod": "^4.3.6",
66
+ "adm-zip": "^0.5.16"
61
67
  },
62
68
  "devDependencies": {
63
69
  "@eslint/css": "^0.14.1",
@@ -67,7 +73,7 @@
67
73
  "@istanbuljs/nyc-config-typescript": "^1.0.2",
68
74
  "@playwright/test": "^1.58.2",
69
75
  "@types/jest": "^30.0.0",
70
- "adm-zip": "^0.5.16",
76
+ "esbuild": "^0.27.3",
71
77
  "eslint": "^10.0.0",
72
78
  "eslint-plugin-vue": "^10.8.0",
73
79
  "globals": "^17.3.0",
@@ -78,5 +84,28 @@
78
84
  "nyc": "^17.1.0",
79
85
  "supertest": "^7.2.2",
80
86
  "v8-to-istanbul": "^9.3.0"
81
- }
87
+ },
88
+ "files": [
89
+ "bin/",
90
+ "server.js",
91
+ "lib/",
92
+ "src/app.js",
93
+ "src/config/",
94
+ "src/controllers/",
95
+ "src/middleware/",
96
+ "src/models/",
97
+ "src/routes/",
98
+ "src/schemas/",
99
+ "src/services/",
100
+ "src/telemetry.js",
101
+ "src/utils/",
102
+ "!src/**/__tests__/",
103
+ "!src/**/*.test.js",
104
+ "views/",
105
+ "public/css/",
106
+ "public/js/*.min.js",
107
+ "public/img/",
108
+ "LICENSE",
109
+ "README.md"
110
+ ]
82
111
  }
@@ -0,0 +1,35 @@
1
+ (()=>{var v=window.__PAGE_DATA||{},L=v.sessions||[],V=v.totalSessions||0,x=v.hasMore||!1,E=v.sourceHints||{},m=[...L],u={};u.copilot={offset:L.length,hasMore:x};var p=!1,i="copilot";function h(){return u[i]||(u[i]={offset:0,hasMore:!0}),u[i]}async function A(){let e=h();if(p||!e.hasMore)return;p=!0;let t=document.getElementById("loading-indicator");t.style.display="block",window.trackClick("LoadMoreClicked",{currentPage:Math.floor(e.offset/20)+1,offset:e.offset,source:i});try{let s=await fetch(`/api/sessions/load-more?offset=${h().offset}&limit=20&source=${encodeURIComponent(i)}`);if(!s.ok)throw new Error("Failed to load more sessions");let o=await s.json(),n=new Set(m.map(r=>r.id)),a=[];for(let r of o.sessions)n.has(r.id)||(m.push(r),a.push(r));h().offset+=o.sessions.length,h().hasMore=o.hasMore,await $(a),S()}catch(s){console.error("Error loading more sessions:",s)}finally{p=!1,t.style.display="none"}}function D(){return m.filter(e=>e.source===i)}function S(){let e=document.getElementById("sessions-container");e.innerHTML="";let t=D();if(t.length===0){e.innerHTML='<div style="text-align: center; color: #6e7681; padding: 40px; font-size: 14px;">No sessions found for this filter.</div>';return}let s=z(t);Object.keys(s).sort((n,a)=>a.localeCompare(n)).forEach(n=>{let a=document.createElement("div");a.className="date-group-header",a.textContent=Z(s[n][0].createdAt),e.appendChild(a);let r=document.createElement("div");r.className="recent-list",s[n].forEach(l=>{r.innerHTML+=j(l)}),e.appendChild(r)})}function T(){let e=window.pageYOffset||document.documentElement.scrollTop,t=window.innerHeight,s=document.documentElement.scrollHeight;e+t>=s-500&&h().hasMore&&!p&&A()}var y;function B(){y||(y=setTimeout(()=>{T(),y=null},100))}function F(e){e.preventDefault();let t=document.getElementById("sessionInput").value.trim();t&&(window.location.href=`/session/${t}`)}document.getElementById("sessionForm").addEventListener("submit",F);var w=document.getElementById("fileInput"),d=document.getElementById("importLink"),I=document.getElementById("importStatus");d.addEventListener("click",e=>{e.preventDefault(),w.click()});w.addEventListener("change",async e=>{let t=e.target.files[0];if(t){if(!t.name.endsWith(".zip")){g("error","\u274C Please select a .zip file");return}d.style.pointerEvents="none",d.style.opacity="0.5",d.textContent="Importing...",g("loading","Uploading and extracting session...");try{let s=new FormData;s.append("sessionZip",t);let o=await fetch("/session/import",{method:"POST",body:s}),n=await o.json();o.ok?(g("success",`\u2705 Session ${n.sessionId} imported successfully!`),setTimeout(()=>{window.location.reload()},1500)):(g("error",`\u274C Import failed: ${n.error}`),d.style.pointerEvents="auto",d.style.opacity="1",d.textContent="Import session from zip")}catch(s){g("error",`\u274C Import failed: ${s.message}`),d.style.pointerEvents="auto",d.style.opacity="1",d.textContent="Import session from zip"}finally{w.value=""}}});function g(e,t){I.className=`import-status ${e}`,I.textContent=t}function P(e){if(!e||e<0)return"\u2014";let t=Math.floor(e/1e3),s=Math.floor(t/60),o=Math.floor(s/60);if(o>0){let n=s%60;return`${o}h ${n}m`}else if(s>0){let n=t%60;return`${s}m ${n}s`}else return`${t}s`}function Z(e){let t=new Date(e),s=t.getFullYear(),o=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0");return`${s}/${o}/${n}`}function N(e){if(!e)return"Unknown";let t=new Date(e),s=t.getFullYear(),o=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0");return`${s}-${o}-${n}`}function z(e){let t={};return e.forEach(s=>{let o=N(s.createdAt);t[o]||(t[o]=[]),t[o].push(s)}),t}function j(e){let t="",s=e.sourceBadgeClass||"source-copilot",o=e.sourceName||"Copilot";if(t+=`<span class="status-badge ${s}" title="${o}">${o}</span>`,e.sessionStatus==="wip"&&(t+='<span class="status-badge wip" title="Session in progress">\u{1F504} WIP</span>'),e.isImported&&(t+='<span class="status-badge imported" title="Imported session">\u{1F4E5}</span>'),e.hasInsight&&(t+='<span class="status-badge insight" title="Has Agent Review">\u{1F4A1}</span>'),e.selectedModel){let C=e.selectedModel.replace("claude-","").replace("gpt-","").replace("gemini-",""),f="model-other";e.selectedModel.includes("claude")?f="model-claude":e.selectedModel.includes("gpt")?f="model-gpt":e.selectedModel.includes("gemini")&&(f="model-gemini"),t+=`<span class="status-badge model ${f}" title="Model: ${c(e.selectedModel)}">${c(C)}</span>`}e.copilotVersion&&(t+=`<span class="status-badge version" title="CLI version">${c(e.copilotVersion)}</span>`);let n="";e.summary&&e.summary!=="No summary"&&e.summary!=="Legacy session"?n=`<div class="session-summary">${c(e.summary)}</div>`:n='<div class="session-summary" style="color: #6e7681; font-style: italic;">No summary available</div>';let a="";e.workspace&&e.workspace.cwd&&(a=`
2
+ <div class="session-info-item workspace" title="${c(e.workspace.cwd)}">
3
+ <svg viewBox="0 0 16 16" fill="currentColor"><path d="M1.75 1A1.75 1.75 0 000 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0016 13.25v-8.5A1.75 1.75 0 0014.25 3H7.5a.25.25 0 01-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1H1.75z"></path></svg>
4
+ <span class="session-info-value">${c(e.workspace.cwd.replace(/^\/Users\/[^/]+/,"~"))}</span>
5
+ </div>
6
+ `);let r=e.createdAt?new Date(e.createdAt).toLocaleString("en-US",{month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:!1}):"unknown",l="";e.duration&&(l=`
7
+ <div class="session-info-item">
8
+ <svg viewBox="0 0 16 16" fill="currentColor"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM7.25 12.5v-5A.75.75 0 0 1 8 6.75h2.5a.75.75 0 0 1 0 1.5H8.75v4.25a.75.75 0 0 1-1.5 0Z"></path></svg>
9
+ <span class="session-info-value">${P(e.duration)}</span>
10
+ </div>
11
+ `);let b=e.sessionStatus==="wip"?" recent-item-wip":"",M="";return e.tags&&e.tags.length>0&&(M=`<div class="session-tags">${e.tags.map(f=>`<span class="session-tag" style="background-color: ${U(f)}" title="${c(f)}">${c(f)}</span>`).join("")}</div>`),`
12
+ <a href="/session/${e.id}" class="recent-item${b}" onclick="trackClick('SessionCardClicked', { sessionId: '${c(e.id)}', source: '${c(e.source||"unknown")}' })">
13
+ <div class="session-id">
14
+ <span class="session-id-text" title="${c(e.id)}">${c(e.id)}</span>
15
+ </div>
16
+ <div class="session-badges-tags">
17
+ <div class="session-badges">${t}</div>
18
+ ${M}
19
+ </div>
20
+ ${n}
21
+ <div class="session-divider"></div>
22
+ <div class="session-info">
23
+ ${a}
24
+ <div class="session-info-item">
25
+ <svg viewBox="0 0 16 16" fill="currentColor"><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"></path></svg>
26
+ <span class="session-info-value">${r}</span>
27
+ </div>
28
+ ${l}
29
+ <div class="session-info-item">
30
+ <svg viewBox="0 0 16 16" fill="currentColor"><path d="M7.72.72a.75.75 0 0 1 1.06 0l1.5 1.5a.75.75 0 0 1-1.06 1.06l-.22-.22v1.69a.75.75 0 0 1-1.5 0V3.06l-.22.22a.75.75 0 0 1-1.06-1.06ZM2 7a.75.75 0 0 0 0 1.5h3.69l-.22.22a.75.75 0 1 0 1.06 1.06l1.5-1.5a.75.75 0 0 0 0-1.06l-1.5-1.5a.75.75 0 0 0-1.06 1.06l.22.22Zm8.53-.28a.75.75 0 0 0 0 1.06l1.5 1.5a.75.75 0 1 0 1.06-1.06l-.22-.22H16a.75.75 0 0 0 0-1.5h-3.13l.22-.22a.75.75 0 0 0-1.06-1.06ZM7.72 12.22a.75.75 0 0 1 1.06 0l1.5 1.5a.75.75 0 1 1-1.06 1.06l-.22-.22v1.69a.75.75 0 0 1-1.5 0v-1.69l-.22.22a.75.75 0 0 1-1.06-1.06Z"></path></svg>
31
+ <span class="session-info-value">${e.eventCount||0} events</span>
32
+ </div>
33
+ </div>
34
+ </a>
35
+ `}function c(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}var k=["#3b82f6","#10b981","#f59e0b","#ef4444","#8b5cf6","#ec4899","#06b6d4","#f97316"];function U(e){let t=0;for(let s=0;s<e.length;s++)t=e.charCodeAt(s)+((t<<5)-t);return k[Math.abs(t)%k.length]}async function O(e){try{let t=e.map(n=>fetch(`/api/sessions/${n}/tags`).then(a=>a.ok?a.json():{tags:[]}).then(a=>({id:n,tags:a.tags||[]})).catch(()=>({id:n,tags:[]}))),s=await Promise.all(t),o={};return s.forEach(({id:n,tags:a})=>{o[n]=a}),o}catch(t){return console.error("Error loading session tags:",t),{}}}async function $(e){let t=e.map(o=>o.id),s=await O(t);e.forEach(o=>{o.tags=s[o.id]||[]})}function H(e){let t=document.getElementById("sourceHint");t&&E[e]?t.innerHTML='Sessions from <span class="hint-code">'+E[e]+"</span>":t&&(t.textContent="")}function _(){let e=document.querySelectorAll(".filter-pill");H(i),e.forEach(t=>{t.addEventListener("click",async()=>{if(e.forEach(s=>s.classList.remove("active")),t.classList.add("active"),i=t.getAttribute("data-source"),H(i),window.trackClick("FilterPillClicked",{pillName:t.textContent.trim(),dataSource:i}),u[i]||(u[i]={offset:0,hasMore:!0}),u[i].offset===0&&!p){p=!0;let s=document.getElementById("sessions-container");s.innerHTML='<div style="text-align: center; color: #6e7681; padding: 40px; font-size: 14px;">\u23F3 Loading...</div>',document.getElementById("loading-indicator").style.display="none";try{let o=await fetch(`/api/sessions/load-more?offset=0&limit=20&source=${encodeURIComponent(i)}`);if(o.ok){let n=await o.json(),a=new Set(m.map(l=>l.id)),r=[];for(let l of n.sessions||[])a.has(l.id)||(m.push(l),r.push(l));u[i].offset=(n.sessions||[]).length,u[i].hasMore=n.hasMore,await $(r)}}catch(o){console.error("Failed to load sessions for source:",i,o)}finally{p=!1}}S()})})}document.addEventListener("DOMContentLoaded",async function(){await $(m),S(),window.addEventListener("scroll",B),_()});})();