claude-code-templates 1.10.1 → 1.11.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.
- package/README.md +6 -0
- package/bin/create-claude-config.js +1 -0
- package/package.json +2 -2
- package/src/analytics/core/ConversationAnalyzer.js +94 -20
- package/src/analytics/core/FileWatcher.js +146 -11
- package/src/analytics/data/DataCache.js +124 -19
- package/src/analytics/notifications/NotificationManager.js +37 -0
- package/src/analytics/notifications/WebSocketServer.js +1 -1
- package/src/analytics-web/FRONT_ARCHITECTURE.md +46 -0
- package/src/analytics-web/assets/js/{main.js → main.js.deprecated} +32 -3
- package/src/analytics-web/components/AgentsPage.js +2535 -0
- package/src/analytics-web/components/App.js +430 -0
- package/src/analytics-web/components/{Dashboard.js → Dashboard.js.deprecated} +23 -7
- package/src/analytics-web/components/DashboardPage.js +1527 -0
- package/src/analytics-web/components/Sidebar.js +197 -0
- package/src/analytics-web/components/ToolDisplay.js +539 -0
- package/src/analytics-web/index.html +3275 -1792
- package/src/analytics-web/services/DataService.js +89 -16
- package/src/analytics-web/services/StateService.js +9 -0
- package/src/analytics-web/services/WebSocketService.js +17 -5
- package/src/analytics.js +323 -35
- package/src/console-bridge.js +610 -0
- package/src/file-operations.js +143 -23
- package/src/index.js +24 -1
- package/src/templates.js +4 -0
- package/src/test-console-bridge.js +67 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App - Main application component that handles routing and navigation
|
|
3
|
+
* Orchestrates the sidebar, dashboard, and agents pages
|
|
4
|
+
*/
|
|
5
|
+
class App {
|
|
6
|
+
constructor(container, services) {
|
|
7
|
+
this.container = container;
|
|
8
|
+
this.services = services;
|
|
9
|
+
|
|
10
|
+
this.components = {};
|
|
11
|
+
this.currentPage = 'dashboard';
|
|
12
|
+
this.isInitialized = false;
|
|
13
|
+
|
|
14
|
+
this.init();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the application
|
|
19
|
+
*/
|
|
20
|
+
async init() {
|
|
21
|
+
if (this.isInitialized) return;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await this.render();
|
|
25
|
+
await this.initializeComponents();
|
|
26
|
+
await this.setupRouting();
|
|
27
|
+
this.bindEvents();
|
|
28
|
+
this.isInitialized = true;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Error initializing app:', error);
|
|
31
|
+
this.showError('Failed to initialize application');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Render the application structure
|
|
37
|
+
*/
|
|
38
|
+
async render() {
|
|
39
|
+
this.container.innerHTML = `
|
|
40
|
+
<div class="app">
|
|
41
|
+
<!-- Sidebar Navigation -->
|
|
42
|
+
<aside class="app-sidebar" id="app-sidebar">
|
|
43
|
+
<!-- Sidebar component will be mounted here -->
|
|
44
|
+
</aside>
|
|
45
|
+
|
|
46
|
+
<!-- Main Content Area -->
|
|
47
|
+
<main class="app-main" id="app-main">
|
|
48
|
+
<div class="app-content" id="app-content">
|
|
49
|
+
<!-- Page content will be mounted here -->
|
|
50
|
+
</div>
|
|
51
|
+
</main>
|
|
52
|
+
|
|
53
|
+
<!-- Global Loading Overlay -->
|
|
54
|
+
<div class="global-loading" id="global-loading" style="display: none;">
|
|
55
|
+
<div class="loading-content">
|
|
56
|
+
<div class="loading-spinner large"></div>
|
|
57
|
+
<span class="loading-text">Loading...</span>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<!-- Global Error Modal -->
|
|
62
|
+
<div class="error-modal" id="error-modal" style="display: none;">
|
|
63
|
+
<div class="error-modal-content">
|
|
64
|
+
<div class="error-modal-header">
|
|
65
|
+
<h3>Application Error</h3>
|
|
66
|
+
<button class="error-modal-close" id="error-modal-close">×</button>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="error-modal-body">
|
|
69
|
+
<p class="error-modal-message" id="error-modal-message"></p>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="error-modal-footer">
|
|
72
|
+
<button class="btn btn-primary" id="error-modal-retry">Retry</button>
|
|
73
|
+
<button class="btn btn-secondary" id="error-modal-dismiss">Dismiss</button>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Initialize child components
|
|
83
|
+
*/
|
|
84
|
+
async initializeComponents() {
|
|
85
|
+
// Initialize Sidebar
|
|
86
|
+
const sidebarContainer = this.container.querySelector('#app-sidebar');
|
|
87
|
+
if (typeof Sidebar !== 'undefined') {
|
|
88
|
+
this.components.sidebar = new Sidebar(sidebarContainer, this.handleNavigation.bind(this));
|
|
89
|
+
} else {
|
|
90
|
+
throw new Error('Sidebar component not available. Check if components/Sidebar.js is loaded.');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Initialize pages
|
|
94
|
+
this.components.pages = {};
|
|
95
|
+
|
|
96
|
+
// Load initial page
|
|
97
|
+
await this.loadPage(this.currentPage);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Setup routing (hash-based routing for SPA behavior)
|
|
102
|
+
*/
|
|
103
|
+
setupRouting() {
|
|
104
|
+
// Handle browser navigation
|
|
105
|
+
window.addEventListener('hashchange', () => {
|
|
106
|
+
const hash = window.location.hash.slice(1) || 'dashboard';
|
|
107
|
+
this.navigateToPage(hash);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Set initial route
|
|
111
|
+
const initialHash = window.location.hash.slice(1) || 'dashboard';
|
|
112
|
+
if (initialHash !== this.currentPage) {
|
|
113
|
+
this.navigateToPage(initialHash);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Bind global event listeners
|
|
119
|
+
*/
|
|
120
|
+
bindEvents() {
|
|
121
|
+
// Sidebar toggle handler
|
|
122
|
+
window.addEventListener('sidebar-toggle', (event) => {
|
|
123
|
+
this.handleSidebarToggle(event.detail.collapsed);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Error modal events
|
|
127
|
+
const errorModalClose = this.container.querySelector('#error-modal-close');
|
|
128
|
+
const errorModalDismiss = this.container.querySelector('#error-modal-dismiss');
|
|
129
|
+
const errorModalRetry = this.container.querySelector('#error-modal-retry');
|
|
130
|
+
|
|
131
|
+
errorModalClose.addEventListener('click', () => this.hideError());
|
|
132
|
+
errorModalDismiss.addEventListener('click', () => this.hideError());
|
|
133
|
+
errorModalRetry.addEventListener('click', () => this.retryLastAction());
|
|
134
|
+
|
|
135
|
+
// Global keyboard shortcuts
|
|
136
|
+
document.addEventListener('keydown', (e) => this.handleKeyboardShortcuts(e));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Handle navigation from sidebar
|
|
141
|
+
* @param {string} page - Page to navigate to
|
|
142
|
+
*/
|
|
143
|
+
async handleNavigation(page) {
|
|
144
|
+
await this.navigateToPage(page);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Navigate to a specific page
|
|
149
|
+
* @param {string} page - Page to navigate to
|
|
150
|
+
*/
|
|
151
|
+
async navigateToPage(page) {
|
|
152
|
+
if (page === this.currentPage) return;
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
this.showGlobalLoading();
|
|
156
|
+
|
|
157
|
+
// Cleanup current page
|
|
158
|
+
await this.cleanupCurrentPage();
|
|
159
|
+
|
|
160
|
+
// Load new page
|
|
161
|
+
await this.loadPage(page);
|
|
162
|
+
|
|
163
|
+
// Update state
|
|
164
|
+
this.currentPage = page;
|
|
165
|
+
|
|
166
|
+
// Update URL hash
|
|
167
|
+
window.location.hash = page;
|
|
168
|
+
|
|
169
|
+
// Update sidebar active state
|
|
170
|
+
if (this.components.sidebar) {
|
|
171
|
+
this.components.sidebar.setActivePage(page);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error('Error navigating to page:', page, error);
|
|
176
|
+
this.showError(`Failed to load ${page} page`);
|
|
177
|
+
} finally {
|
|
178
|
+
this.hideGlobalLoading();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Load a specific page
|
|
184
|
+
* @param {string} page - Page to load
|
|
185
|
+
*/
|
|
186
|
+
async loadPage(page) {
|
|
187
|
+
const contentContainer = this.container.querySelector('#app-content');
|
|
188
|
+
|
|
189
|
+
if (!contentContainer) {
|
|
190
|
+
throw new Error('App content container not found');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
console.log(`🔄 Loading page: ${page}`);
|
|
194
|
+
|
|
195
|
+
// First, destroy any existing page component for this page
|
|
196
|
+
if (this.components.pages[page] && this.components.pages[page].destroy) {
|
|
197
|
+
console.log(`🧹 Destroying existing ${page} page component`);
|
|
198
|
+
this.components.pages[page].destroy();
|
|
199
|
+
this.components.pages[page] = null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Always clear and recreate the page to ensure clean state
|
|
203
|
+
contentContainer.innerHTML = '';
|
|
204
|
+
|
|
205
|
+
// Create or recreate page component
|
|
206
|
+
await this.createPageComponent(page, contentContainer);
|
|
207
|
+
|
|
208
|
+
// Call showPage for any additional setup
|
|
209
|
+
await this.showPage(page);
|
|
210
|
+
|
|
211
|
+
console.log(`✅ Page '${page}' loaded successfully`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Create page component
|
|
216
|
+
* @param {string} page - Page type
|
|
217
|
+
* @param {HTMLElement} container - Container element
|
|
218
|
+
*/
|
|
219
|
+
async createPageComponent(page, container) {
|
|
220
|
+
// Cleanup existing page component if it exists
|
|
221
|
+
if (this.components.pages[page] && this.components.pages[page].destroy) {
|
|
222
|
+
this.components.pages[page].destroy();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
switch (page) {
|
|
226
|
+
case 'dashboard':
|
|
227
|
+
if (typeof DashboardPage !== 'undefined') {
|
|
228
|
+
this.components.pages.dashboard = new DashboardPage(container, this.services);
|
|
229
|
+
await this.components.pages.dashboard.initialize();
|
|
230
|
+
} else {
|
|
231
|
+
throw new Error('DashboardPage component not available. Check if components/DashboardPage.js is loaded.');
|
|
232
|
+
}
|
|
233
|
+
break;
|
|
234
|
+
|
|
235
|
+
case 'agents':
|
|
236
|
+
if (typeof AgentsPage !== 'undefined') {
|
|
237
|
+
this.components.pages.agents = new AgentsPage(container, this.services);
|
|
238
|
+
await this.components.pages.agents.initialize();
|
|
239
|
+
} else {
|
|
240
|
+
throw new Error('AgentsPage component not available. Check if components/AgentsPage.js is loaded.');
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
|
|
244
|
+
default:
|
|
245
|
+
throw new Error(`Unknown page: ${page}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Show a specific page (simplified - loadPage handles everything)
|
|
251
|
+
* @param {string} page - Page to show
|
|
252
|
+
*/
|
|
253
|
+
async showPage(page) {
|
|
254
|
+
// This method is now mainly for compatibility
|
|
255
|
+
// The actual work is done in loadPage
|
|
256
|
+
if (this.components.pages[page] && this.components.pages[page].onActivate) {
|
|
257
|
+
try {
|
|
258
|
+
await this.components.pages[page].onActivate();
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.warn(`Warning: onActivate failed for ${page}:`, error);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Cleanup current page
|
|
267
|
+
*/
|
|
268
|
+
async cleanupCurrentPage() {
|
|
269
|
+
const currentPageComponent = this.components.pages[this.currentPage];
|
|
270
|
+
if (currentPageComponent && currentPageComponent.onDeactivate) {
|
|
271
|
+
await currentPageComponent.onDeactivate();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Handle sidebar toggle
|
|
277
|
+
* @param {boolean} collapsed - Whether sidebar is collapsed
|
|
278
|
+
*/
|
|
279
|
+
handleSidebarToggle(collapsed) {
|
|
280
|
+
const appMain = this.container.querySelector('#app-main');
|
|
281
|
+
appMain.classList.toggle('sidebar-collapsed', collapsed);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Handle keyboard shortcuts
|
|
286
|
+
* @param {KeyboardEvent} e - Keyboard event
|
|
287
|
+
*/
|
|
288
|
+
handleKeyboardShortcuts(e) {
|
|
289
|
+
// Ctrl/Cmd + number keys for quick navigation
|
|
290
|
+
if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey) {
|
|
291
|
+
switch (e.key) {
|
|
292
|
+
case '1':
|
|
293
|
+
e.preventDefault();
|
|
294
|
+
this.navigateToPage('dashboard');
|
|
295
|
+
break;
|
|
296
|
+
case '2':
|
|
297
|
+
e.preventDefault();
|
|
298
|
+
this.navigateToPage('agents');
|
|
299
|
+
break;
|
|
300
|
+
case 'r':
|
|
301
|
+
e.preventDefault();
|
|
302
|
+
this.refreshCurrentPage();
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Escape key to close modals
|
|
308
|
+
if (e.key === 'Escape') {
|
|
309
|
+
this.hideError();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Refresh current page
|
|
315
|
+
*/
|
|
316
|
+
async refreshCurrentPage() {
|
|
317
|
+
const currentPageComponent = this.components.pages[this.currentPage];
|
|
318
|
+
if (currentPageComponent && currentPageComponent.refreshData) {
|
|
319
|
+
await currentPageComponent.refreshData();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Show global loading
|
|
325
|
+
*/
|
|
326
|
+
showGlobalLoading() {
|
|
327
|
+
const loadingOverlay = this.container.querySelector('#global-loading');
|
|
328
|
+
loadingOverlay.style.display = 'flex';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Hide global loading
|
|
333
|
+
*/
|
|
334
|
+
hideGlobalLoading() {
|
|
335
|
+
const loadingOverlay = this.container.querySelector('#global-loading');
|
|
336
|
+
loadingOverlay.style.display = 'none';
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Show error modal
|
|
341
|
+
* @param {string} message - Error message
|
|
342
|
+
*/
|
|
343
|
+
showError(message) {
|
|
344
|
+
const errorModal = this.container.querySelector('#error-modal');
|
|
345
|
+
const errorMessage = this.container.querySelector('#error-modal-message');
|
|
346
|
+
|
|
347
|
+
errorMessage.textContent = message;
|
|
348
|
+
errorModal.style.display = 'flex';
|
|
349
|
+
|
|
350
|
+
this.lastError = message;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Hide error modal
|
|
355
|
+
*/
|
|
356
|
+
hideError() {
|
|
357
|
+
const errorModal = this.container.querySelector('#error-modal');
|
|
358
|
+
errorModal.style.display = 'none';
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Retry last action that caused an error
|
|
363
|
+
*/
|
|
364
|
+
async retryLastAction() {
|
|
365
|
+
this.hideError();
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
// Retry loading current page
|
|
369
|
+
await this.loadPage(this.currentPage);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error('Retry failed:', error);
|
|
372
|
+
this.showError('Retry failed. Please refresh the page.');
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Get current page name
|
|
378
|
+
* @returns {string} Current page name
|
|
379
|
+
*/
|
|
380
|
+
getCurrentPage() {
|
|
381
|
+
return this.currentPage;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Get page component
|
|
386
|
+
* @param {string} page - Page name
|
|
387
|
+
* @returns {Object|null} Page component
|
|
388
|
+
*/
|
|
389
|
+
getPageComponent(page) {
|
|
390
|
+
return this.components.pages[page] || null;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Check if page is loaded
|
|
395
|
+
* @param {string} page - Page name
|
|
396
|
+
* @returns {boolean} Whether page is loaded
|
|
397
|
+
*/
|
|
398
|
+
isPageLoaded(page) {
|
|
399
|
+
return !!this.components.pages[page];
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Destroy application
|
|
404
|
+
*/
|
|
405
|
+
destroy() {
|
|
406
|
+
// Cleanup pages
|
|
407
|
+
Object.values(this.components.pages).forEach(page => {
|
|
408
|
+
if (page.destroy) {
|
|
409
|
+
page.destroy();
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// Cleanup sidebar
|
|
414
|
+
if (this.components.sidebar && this.components.sidebar.destroy) {
|
|
415
|
+
this.components.sidebar.destroy();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Remove event listeners
|
|
419
|
+
window.removeEventListener('hashchange', this.handleNavigation);
|
|
420
|
+
window.removeEventListener('sidebar-toggle', this.handleSidebarToggle);
|
|
421
|
+
document.removeEventListener('keydown', this.handleKeyboardShortcuts);
|
|
422
|
+
|
|
423
|
+
this.isInitialized = false;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Export for module use
|
|
428
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
429
|
+
module.exports = App;
|
|
430
|
+
}
|
|
@@ -24,8 +24,8 @@ class Dashboard {
|
|
|
24
24
|
if (this.isInitialized) return;
|
|
25
25
|
|
|
26
26
|
try {
|
|
27
|
-
this.stateService.setLoading(true);
|
|
28
27
|
await this.render();
|
|
28
|
+
this.stateService.setLoading(true);
|
|
29
29
|
await this.initializeComponents();
|
|
30
30
|
await this.loadInitialData();
|
|
31
31
|
this.startPeriodicRefresh();
|
|
@@ -419,7 +419,18 @@ class Dashboard {
|
|
|
419
419
|
*/
|
|
420
420
|
updateLoadingState(isLoading) {
|
|
421
421
|
const loadingOverlay = this.container.querySelector('#loading-overlay');
|
|
422
|
-
loadingOverlay
|
|
422
|
+
if (loadingOverlay) {
|
|
423
|
+
loadingOverlay.style.display = isLoading ? 'flex' : 'none';
|
|
424
|
+
} else {
|
|
425
|
+
// If the DOM isn't ready yet, show a temporary loading state
|
|
426
|
+
if (isLoading && this.container) {
|
|
427
|
+
this.container.style.opacity = '0.5';
|
|
428
|
+
this.container.style.pointerEvents = 'none';
|
|
429
|
+
} else if (this.container) {
|
|
430
|
+
this.container.style.opacity = '1';
|
|
431
|
+
this.container.style.pointerEvents = 'auto';
|
|
432
|
+
}
|
|
433
|
+
}
|
|
423
434
|
}
|
|
424
435
|
|
|
425
436
|
/**
|
|
@@ -430,11 +441,16 @@ class Dashboard {
|
|
|
430
441
|
const errorBanner = this.container.querySelector('#error-banner');
|
|
431
442
|
const errorText = this.container.querySelector('.error-text');
|
|
432
443
|
|
|
433
|
-
if (
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
444
|
+
if (errorBanner && errorText) {
|
|
445
|
+
if (error) {
|
|
446
|
+
errorText.textContent = error.message || error;
|
|
447
|
+
errorBanner.style.display = 'flex';
|
|
448
|
+
} else {
|
|
449
|
+
errorBanner.style.display = 'none';
|
|
450
|
+
}
|
|
451
|
+
} else if (error) {
|
|
452
|
+
// If DOM isn't ready, log error for now
|
|
453
|
+
console.error('Dashboard error (DOM not ready):', error.message || error);
|
|
438
454
|
}
|
|
439
455
|
}
|
|
440
456
|
|