@qiaolei81/copilot-session-viewer 0.3.4 → 0.3.5
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/bin/copilot-session-viewer +2 -2
- package/dist/server.min.js +99 -0
- package/package.json +5 -17
- package/public/vendor/marked.umd.min.js +8 -0
- package/public/vendor/purify.min.js +3 -0
- package/public/vendor/vue-virtual-scroller.css +1 -0
- package/public/vendor/vue-virtual-scroller.min.js +2 -0
- package/public/vendor/vue.global.prod.min.js +19 -0
- package/views/session-vue.ejs +5 -5
- package/views/time-analyze.ejs +2 -2
- package/lib/parsers/README.md +0 -239
- package/lib/parsers/base-parser.js +0 -53
- package/lib/parsers/claude-parser.js +0 -181
- package/lib/parsers/copilot-parser.js +0 -143
- package/lib/parsers/index.js +0 -15
- package/lib/parsers/parser-factory.js +0 -77
- package/lib/parsers/pi-mono-parser.js +0 -119
- package/lib/parsers/vscode-parser.js +0 -591
- package/server.js +0 -29
- package/src/app.js +0 -129
- package/src/config/index.js +0 -27
- package/src/controllers/insightController.js +0 -136
- package/src/controllers/sessionController.js +0 -449
- package/src/controllers/tagController.js +0 -113
- package/src/controllers/uploadController.js +0 -648
- package/src/middleware/common.js +0 -67
- package/src/middleware/rateLimiting.js +0 -62
- package/src/models/Session.js +0 -146
- package/src/routes/api.js +0 -11
- package/src/routes/insights.js +0 -12
- package/src/routes/pages.js +0 -12
- package/src/routes/uploads.js +0 -14
- package/src/schemas/event.schema.js +0 -73
- package/src/services/eventNormalizer.js +0 -291
- package/src/services/insightService.js +0 -535
- package/src/services/sessionRepository.js +0 -1092
- package/src/services/sessionService.js +0 -1919
- package/src/services/tagService.js +0 -205
- package/src/telemetry.js +0 -152
- package/src/utils/fileUtils.js +0 -305
- package/src/utils/helpers.js +0 -45
- package/src/utils/processManager.js +0 -85
package/server.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// IMPORTANT: Initialize telemetry FIRST, before any other requires (especially Express)
|
|
2
|
-
require('./src/telemetry');
|
|
3
|
-
|
|
4
|
-
const createApp = require('./src/app');
|
|
5
|
-
const config = require('./src/config');
|
|
6
|
-
const processManager = require('./src/utils/processManager');
|
|
7
|
-
|
|
8
|
-
// Create the Express app
|
|
9
|
-
const app = createApp();
|
|
10
|
-
|
|
11
|
-
// Export app for testing
|
|
12
|
-
module.exports = app;
|
|
13
|
-
|
|
14
|
-
// Start server only if not being required by tests
|
|
15
|
-
if (require.main === module) {
|
|
16
|
-
const server = app.listen(config.PORT, () => {
|
|
17
|
-
console.log(`🚀 Copilot Session Viewer running at http://localhost:${config.PORT}`);
|
|
18
|
-
console.log(`🔧 Environment: ${config.NODE_ENV}`);
|
|
19
|
-
console.log(`⚡ Active processes: ${processManager.getActiveCount()}`);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
// Graceful shutdown
|
|
23
|
-
process.on('SIGTERM', () => {
|
|
24
|
-
console.log('📛 SIGTERM received, closing server...');
|
|
25
|
-
server.close(() => {
|
|
26
|
-
console.log('✅ Server closed');
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
}
|
package/src/app.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
const express = require('express');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const compression = require('compression');
|
|
4
|
-
const helmet = require('helmet');
|
|
5
|
-
|
|
6
|
-
// Configuration
|
|
7
|
-
const config = require('./config');
|
|
8
|
-
|
|
9
|
-
// Middleware
|
|
10
|
-
// Rate limiting disabled for local development
|
|
11
|
-
// const { globalLimiter, insightGenerationLimiter, insightAccessLimiter, uploadLimiter } = require('./middleware/rateLimiting');
|
|
12
|
-
const { requestTimeout, developmentCors, errorHandler, notFoundHandler, telemetryLocals } = require('./middleware/common');
|
|
13
|
-
|
|
14
|
-
// Controllers
|
|
15
|
-
const SessionController = require('./controllers/sessionController');
|
|
16
|
-
const InsightController = require('./controllers/insightController');
|
|
17
|
-
const UploadController = require('./controllers/uploadController');
|
|
18
|
-
const TagController = require('./controllers/tagController');
|
|
19
|
-
|
|
20
|
-
function createApp(options = {}) {
|
|
21
|
-
const app = express();
|
|
22
|
-
|
|
23
|
-
// Disable Express's automatic ETag generation (prevents 304 on live/active session files)
|
|
24
|
-
app.set('etag', false);
|
|
25
|
-
|
|
26
|
-
// Create controller instances (with optional dependency injection)
|
|
27
|
-
const sessionController = new SessionController(options.sessionService);
|
|
28
|
-
const insightController = new InsightController(options.insightService, options.sessionService);
|
|
29
|
-
const uploadController = new UploadController();
|
|
30
|
-
const tagController = new TagController(options.tagService);
|
|
31
|
-
|
|
32
|
-
// Minimal security headers for local development tool
|
|
33
|
-
// Custom CSP without upgrade-insecure-requests
|
|
34
|
-
app.use((req, res, next) => {
|
|
35
|
-
res.setHeader(
|
|
36
|
-
'Content-Security-Policy',
|
|
37
|
-
"default-src 'self'; " +
|
|
38
|
-
"style-src 'self' 'unsafe-inline' https: http:; " +
|
|
39
|
-
"font-src 'self' https: http:; " +
|
|
40
|
-
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https: http:; " +
|
|
41
|
-
"img-src 'self' data: https: http:; " +
|
|
42
|
-
"connect-src 'self' https: http:"
|
|
43
|
-
);
|
|
44
|
-
next();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Other helmet protections (without CSP and HSTS)
|
|
48
|
-
app.use(helmet({
|
|
49
|
-
contentSecurityPolicy: false,
|
|
50
|
-
hsts: false,
|
|
51
|
-
referrerPolicy: false,
|
|
52
|
-
crossOriginEmbedderPolicy: false,
|
|
53
|
-
crossOriginOpenerPolicy: false,
|
|
54
|
-
crossOriginResourcePolicy: false
|
|
55
|
-
}));
|
|
56
|
-
|
|
57
|
-
app.use(compression({
|
|
58
|
-
level: 1, // Fast compression (speed > ratio for local use)
|
|
59
|
-
threshold: 1024, // Compress responses > 1KB
|
|
60
|
-
filter: (req, res) => {
|
|
61
|
-
// Skip compression for large JSON API responses (handled separately)
|
|
62
|
-
if (req.path.includes('/events') && res.getHeader('Content-Type')?.includes('application/json')) {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
return compression.filter(req, res);
|
|
66
|
-
}
|
|
67
|
-
}));
|
|
68
|
-
app.use(express.json({ limit: '1mb' }));
|
|
69
|
-
app.use(express.urlencoded({ extended: true }));
|
|
70
|
-
app.use(requestTimeout);
|
|
71
|
-
app.use(telemetryLocals);
|
|
72
|
-
|
|
73
|
-
// CORS in development
|
|
74
|
-
if (config.NODE_ENV === 'development') {
|
|
75
|
-
app.use(developmentCors);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Rate limiting - DISABLED for local development
|
|
79
|
-
// app.use(globalLimiter);
|
|
80
|
-
|
|
81
|
-
// Static files
|
|
82
|
-
app.use('/public', express.static(path.join(__dirname, '../public')));
|
|
83
|
-
|
|
84
|
-
// View engine
|
|
85
|
-
app.set('view engine', 'ejs');
|
|
86
|
-
app.set('views', path.join(__dirname, '../views'));
|
|
87
|
-
|
|
88
|
-
// Routes with controllers
|
|
89
|
-
|
|
90
|
-
// Page routes
|
|
91
|
-
app.get('/', sessionController.getHomepage.bind(sessionController));
|
|
92
|
-
app.get('/session/:id', sessionController.getSessionDetail.bind(sessionController));
|
|
93
|
-
app.get('/session/:id/time-analyze', sessionController.getTimeAnalysis.bind(sessionController));
|
|
94
|
-
app.get('/session/:id/export', sessionController.exportSession.bind(sessionController));
|
|
95
|
-
|
|
96
|
-
// API routes (more specific routes first)
|
|
97
|
-
app.get('/api/sessions/load-more', sessionController.loadMoreSessions.bind(sessionController));
|
|
98
|
-
app.get('/api/sessions', sessionController.getSessions.bind(sessionController));
|
|
99
|
-
app.get('/api/sessions/:id/events', sessionController.getSessionEvents.bind(sessionController));
|
|
100
|
-
app.get('/api/sessions/:id/timeline', sessionController.getTimeline.bind(sessionController));
|
|
101
|
-
|
|
102
|
-
// Tag routes
|
|
103
|
-
app.get('/api/tags', tagController.getAllTags.bind(tagController));
|
|
104
|
-
app.get('/api/sessions/:id/tags', tagController.getSessionTags.bind(tagController));
|
|
105
|
-
app.put('/api/sessions/:id/tags', tagController.setSessionTags.bind(tagController));
|
|
106
|
-
|
|
107
|
-
// Upload routes
|
|
108
|
-
app.get('/session/:id/share', uploadController.shareSession.bind(uploadController));
|
|
109
|
-
app.post('/session/import',
|
|
110
|
-
(req, res, next) => uploadController.getUploadMiddleware()(req, res, next),
|
|
111
|
-
uploadController.importSession.bind(uploadController)
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
// Insight routes (rate limiting disabled)
|
|
115
|
-
app.post('/session/:id/insight', insightController.generateInsight.bind(insightController));
|
|
116
|
-
app.get('/session/:id/insight', insightController.getInsightStatus.bind(insightController));
|
|
117
|
-
app.delete('/session/:id/insight', insightController.deleteInsight.bind(insightController));
|
|
118
|
-
|
|
119
|
-
// Upload rate limiting - DISABLED
|
|
120
|
-
// app.use('/session/import', uploadLimiter);
|
|
121
|
-
|
|
122
|
-
// Error handling
|
|
123
|
-
app.use(notFoundHandler);
|
|
124
|
-
app.use(errorHandler);
|
|
125
|
-
|
|
126
|
-
return app;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
module.exports = createApp;
|
package/src/config/index.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Application Configuration Constants
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
module.exports = {
|
|
6
|
-
// Server
|
|
7
|
-
PORT: process.env.PORT || 3838,
|
|
8
|
-
NODE_ENV: process.env.NODE_ENV || 'production', // Default to production for security
|
|
9
|
-
|
|
10
|
-
// Logging
|
|
11
|
-
LOG_LEVEL: process.env.LOG_LEVEL || (process.env.NODE_ENV === 'development' ? 'DEBUG' : 'INFO'),
|
|
12
|
-
|
|
13
|
-
// Insight Generation
|
|
14
|
-
INSIGHT_TIMEOUT_MS: 5 * 60 * 1000, // 5 minutes
|
|
15
|
-
INSIGHT_CACHE_TTL_MS: 24 * 60 * 60 * 1000, // 24 hours
|
|
16
|
-
|
|
17
|
-
// File Upload
|
|
18
|
-
MAX_UPLOAD_SIZE: 10 * 1024 * 1024, // 10MB (reduced from 50MB for security)
|
|
19
|
-
|
|
20
|
-
// Session Repository
|
|
21
|
-
SESSION_CACHE_TTL_MS: 30 * 1000, // 30 seconds
|
|
22
|
-
|
|
23
|
-
// Request Limits - More lenient for better UX
|
|
24
|
-
RATE_LIMIT_WINDOW_MS: 15 * 60 * 1000, // 15 minutes
|
|
25
|
-
RATE_LIMIT_MAX_REQUESTS: 15, // Increased from 5 to 15 for better UX
|
|
26
|
-
REQUEST_TIMEOUT_MS: 30 * 1000 // 30 seconds
|
|
27
|
-
};
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
const InsightService = require('../services/insightService');
|
|
2
|
-
const { isValidSessionId } = require('../utils/helpers');
|
|
3
|
-
const { trackEvent, trackMetric, trackException } = require('../telemetry');
|
|
4
|
-
|
|
5
|
-
class InsightController {
|
|
6
|
-
constructor(insightService = null, sessionService = null) {
|
|
7
|
-
if (insightService) {
|
|
8
|
-
this.insightService = insightService;
|
|
9
|
-
} else {
|
|
10
|
-
// Use default multi-source configuration
|
|
11
|
-
this.insightService = new InsightService();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// SessionService for getting session metadata (source)
|
|
15
|
-
if (sessionService) {
|
|
16
|
-
this.sessionService = sessionService;
|
|
17
|
-
} else {
|
|
18
|
-
const SessionService = require('../services/sessionService');
|
|
19
|
-
this.sessionService = new SessionService();
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Generate or get insight
|
|
24
|
-
async generateInsight(req, res) {
|
|
25
|
-
try {
|
|
26
|
-
const sessionId = req.params.id;
|
|
27
|
-
const forceRegenerate = req.body?.force === true;
|
|
28
|
-
|
|
29
|
-
if (!isValidSessionId(sessionId)) {
|
|
30
|
-
return res.status(400).json({ error: 'Invalid session ID' });
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Get session to determine source and directory
|
|
34
|
-
const session = await this.sessionService.getSessionById(sessionId);
|
|
35
|
-
if (!session) {
|
|
36
|
-
return res.status(404).json({ error: 'Session not found' });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (!session.directory) {
|
|
40
|
-
return res.status(400).json({ error: 'Session directory not available' });
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const startTime = Date.now();
|
|
44
|
-
const result = await this.insightService.generateInsight(session.id, session.directory, session.source, forceRegenerate);
|
|
45
|
-
const durationMs = Date.now() - startTime;
|
|
46
|
-
|
|
47
|
-
// Track InsightGenerated event
|
|
48
|
-
trackEvent('InsightGenerated', {
|
|
49
|
-
sessionId,
|
|
50
|
-
source: session.source || 'unknown',
|
|
51
|
-
durationMs: durationMs.toString()
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// Track InsightGenerationTime metric
|
|
55
|
-
trackMetric('InsightGenerationTime', durationMs, { sessionId, source: session.source || 'unknown' });
|
|
56
|
-
|
|
57
|
-
res.json(result);
|
|
58
|
-
} catch (err) {
|
|
59
|
-
console.error('Error generating insight:', err);
|
|
60
|
-
|
|
61
|
-
// Track insight generation failure
|
|
62
|
-
trackException(err, {
|
|
63
|
-
sessionId: req.params.id,
|
|
64
|
-
operation: 'generateInsight'
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
res.status(500).json({ error: err.message || 'Error generating insight' });
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Get insight status
|
|
72
|
-
async getInsightStatus(req, res) {
|
|
73
|
-
try {
|
|
74
|
-
const sessionId = req.params.id;
|
|
75
|
-
|
|
76
|
-
if (!isValidSessionId(sessionId)) {
|
|
77
|
-
return res.status(400).json({ error: 'Invalid session ID' });
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Get session to determine directory
|
|
81
|
-
const session = await this.sessionService.getSessionById(sessionId);
|
|
82
|
-
if (!session) {
|
|
83
|
-
return res.status(404).json({ error: 'Session not found' });
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!session.directory) {
|
|
87
|
-
return res.status(400).json({ error: 'Session directory not available' });
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const result = await this.insightService.getInsightStatus(session.id, session.directory, session.source);
|
|
91
|
-
|
|
92
|
-
// Track InsightViewed event if insight is ready
|
|
93
|
-
if (result.status === 'ready' && result.report) {
|
|
94
|
-
trackEvent('InsightViewed', { sessionId });
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
res.json(result);
|
|
98
|
-
} catch (err) {
|
|
99
|
-
console.error('Error getting insight status:', err);
|
|
100
|
-
res.status(500).json({ error: 'Error getting insight status' });
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Delete insight
|
|
105
|
-
async deleteInsight(req, res) {
|
|
106
|
-
try {
|
|
107
|
-
const sessionId = req.params.id;
|
|
108
|
-
|
|
109
|
-
if (!isValidSessionId(sessionId)) {
|
|
110
|
-
return res.status(400).json({ error: 'Invalid session ID' });
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Get session to determine directory
|
|
114
|
-
const session = await this.sessionService.getSessionById(sessionId);
|
|
115
|
-
if (!session) {
|
|
116
|
-
return res.status(404).json({ error: 'Session not found' });
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (!session.directory) {
|
|
120
|
-
return res.status(400).json({ error: 'Session directory not available' });
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const result = await this.insightService.deleteInsight(session.id, session.directory, session.source);
|
|
124
|
-
|
|
125
|
-
// Track InsightDeleted event
|
|
126
|
-
trackEvent('InsightDeleted', { sessionId });
|
|
127
|
-
|
|
128
|
-
res.json(result);
|
|
129
|
-
} catch (err) {
|
|
130
|
-
console.error('Error deleting insight:', err);
|
|
131
|
-
res.status(500).json({ error: 'Error deleting insight' });
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
module.exports = InsightController;
|