@graphty/remote-logger 0.0.1 → 1.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 (138) hide show
  1. package/README.md +944 -28
  2. package/bin/remote-log-server.js +3 -0
  3. package/dist/client/RemoteLogClient.d.ts +116 -0
  4. package/dist/client/RemoteLogClient.d.ts.map +1 -0
  5. package/dist/client/RemoteLogClient.js +269 -0
  6. package/dist/client/RemoteLogClient.js.map +1 -0
  7. package/dist/client/index.d.ts +7 -0
  8. package/dist/client/index.d.ts.map +1 -0
  9. package/dist/client/index.js +6 -0
  10. package/dist/client/index.js.map +1 -0
  11. package/dist/client/types.d.ts +60 -0
  12. package/dist/client/types.d.ts.map +1 -0
  13. package/dist/client/types.js +6 -0
  14. package/dist/client/types.js.map +1 -0
  15. package/dist/index.d.ts +22 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +23 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/mcp/index.d.ts +9 -0
  20. package/dist/mcp/index.d.ts.map +1 -0
  21. package/dist/mcp/index.js +9 -0
  22. package/dist/mcp/index.js.map +1 -0
  23. package/dist/mcp/mcp-server.d.ts +32 -0
  24. package/dist/mcp/mcp-server.d.ts.map +1 -0
  25. package/dist/mcp/mcp-server.js +270 -0
  26. package/dist/mcp/mcp-server.js.map +1 -0
  27. package/dist/mcp/tools/index.d.ts +14 -0
  28. package/dist/mcp/tools/index.d.ts.map +1 -0
  29. package/dist/mcp/tools/index.js +14 -0
  30. package/dist/mcp/tools/index.js.map +1 -0
  31. package/dist/mcp/tools/logs-clear.d.ts +76 -0
  32. package/dist/mcp/tools/logs-clear.d.ts.map +1 -0
  33. package/dist/mcp/tools/logs-clear.js +58 -0
  34. package/dist/mcp/tools/logs-clear.js.map +1 -0
  35. package/dist/mcp/tools/logs-get-all.d.ts +60 -0
  36. package/dist/mcp/tools/logs-get-all.d.ts.map +1 -0
  37. package/dist/mcp/tools/logs-get-all.js +50 -0
  38. package/dist/mcp/tools/logs-get-all.js.map +1 -0
  39. package/dist/mcp/tools/logs-get-errors.d.ts +65 -0
  40. package/dist/mcp/tools/logs-get-errors.d.ts.map +1 -0
  41. package/dist/mcp/tools/logs-get-errors.js +46 -0
  42. package/dist/mcp/tools/logs-get-errors.js.map +1 -0
  43. package/dist/mcp/tools/logs-get-file-path.d.ts +75 -0
  44. package/dist/mcp/tools/logs-get-file-path.d.ts.map +1 -0
  45. package/dist/mcp/tools/logs-get-file-path.js +90 -0
  46. package/dist/mcp/tools/logs-get-file-path.js.map +1 -0
  47. package/dist/mcp/tools/logs-get-recent.d.ts +89 -0
  48. package/dist/mcp/tools/logs-get-recent.d.ts.map +1 -0
  49. package/dist/mcp/tools/logs-get-recent.js +74 -0
  50. package/dist/mcp/tools/logs-get-recent.js.map +1 -0
  51. package/dist/mcp/tools/logs-list-sessions.d.ts +64 -0
  52. package/dist/mcp/tools/logs-list-sessions.d.ts.map +1 -0
  53. package/dist/mcp/tools/logs-list-sessions.js +48 -0
  54. package/dist/mcp/tools/logs-list-sessions.js.map +1 -0
  55. package/dist/mcp/tools/logs-receive.d.ts +150 -0
  56. package/dist/mcp/tools/logs-receive.d.ts.map +1 -0
  57. package/dist/mcp/tools/logs-receive.js +68 -0
  58. package/dist/mcp/tools/logs-receive.js.map +1 -0
  59. package/dist/mcp/tools/logs-search.d.ts +91 -0
  60. package/dist/mcp/tools/logs-search.d.ts.map +1 -0
  61. package/dist/mcp/tools/logs-search.js +68 -0
  62. package/dist/mcp/tools/logs-search.js.map +1 -0
  63. package/dist/mcp/tools/logs-status.d.ts +45 -0
  64. package/dist/mcp/tools/logs-status.d.ts.map +1 -0
  65. package/dist/mcp/tools/logs-status.js +45 -0
  66. package/dist/mcp/tools/logs-status.js.map +1 -0
  67. package/dist/server/dual-server.d.ts +76 -0
  68. package/dist/server/dual-server.d.ts.map +1 -0
  69. package/dist/server/dual-server.js +214 -0
  70. package/dist/server/dual-server.js.map +1 -0
  71. package/dist/server/index.d.ts +12 -0
  72. package/dist/server/index.d.ts.map +1 -0
  73. package/dist/server/index.js +12 -0
  74. package/dist/server/index.js.map +1 -0
  75. package/dist/server/jsonl-writer.d.ts +93 -0
  76. package/dist/server/jsonl-writer.d.ts.map +1 -0
  77. package/dist/server/jsonl-writer.js +205 -0
  78. package/dist/server/jsonl-writer.js.map +1 -0
  79. package/dist/server/log-server.d.ts +126 -0
  80. package/dist/server/log-server.d.ts.map +1 -0
  81. package/dist/server/log-server.js +589 -0
  82. package/dist/server/log-server.js.map +1 -0
  83. package/dist/server/log-storage.d.ts +301 -0
  84. package/dist/server/log-storage.d.ts.map +1 -0
  85. package/dist/server/log-storage.js +408 -0
  86. package/dist/server/log-storage.js.map +1 -0
  87. package/dist/server/marker-utils.d.ts +69 -0
  88. package/dist/server/marker-utils.d.ts.map +1 -0
  89. package/dist/server/marker-utils.js +118 -0
  90. package/dist/server/marker-utils.js.map +1 -0
  91. package/dist/server/self-signed-cert.d.ts +30 -0
  92. package/dist/server/self-signed-cert.d.ts.map +1 -0
  93. package/dist/server/self-signed-cert.js +83 -0
  94. package/dist/server/self-signed-cert.js.map +1 -0
  95. package/dist/ui/ConsoleCaptureUI.d.ts +118 -0
  96. package/dist/ui/ConsoleCaptureUI.d.ts.map +1 -0
  97. package/dist/ui/ConsoleCaptureUI.js +571 -0
  98. package/dist/ui/ConsoleCaptureUI.js.map +1 -0
  99. package/dist/ui/index.d.ts +15 -0
  100. package/dist/ui/index.d.ts.map +1 -0
  101. package/dist/ui/index.js +15 -0
  102. package/dist/ui/index.js.map +1 -0
  103. package/dist/vite/index.d.ts +8 -0
  104. package/dist/vite/index.d.ts.map +1 -0
  105. package/dist/vite/index.js +8 -0
  106. package/dist/vite/index.js.map +1 -0
  107. package/dist/vite/plugin.d.ts +42 -0
  108. package/dist/vite/plugin.d.ts.map +1 -0
  109. package/dist/vite/plugin.js +46 -0
  110. package/dist/vite/plugin.js.map +1 -0
  111. package/package.json +90 -7
  112. package/src/client/RemoteLogClient.ts +328 -0
  113. package/src/client/index.ts +7 -0
  114. package/src/client/types.ts +62 -0
  115. package/src/index.ts +28 -0
  116. package/src/mcp/index.ts +25 -0
  117. package/src/mcp/mcp-server.ts +364 -0
  118. package/src/mcp/tools/index.ts +69 -0
  119. package/src/mcp/tools/logs-clear.ts +86 -0
  120. package/src/mcp/tools/logs-get-all.ts +78 -0
  121. package/src/mcp/tools/logs-get-errors.ts +71 -0
  122. package/src/mcp/tools/logs-get-file-path.ts +121 -0
  123. package/src/mcp/tools/logs-get-recent.ts +104 -0
  124. package/src/mcp/tools/logs-list-sessions.ts +71 -0
  125. package/src/mcp/tools/logs-receive.ts +96 -0
  126. package/src/mcp/tools/logs-search.ts +95 -0
  127. package/src/mcp/tools/logs-status.ts +69 -0
  128. package/src/server/dual-server.ts +308 -0
  129. package/src/server/index.ts +54 -0
  130. package/src/server/jsonl-writer.ts +277 -0
  131. package/src/server/log-server.ts +763 -0
  132. package/src/server/log-storage.ts +651 -0
  133. package/src/server/marker-utils.ts +144 -0
  134. package/src/server/self-signed-cert.ts +93 -0
  135. package/src/ui/ConsoleCaptureUI.ts +649 -0
  136. package/src/ui/index.ts +15 -0
  137. package/src/vite/index.ts +8 -0
  138. package/src/vite/plugin.ts +59 -0
@@ -0,0 +1,649 @@
1
+ /**
2
+ * ConsoleCaptureUI - Console capture with floating UI widget
3
+ *
4
+ * Intercepts console output (log, error, warn, info, debug) and provides
5
+ * a floating button UI with copy, download, view, and clear functionality.
6
+ *
7
+ * Features:
8
+ * - Floating button in top-right corner
9
+ * - Menu with Copy, Download, Clear, and Show Logs options
10
+ * - Works on mobile devices and in XR environments
11
+ * - Global window.__console__ methods for programmatic access
12
+ *
13
+ * Usage:
14
+ * import { initConsoleCaptureUI } from "@graphty/remote-logger/ui";
15
+ * initConsoleCaptureUI();
16
+ *
17
+ * Or programmatically:
18
+ * const ui = new ConsoleCaptureUI();
19
+ * console.log("This is captured");
20
+ * ui.destroy(); // Clean up when done
21
+ */
22
+
23
+ // Extended window interface for global console methods
24
+ declare global {
25
+ interface Window {
26
+ __console__?: {
27
+ copy: () => Promise<void>;
28
+ download: () => void;
29
+ clear: () => void;
30
+ get: () => string;
31
+ logs: CapturedLogEntry[];
32
+ };
33
+ }
34
+ }
35
+
36
+ /** A single captured log entry */
37
+ interface CapturedLogEntry {
38
+ type: string;
39
+ args: unknown[];
40
+ timestamp: string;
41
+ }
42
+
43
+ /** Console method names we intercept */
44
+ type ConsoleMethod = "log" | "error" | "warn" | "info" | "debug";
45
+
46
+ // Get reference to the global console object
47
+ const globalConsole = globalThis.console;
48
+
49
+ /**
50
+ * ConsoleCaptureUI class - captures console output and provides floating UI widget
51
+ */
52
+ export class ConsoleCaptureUI {
53
+ private logs: CapturedLogEntry[] = [];
54
+ private originalMethods: Record<ConsoleMethod, typeof globalConsole.log>;
55
+ private buttonContainer: HTMLElement | null = null;
56
+
57
+ /**
58
+ * Creates a new ConsoleCaptureUI instance that intercepts console methods
59
+ * and creates a floating UI widget
60
+ */
61
+ constructor() {
62
+ // Store original methods at construction time
63
+ this.originalMethods = {
64
+ log: globalConsole.log.bind(globalConsole),
65
+ error: globalConsole.error.bind(globalConsole),
66
+ warn: globalConsole.warn.bind(globalConsole),
67
+ info: globalConsole.info.bind(globalConsole),
68
+ debug: globalConsole.debug.bind(globalConsole),
69
+ };
70
+
71
+ this.interceptConsole();
72
+ this.createUI();
73
+ this.setupGlobalMethods();
74
+ }
75
+
76
+ /**
77
+ * Intercept all console methods to capture output
78
+ */
79
+ private interceptConsole(): void {
80
+ const methods: ConsoleMethod[] = [
81
+ "log",
82
+ "error",
83
+ "warn",
84
+ "info",
85
+ "debug",
86
+ ];
87
+ for (const method of methods) {
88
+ const original = this.originalMethods[method];
89
+
90
+ // Override the console method - this is intentional for this module
91
+ globalConsole[method] = (...args: unknown[]): void => {
92
+ // Capture the log entry
93
+ this.logs.push({
94
+ type: method,
95
+ args: args,
96
+ timestamp: new Date().toISOString(),
97
+ });
98
+ this.updateButtonBadge();
99
+ // Still call the original method
100
+ original.apply(globalConsole, args);
101
+ };
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Create the floating UI button and menu
107
+ */
108
+ private createUI(): void {
109
+ if (typeof document === "undefined") {
110
+ return;
111
+ }
112
+
113
+ // Create floating button container
114
+ this.buttonContainer = document.createElement("div");
115
+ this.buttonContainer.id = "console-capture-container";
116
+ this.buttonContainer.innerHTML = `
117
+ <button id="console-capture-btn" style="
118
+ position: fixed;
119
+ top: 20px;
120
+ right: 20px;
121
+ width: 32px;
122
+ height: 32px;
123
+ border-radius: 16px;
124
+ background: rgba(128, 128, 128, 0.3);
125
+ color: rgba(255, 255, 255, 0.8);
126
+ border: 1px solid rgba(255, 255, 255, 0.2);
127
+ cursor: pointer;
128
+ box-shadow: 0 1px 4px rgba(0,0,0,0.1);
129
+ z-index: 99999;
130
+ font-size: 14px;
131
+ display: flex;
132
+ align-items: center;
133
+ justify-content: center;
134
+ transition: all 0.2s;
135
+ opacity: 0.6;
136
+ ">📋</button>
137
+ <div id="console-capture-menu" style="
138
+ position: fixed;
139
+ top: 60px;
140
+ right: 20px;
141
+ background: white;
142
+ border-radius: 8px;
143
+ box-shadow: 0 2px 20px rgba(0,0,0,0.2);
144
+ padding: 10px;
145
+ z-index: 99998;
146
+ display: none;
147
+ ">
148
+ <button id="cc-copy" style="
149
+ display: block;
150
+ width: 100%;
151
+ padding: 8px 16px;
152
+ margin: 4px 0;
153
+ border: none;
154
+ background: #4CAF50;
155
+ color: white;
156
+ border-radius: 4px;
157
+ cursor: pointer;
158
+ ">📋 Copy Logs</button>
159
+ <button id="cc-download" style="
160
+ display: block;
161
+ width: 100%;
162
+ padding: 8px 16px;
163
+ margin: 4px 0;
164
+ border: none;
165
+ background: #FF9800;
166
+ color: white;
167
+ border-radius: 4px;
168
+ cursor: pointer;
169
+ ">💾 Download</button>
170
+ <button id="cc-clear" style="
171
+ display: block;
172
+ width: 100%;
173
+ padding: 8px 16px;
174
+ margin: 4px 0;
175
+ border: none;
176
+ background: #f44336;
177
+ color: white;
178
+ border-radius: 4px;
179
+ cursor: pointer;
180
+ ">🗑️ Clear</button>
181
+ <button id="cc-show" style="
182
+ display: block;
183
+ width: 100%;
184
+ padding: 8px 16px;
185
+ margin: 4px 0;
186
+ border: none;
187
+ background: #9C27B0;
188
+ color: white;
189
+ border-radius: 4px;
190
+ cursor: pointer;
191
+ ">👁️ Show Logs</button>
192
+ <div style="
193
+ margin-top: 10px;
194
+ padding-top: 10px;
195
+ border-top: 1px solid #ddd;
196
+ font-size: 12px;
197
+ color: #666;
198
+ text-align: center;
199
+ ">
200
+ <div>Logs: <span id="cc-count">0</span></div>
201
+ </div>
202
+ </div>
203
+ `;
204
+
205
+ document.body.appendChild(this.buttonContainer);
206
+
207
+ // Add event listeners
208
+ const btn = document.getElementById("console-capture-btn");
209
+ const menu = document.getElementById("console-capture-menu");
210
+
211
+ if (btn && menu) {
212
+ // Add hover effect to make button more visible on hover
213
+ btn.addEventListener("mouseenter", () => {
214
+ btn.style.opacity = "1";
215
+ btn.style.background = "rgba(128, 128, 128, 0.5)";
216
+ });
217
+
218
+ btn.addEventListener("mouseleave", () => {
219
+ btn.style.opacity = "0.6";
220
+ btn.style.background = "rgba(128, 128, 128, 0.3)";
221
+ });
222
+
223
+ btn.addEventListener("click", () => {
224
+ menu.style.display =
225
+ menu.style.display === "none" ? "block" : "none";
226
+ });
227
+
228
+ const copyBtn = document.getElementById("cc-copy");
229
+ const downloadBtn = document.getElementById("cc-download");
230
+ const clearBtn = document.getElementById("cc-clear");
231
+ const showBtn = document.getElementById("cc-show");
232
+
233
+ if (copyBtn) {
234
+ copyBtn.addEventListener("click", () => {
235
+ void this.copyLogs();
236
+ menu.style.display = "none";
237
+ });
238
+ }
239
+
240
+ if (downloadBtn) {
241
+ downloadBtn.addEventListener("click", () => {
242
+ this.downloadLogs();
243
+ menu.style.display = "none";
244
+ });
245
+ }
246
+
247
+ if (clearBtn) {
248
+ clearBtn.addEventListener("click", () => {
249
+ this.clearLogs();
250
+ menu.style.display = "none";
251
+ });
252
+ }
253
+
254
+ if (showBtn) {
255
+ showBtn.addEventListener("click", () => {
256
+ this.showLogsModal();
257
+ menu.style.display = "none";
258
+ });
259
+ }
260
+
261
+ // Close menu when clicking outside
262
+ document.addEventListener("click", (e) => {
263
+ if (
264
+ this.buttonContainer &&
265
+ !this.buttonContainer.contains(e.target as Node)
266
+ ) {
267
+ menu.style.display = "none";
268
+ }
269
+ });
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Update the log count badge in the menu
275
+ */
276
+ private updateButtonBadge(): void {
277
+ if (typeof document === "undefined") {
278
+ return;
279
+ }
280
+ const count = document.getElementById("cc-count");
281
+ if (count) {
282
+ count.textContent = String(this.logs.length);
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Format a single log entry as a string
288
+ * @param log - The log entry to format
289
+ * @returns Formatted log string
290
+ */
291
+ private formatLog(log: CapturedLogEntry): string {
292
+ const args = log.args
293
+ .map((arg) => {
294
+ if (typeof arg === "string") {
295
+ return arg;
296
+ }
297
+ if (arg instanceof Error) {
298
+ return `${arg.name}: ${arg.message}\n${arg.stack ?? ""}`;
299
+ }
300
+ try {
301
+ return JSON.stringify(arg, null, 2);
302
+ } catch {
303
+ return String(arg);
304
+ }
305
+ })
306
+ .join(" ");
307
+
308
+ return `[${log.timestamp}] [${log.type.toUpperCase()}] ${args}`;
309
+ }
310
+
311
+ /**
312
+ * Setup global window.__console__ methods for easy access
313
+ */
314
+ private setupGlobalMethods(): void {
315
+ if (typeof window !== "undefined") {
316
+ window.__console__ = {
317
+ copy: () => this.copyLogs(),
318
+ download: () => {
319
+ this.downloadLogs();
320
+ },
321
+ clear: () => {
322
+ this.clearLogs();
323
+ },
324
+ get: () => this.getLogs(),
325
+ logs: this.logs,
326
+ };
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Show success indicator on the button
332
+ */
333
+ private showCopySuccess(): void {
334
+ if (typeof document === "undefined") {
335
+ return;
336
+ }
337
+ const btn = document.getElementById("console-capture-btn");
338
+ if (btn) {
339
+ btn.innerHTML = "✅";
340
+ setTimeout(() => {
341
+ btn.innerHTML = "📋";
342
+ }, 1500);
343
+ }
344
+ globalConsole.log("📋 Console logs copied to clipboard!");
345
+ }
346
+
347
+ /**
348
+ * Show error indicator on the button and fallback message
349
+ */
350
+ private showCopyError(): void {
351
+ if (typeof document === "undefined") {
352
+ return;
353
+ }
354
+ const btn = document.getElementById("console-capture-btn");
355
+ if (btn) {
356
+ btn.innerHTML = "❌";
357
+ setTimeout(() => {
358
+ btn.innerHTML = "📋";
359
+ }, 1500);
360
+ }
361
+
362
+ // Show logs in console as fallback
363
+ globalConsole.log(
364
+ "❌ Failed to copy to clipboard. Here are your logs:"
365
+ );
366
+ globalConsole.log("═".repeat(50));
367
+ globalConsole.log(this.getLogs());
368
+ globalConsole.log("═".repeat(50));
369
+ }
370
+
371
+ /**
372
+ * Fallback copy method using textarea (for mobile devices)
373
+ * @param text - The text to copy
374
+ */
375
+ private copyUsingTextarea(text: string): void {
376
+ if (typeof document === "undefined") {
377
+ return;
378
+ }
379
+ // Create temporary textarea
380
+ const textarea = document.createElement("textarea");
381
+ textarea.value = text;
382
+ textarea.style.cssText =
383
+ "position: absolute; left: -9999px; top: -9999px;";
384
+ document.body.appendChild(textarea);
385
+
386
+ // Select and copy
387
+ textarea.select();
388
+ textarea.setSelectionRange(0, 99999); // For mobile devices
389
+
390
+ try {
391
+ // execCommand is deprecated but still necessary as a fallback for:
392
+ // - Older mobile browsers that don't support Clipboard API
393
+ // - iOS Safari in certain contexts where Clipboard API fails
394
+ // - XR/VR browsers with limited API support
395
+ // This provides the best compatibility across all target platforms
396
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- Required fallback for mobile/XR compatibility
397
+ const success = document.execCommand("copy");
398
+ if (success) {
399
+ this.showCopySuccess();
400
+ } else {
401
+ this.showCopyError();
402
+ }
403
+ } catch {
404
+ this.showCopyError();
405
+ } finally {
406
+ document.body.removeChild(textarea);
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Show a modal dialog with all captured logs
412
+ */
413
+ private showLogsModal(): void {
414
+ if (typeof document === "undefined") {
415
+ return;
416
+ }
417
+
418
+ // Remove existing modal if any
419
+ const existing = document.getElementById("console-logs-modal");
420
+ if (existing) {
421
+ existing.remove();
422
+ }
423
+
424
+ const modal = document.createElement("div");
425
+ modal.id = "console-logs-modal";
426
+ modal.innerHTML = `
427
+ <div style="
428
+ position: fixed;
429
+ top: 0;
430
+ left: 0;
431
+ right: 0;
432
+ bottom: 0;
433
+ background: rgba(0,0,0,0.5);
434
+ z-index: 100001;
435
+ display: flex;
436
+ align-items: center;
437
+ justify-content: center;
438
+ ">
439
+ <div style="
440
+ background: white;
441
+ border-radius: 8px;
442
+ width: 90%;
443
+ max-width: 800px;
444
+ height: 85vh;
445
+ max-height: 85vh;
446
+ display: flex;
447
+ flex-direction: column;
448
+ box-shadow: 0 4px 20px rgba(0,0,0,0.3);
449
+ ">
450
+ <div style="
451
+ padding: 16px;
452
+ border-bottom: 1px solid #ddd;
453
+ display: flex;
454
+ justify-content: space-between;
455
+ align-items: center;
456
+ ">
457
+ <h3 style="margin: 0; color: #333;">Console Logs</h3>
458
+ <button id="close-modal" style="
459
+ background: none;
460
+ border: none;
461
+ font-size: 24px;
462
+ cursor: pointer;
463
+ color: #666;
464
+ ">×</button>
465
+ </div>
466
+ <textarea id="logs-textarea" style="
467
+ flex: 1;
468
+ margin: 16px;
469
+ padding: 12px;
470
+ border: 1px solid #ddd;
471
+ border-radius: 4px;
472
+ font-family: monospace;
473
+ font-size: 12px;
474
+ resize: none;
475
+ background: #f5f5f5;
476
+ min-height: 50vh;
477
+ overflow-y: auto;
478
+ " readonly>${this.getLogs()}</textarea>
479
+ <div style="
480
+ padding: 16px;
481
+ border-top: 1px solid #ddd;
482
+ text-align: center;
483
+ ">
484
+ <button id="select-all-logs" style="
485
+ padding: 8px 24px;
486
+ background: #2196F3;
487
+ color: white;
488
+ border: none;
489
+ border-radius: 4px;
490
+ cursor: pointer;
491
+ font-size: 14px;
492
+ ">Select All</button>
493
+ <span style="
494
+ margin-left: 16px;
495
+ color: #666;
496
+ font-size: 14px;
497
+ ">Press Ctrl+C (or Cmd+C) to copy</span>
498
+ </div>
499
+ </div>
500
+ </div>
501
+ `;
502
+
503
+ document.body.appendChild(modal);
504
+
505
+ // Event listeners
506
+ const closeBtn = document.getElementById("close-modal");
507
+ if (closeBtn) {
508
+ closeBtn.addEventListener("click", () => {
509
+ modal.remove();
510
+ });
511
+ }
512
+
513
+ modal.addEventListener("click", (e) => {
514
+ if (e.target === modal.firstElementChild) {
515
+ modal.remove();
516
+ }
517
+ });
518
+
519
+ const textarea = document.getElementById(
520
+ "logs-textarea"
521
+ ) as HTMLTextAreaElement | null;
522
+ const selectAllBtn = document.getElementById("select-all-logs");
523
+
524
+ if (selectAllBtn && textarea) {
525
+ selectAllBtn.addEventListener("click", () => {
526
+ textarea.select();
527
+ textarea.focus();
528
+ });
529
+ }
530
+
531
+ // Auto-select on open
532
+ if (textarea) {
533
+ setTimeout(() => {
534
+ textarea.select();
535
+ textarea.focus();
536
+ }, 100);
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Get all captured logs as a formatted string
542
+ * @returns Formatted log string with timestamps and levels
543
+ */
544
+ getLogs(): string {
545
+ return this.logs.map((log) => this.formatLog(log)).join("\n");
546
+ }
547
+
548
+ /**
549
+ * Clear all captured logs
550
+ */
551
+ clearLogs(): void {
552
+ this.logs = [];
553
+ this.updateButtonBadge();
554
+ globalConsole.log("🗑️ Console logs cleared");
555
+ }
556
+
557
+ /**
558
+ * Copy logs to clipboard
559
+ */
560
+ async copyLogs(): Promise<void> {
561
+ const text = this.getLogs();
562
+
563
+ try {
564
+ // Try modern clipboard API first
565
+ if (
566
+ typeof navigator !== "undefined" &&
567
+ navigator.clipboard &&
568
+ navigator.clipboard.writeText
569
+ ) {
570
+ await navigator.clipboard.writeText(text);
571
+ this.showCopySuccess();
572
+ } else {
573
+ // Fallback method using textarea
574
+ this.copyUsingTextarea(text);
575
+ }
576
+ } catch {
577
+ // If modern API fails, try fallback
578
+ globalConsole.warn("Clipboard API failed, using fallback");
579
+ this.copyUsingTextarea(text);
580
+ }
581
+ }
582
+
583
+ /**
584
+ * Download logs as a text file
585
+ */
586
+ downloadLogs(): void {
587
+ if (typeof document === "undefined") {
588
+ return;
589
+ }
590
+
591
+ const text = this.getLogs();
592
+ const blob = new Blob([text], { type: "text/plain" });
593
+ const url = URL.createObjectURL(blob);
594
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
595
+ const filename = `console-logs-${timestamp}.txt`;
596
+
597
+ const link = document.createElement("a");
598
+ link.href = url;
599
+ link.download = filename;
600
+ document.body.appendChild(link);
601
+ link.click();
602
+ document.body.removeChild(link);
603
+
604
+ URL.revokeObjectURL(url);
605
+ globalConsole.log("💾 Console logs downloaded!");
606
+ }
607
+
608
+ /**
609
+ * Restore original console methods and clean up UI
610
+ */
611
+ destroy(): void {
612
+ // Restore original console methods
613
+ globalConsole.log = this.originalMethods.log;
614
+ globalConsole.error = this.originalMethods.error;
615
+ globalConsole.warn = this.originalMethods.warn;
616
+ globalConsole.info = this.originalMethods.info;
617
+ globalConsole.debug = this.originalMethods.debug;
618
+
619
+ // Remove UI elements
620
+ if (
621
+ typeof document !== "undefined" &&
622
+ this.buttonContainer &&
623
+ this.buttonContainer.parentNode
624
+ ) {
625
+ this.buttonContainer.parentNode.removeChild(this.buttonContainer);
626
+ }
627
+
628
+ // Remove modal if open
629
+ if (typeof document !== "undefined") {
630
+ const modal = document.getElementById("console-logs-modal");
631
+ if (modal) {
632
+ modal.remove();
633
+ }
634
+ }
635
+
636
+ // Remove global methods
637
+ if (typeof window !== "undefined") {
638
+ delete window.__console__;
639
+ }
640
+ }
641
+ }
642
+
643
+ /**
644
+ * Convenience function to initialize ConsoleCaptureUI
645
+ * @returns A new ConsoleCaptureUI instance
646
+ */
647
+ export function initConsoleCaptureUI(): ConsoleCaptureUI {
648
+ return new ConsoleCaptureUI();
649
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * UI components for console capture and display.
3
+ *
4
+ * Usage:
5
+ * import { initConsoleCaptureUI } from "@graphty/remote-logger/ui";
6
+ * initConsoleCaptureUI();
7
+ *
8
+ * Or access programmatically:
9
+ * import { ConsoleCaptureUI } from "@graphty/remote-logger/ui";
10
+ * const ui = new ConsoleCaptureUI();
11
+ * console.log("This will be captured");
12
+ * console.log(ui.getLogs());
13
+ */
14
+
15
+ export { ConsoleCaptureUI, initConsoleCaptureUI } from "./ConsoleCaptureUI.js";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Vite integration for remote-logger.
3
+ *
4
+ * Provides a Vite plugin for automatic project marker injection.
5
+ * @module vite
6
+ */
7
+
8
+ export { remoteLoggerPlugin } from "./plugin.js";