claude-code-templates 1.10.0 → 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.
Files changed (35) hide show
  1. package/README.md +6 -0
  2. package/bin/create-claude-config.js +1 -0
  3. package/package.json +2 -2
  4. package/src/analytics/core/ConversationAnalyzer.js +94 -20
  5. package/src/analytics/core/FileWatcher.js +146 -11
  6. package/src/analytics/data/DataCache.js +124 -19
  7. package/src/analytics/notifications/NotificationManager.js +37 -0
  8. package/src/analytics/notifications/WebSocketServer.js +1 -1
  9. package/src/analytics-web/FRONT_ARCHITECTURE.md +46 -0
  10. package/src/analytics-web/assets/js/{main.js → main.js.deprecated} +32 -3
  11. package/src/analytics-web/components/AgentsPage.js +2535 -0
  12. package/src/analytics-web/components/App.js +430 -0
  13. package/src/analytics-web/components/{Dashboard.js → Dashboard.js.deprecated} +23 -7
  14. package/src/analytics-web/components/DashboardPage.js +1527 -0
  15. package/src/analytics-web/components/Sidebar.js +197 -0
  16. package/src/analytics-web/components/ToolDisplay.js +539 -0
  17. package/src/analytics-web/index.html +3275 -1792
  18. package/src/analytics-web/services/DataService.js +89 -16
  19. package/src/analytics-web/services/StateService.js +9 -0
  20. package/src/analytics-web/services/WebSocketService.js +17 -5
  21. package/src/analytics.js +323 -35
  22. package/src/console-bridge.js +610 -0
  23. package/src/file-operations.js +143 -23
  24. package/src/hook-scanner.js +21 -1
  25. package/src/index.js +24 -1
  26. package/src/templates.js +28 -0
  27. package/src/test-console-bridge.js +67 -0
  28. package/src/utils.js +46 -0
  29. package/templates/ruby/.claude/commands/model.md +360 -0
  30. package/templates/ruby/.claude/commands/test.md +480 -0
  31. package/templates/ruby/.claude/settings.json +146 -0
  32. package/templates/ruby/.mcp.json +83 -0
  33. package/templates/ruby/CLAUDE.md +284 -0
  34. package/templates/ruby/examples/rails-app/.claude/commands/authentication.md +490 -0
  35. package/templates/ruby/examples/rails-app/CLAUDE.md +376 -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.style.display = isLoading ? 'flex' : 'none';
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 (error) {
434
- errorText.textContent = error.message || error;
435
- errorBanner.style.display = 'flex';
436
- } else {
437
- errorBanner.style.display = 'none';
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