handhold-sdk 1.0.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 (50) hide show
  1. package/README.md +427 -0
  2. package/dist/d44f1fe8001e32f8e74d.svg +3056 -0
  3. package/dist/fonts/Phosphor.ttf +0 -0
  4. package/dist/fonts/Phosphor.woff +0 -0
  5. package/dist/fonts/Phosphor.woff2 +0 -0
  6. package/dist/handhold.min.js +1 -0
  7. package/package.json +114 -0
  8. package/src/HandHold.js +1599 -0
  9. package/src/agent/VENDOR.md +203 -0
  10. package/src/agent/agent/AgentLoop.js +366 -0
  11. package/src/agent/agent/prompts.js +163 -0
  12. package/src/agent/agent/tools.js +148 -0
  13. package/src/agent/controller.js +235 -0
  14. package/src/agent/dom/actions.js +456 -0
  15. package/src/agent/dom/domTree.js +1761 -0
  16. package/src/agent/dom/redact.js +98 -0
  17. package/src/agent/dom/serializer.js +332 -0
  18. package/src/agent/dom/utils.js +99 -0
  19. package/src/agent/index.js +169 -0
  20. package/src/agent/llm/connector.js +143 -0
  21. package/src/agent/package.json +3 -0
  22. package/src/agent/policy/PolicyGuard.js +172 -0
  23. package/src/agent/site/manifest.js +145 -0
  24. package/src/agent/ui/ChatWidget.js +219 -0
  25. package/src/agent-mode/AgentMode.js +429 -0
  26. package/src/api/APIClient.js +258 -0
  27. package/src/core/Config.js +207 -0
  28. package/src/core/UiState.js +83 -0
  29. package/src/core/defaults.js +20 -0
  30. package/src/events.js +59 -0
  31. package/src/index.js +77 -0
  32. package/src/intent/ActionVerbMap.js +324 -0
  33. package/src/intent/IntentExtractor.js +317 -0
  34. package/src/observers/ElementScanner.js +284 -0
  35. package/src/observers/NavigationObserver.js +182 -0
  36. package/src/observers/PageObserver.js +293 -0
  37. package/src/session/SessionManager.js +402 -0
  38. package/src/session/SessionStorage.js +148 -0
  39. package/src/ui/HelpWidget.js +1189 -0
  40. package/src/ui/UICoach.js +1725 -0
  41. package/src/ui/components/Button.css +137 -0
  42. package/src/ui/components/Button.js +102 -0
  43. package/src/ui/widget.css +599 -0
  44. package/src/utils/Logger.js +132 -0
  45. package/src/utils/MarkdownRenderer.js +39 -0
  46. package/src/utils/domUtils.js +350 -0
  47. package/src/utils/eventBus.js +102 -0
  48. package/src/utils/index.js +8 -0
  49. package/src/utils/stringUtils.js +202 -0
  50. package/types/index.d.ts +538 -0
@@ -0,0 +1,599 @@
1
+ #handhold-container {
2
+ /* Light Theme Variables (Default) */
3
+ --hh-bg: #FFFFFF;
4
+ --hh-fg: #0F172A;
5
+ --hh-muted: #64748B;
6
+ --hh-muted-fg: #64748B;
7
+ --hh-border: #E2E8F0;
8
+ --hh-input: #F8FAFC;
9
+ /* --hh-primary and --hh-primary-dark are set via inline style */
10
+ --hh-primary-fg: #ffffff;
11
+ --hh-secondary: #F1F5F9;
12
+ --hh-secondary-fg: #0F172A;
13
+ --hh-accent: #E2E8F0;
14
+ --hh-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.10), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
15
+ --hh-card-bg: #FFFFFF;
16
+ --hh-card-active-bg: #F1F5F9;
17
+ --hh-radius: 6px;
18
+ --hh-backdrop: blur(8px);
19
+ --hh-skeleton: #E2E8F0;
20
+
21
+ font-family: 'Manrope', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
22
+ z-index: var(--hh-z-index, 9999);
23
+ line-height: 1.5;
24
+ -webkit-font-smoothing: antialiased;
25
+ -moz-osx-font-smoothing: grayscale;
26
+ }
27
+
28
+ #handhold-container.dark {
29
+ --hh-bg: #1e293b;
30
+ --hh-fg: #f8fafc;
31
+ --hh-muted: #94a3b8;
32
+ --hh-muted-fg: #cbd5e1;
33
+ --hh-border: #334155;
34
+ --hh-input: #0f172a;
35
+ --hh-secondary: #334155;
36
+ --hh-secondary-fg: #f8fafc;
37
+ --hh-accent: #475569;
38
+ --hh-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
39
+ --hh-card-bg: #1e293b;
40
+ --hh-card-active-bg: #334155;
41
+ --hh-skeleton: #334155;
42
+ }
43
+
44
+ #handhold-container * {
45
+ box-sizing: border-box;
46
+ }
47
+
48
+ /* Help Button (FAB) */
49
+ .handhold-help-button {
50
+ position: fixed;
51
+ width: 56px;
52
+ height: 56px;
53
+ border-radius: 50%;
54
+ background: var(--hh-bg);
55
+ color: var(--hh-fg);
56
+ border: 2px solid var(--hh-border);
57
+ cursor: pointer;
58
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
59
+ z-index: var(--hh-z-index, 9999);
60
+ display: flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ font-size: 24px;
64
+ font-weight: 500;
65
+ transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
66
+ }
67
+
68
+ .handhold-help-button.position-bottom-right {
69
+ right: 32px;
70
+ bottom: 32px;
71
+ }
72
+ .handhold-help-button.position-bottom-left {
73
+ left: 32px;
74
+ bottom: 32px;
75
+ }
76
+ .handhold-help-button.position-top-right {
77
+ right: 32px;
78
+ top: 32px;
79
+ }
80
+ .handhold-help-button.position-top-left {
81
+ left: 32px;
82
+ top: 32px;
83
+ }
84
+
85
+ .handhold-help-button:hover {
86
+ transform: none;
87
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
88
+ color: var(--hh-primary);
89
+ border-color: var(--hh-primary);
90
+ }
91
+
92
+ .handhold-help-button:active {
93
+ transform: none;
94
+ }
95
+
96
+ .handhold-help-button.hidden {
97
+ transform: scale(0) rotate(90deg);
98
+ opacity: 0;
99
+ pointer-events: none;
100
+ }
101
+
102
+ /* Widget Panel */
103
+ .handhold-widget {
104
+ position: fixed;
105
+ width: 380px;
106
+ max-height: calc(100vh - 124px);
107
+ background: var(--hh-bg);
108
+ color: var(--hh-fg);
109
+ border-radius: var(--hh-radius);
110
+ border: 1px solid var(--hh-border);
111
+ box-shadow: var(--hh-shadow);
112
+ z-index: calc(var(--hh-z-index, 9999) + 1);
113
+ overflow: hidden;
114
+ display: flex;
115
+ flex-direction: column;
116
+ opacity: 0;
117
+ transform: translateY(10px) scale(0.98);
118
+ transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
119
+ pointer-events: none;
120
+ }
121
+
122
+ .handhold-widget.position-bottom-right {
123
+ right: 20px;
124
+ bottom: 24px;
125
+ }
126
+ .handhold-widget.position-bottom-left {
127
+ left: 20px;
128
+ bottom: 24px;
129
+ }
130
+ .handhold-widget.position-top-right {
131
+ right: 20px;
132
+ top: 24px;
133
+ }
134
+ .handhold-widget.position-top-left {
135
+ left: 20px;
136
+ top: 24px;
137
+ }
138
+
139
+ .handhold-widget.open {
140
+ opacity: 1;
141
+ transform: translateY(0) scale(1);
142
+ pointer-events: auto;
143
+ }
144
+
145
+ /* Widget Header */
146
+ .handhold-widget-header {
147
+ display: flex;
148
+ align-items: center;
149
+ justify-content: space-between;
150
+ padding: 20px 24px 16px;
151
+ background: transparent;
152
+ border-bottom: 1px solid transparent;
153
+ }
154
+
155
+ .handhold-widget-title {
156
+ font-size: 18px;
157
+ font-weight: 700;
158
+ margin: 0;
159
+ color: var(--hh-fg);
160
+ }
161
+
162
+ .handhold-header-actions {
163
+ display: flex;
164
+ align-items: center;
165
+ gap: 8px;
166
+ }
167
+
168
+ .handhold-widget-close {
169
+ width: 56px;
170
+ height: 56px;
171
+ border-radius: 6px;
172
+ border: none;
173
+ background: transparent;
174
+ cursor: pointer;
175
+ display: flex;
176
+ align-items: center;
177
+ justify-content: center;
178
+ color: var(--hh-muted);
179
+ transition: all 0.2s;
180
+ padding: 0;
181
+ }
182
+
183
+ .handhold-widget-close:hover {
184
+ background: var(--hh-secondary);
185
+ color: var(--hh-fg);
186
+ }
187
+
188
+ /* Widget Body */
189
+ .handhold-widget-body {
190
+ flex: 1;
191
+ overflow-y: auto;
192
+ padding: 0 24px 24px;
193
+ scrollbar-width: thin;
194
+ scrollbar-color: var(--hh-muted) transparent;
195
+ }
196
+
197
+ .handhold-widget-body::-webkit-scrollbar {
198
+ width: 4px;
199
+ }
200
+ .handhold-widget-body::-webkit-scrollbar-track {
201
+ background: transparent;
202
+ }
203
+ .handhold-widget-body::-webkit-scrollbar-thumb {
204
+ background: var(--hh-muted);
205
+ border-radius: 2px;
206
+ }
207
+
208
+ /* Search Input */
209
+ .handhold-search-container {
210
+ margin-bottom: 24px;
211
+ }
212
+
213
+ .handhold-search-label {
214
+ display: block;
215
+ font-size: 14px;
216
+ font-weight: 600;
217
+ margin-bottom: 8px;
218
+ color: var(--hh-fg);
219
+ }
220
+
221
+ .handhold-input-wrapper {
222
+ position: relative;
223
+ display: flex;
224
+ align-items: center;
225
+ }
226
+
227
+ .handhold-search-input {
228
+ width: 100%;
229
+ padding: 12px 16px;
230
+ padding-right: 48px;
231
+ border: 2px solid var(--hh-border);
232
+ border-radius: 8px;
233
+ background: var(--hh-input);
234
+ color: var(--hh-fg);
235
+ font-size: 15px;
236
+ transition: all 0.2s;
237
+ outline: none;
238
+ }
239
+
240
+ .handhold-search-input:focus {
241
+ border-color: var(--hh-primary);
242
+ background: var(--hh-bg);
243
+ box-shadow: 0 0 0 3px rgba(var(--hh-primary), 0.1);
244
+ }
245
+
246
+ .handhold-send-btn {
247
+ position: absolute;
248
+ right: 6px;
249
+ top: 6px;
250
+ bottom: 6px;
251
+ width: 36px;
252
+ padding: 0;
253
+ display: flex;
254
+ align-items: center;
255
+ justify-content: center;
256
+ border: none;
257
+ background: transparent;
258
+ color: var(--hh-primary);
259
+ border-radius: 6px;
260
+ cursor: pointer;
261
+ transition: all 0.2s;
262
+ }
263
+
264
+ .handhold-send-btn:hover {
265
+ background: var(--hh-secondary);
266
+ }
267
+
268
+ /* Intent Menu */
269
+ .handhold-section-title {
270
+ font-size: 13px;
271
+ text-transform: uppercase;
272
+ letter-spacing: 0.5px;
273
+ color: var(--hh-muted);
274
+ margin: 0 0 12px;
275
+ font-weight: 700;
276
+ }
277
+
278
+ .handhold-intent-item {
279
+ display: flex;
280
+ align-items: center;
281
+ justify-content: space-between;
282
+ width: 100%;
283
+ padding: 12px 16px;
284
+ margin-bottom: 8px;
285
+ border: 1px solid var(--hh-border);
286
+ border-radius: 8px;
287
+ background: var(--hh-card-bg);
288
+ color: var(--hh-fg);
289
+ font-size: 15px;
290
+ font-weight: 500;
291
+ cursor: pointer;
292
+ transition: all 0.2s;
293
+ text-align: left;
294
+ }
295
+
296
+ .handhold-btn-text {
297
+ flex-shrink: 0;
298
+ margin-left: 12px;
299
+ font-size: 12px;
300
+ font-weight: 600;
301
+ color: var(--hh-primary);
302
+ opacity: 0.7;
303
+ text-transform: uppercase;
304
+ letter-spacing: 0.5px;
305
+ }
306
+
307
+ .handhold-intent-item:hover {
308
+ background: var(--hh-card-active-bg);
309
+ border-color: var(--hh-primary);
310
+ transform: translateX(4px);
311
+ }
312
+
313
+ .handhold-divider {
314
+ height: 1px;
315
+ background: var(--hh-border);
316
+ margin: 24px 0;
317
+ }
318
+
319
+ .handhold-contact-support {
320
+ justify-content: center;
321
+ gap: 8px;
322
+ background: transparent;
323
+ border-style: dashed;
324
+ }
325
+
326
+ /* Loading State */
327
+ .handhold-loading {
328
+ display: flex;
329
+ flex-direction: column;
330
+ align-items: center;
331
+ justify-content: center;
332
+ padding: 40px 0;
333
+ color: var(--hh-muted);
334
+ text-align: center;
335
+ }
336
+
337
+ /* Skeleton Loading */
338
+ .handhold-skeleton-container {
339
+ display: flex;
340
+ flex-direction: column;
341
+ gap: 16px;
342
+ padding: 8px 0;
343
+ }
344
+
345
+ .handhold-skeleton {
346
+ background-color: var(--hh-skeleton);
347
+ border-radius: 4px;
348
+ animation: handhold-pulse-bg 1.5s infinite ease-in-out;
349
+ }
350
+
351
+ @keyframes handhold-pulse-bg {
352
+ 0% { opacity: 0.6; }
353
+ 50% { opacity: 1; }
354
+ 100% { opacity: 0.6; }
355
+ }
356
+
357
+ .handhold-skeleton.title {
358
+ height: 24px;
359
+ width: 60%;
360
+ margin-bottom: 8px;
361
+ }
362
+
363
+ .handhold-skeleton.text {
364
+ height: 16px;
365
+ width: 100%;
366
+ }
367
+
368
+ .handhold-skeleton.text.short {
369
+ width: 70%;
370
+ }
371
+
372
+ .handhold-skeleton.card {
373
+ height: 80px;
374
+ width: 100%;
375
+ border-radius: 8px;
376
+ }
377
+
378
+ /* Guidance View */
379
+ .handhold-guidance {
380
+ display: flex;
381
+ flex-direction: column;
382
+ gap: 16px;
383
+ }
384
+
385
+ .handhold-progress {
386
+ display: flex;
387
+ align-items: center;
388
+ gap: 12px;
389
+ margin-bottom: 8px;
390
+ }
391
+
392
+ .handhold-progress-bar {
393
+ flex: 1;
394
+ height: 6px;
395
+ background: var(--hh-secondary);
396
+ border-radius: 3px;
397
+ overflow: hidden;
398
+ }
399
+
400
+ .handhold-progress-fill {
401
+ height: 100%;
402
+ background: var(--hh-primary);
403
+ border-radius: 3px;
404
+ transition: width 0.3s ease;
405
+ }
406
+
407
+ .handhold-progress-text {
408
+ font-size: 13px;
409
+ font-weight: 600;
410
+ color: var(--hh-muted);
411
+ }
412
+
413
+ .handhold-guidance-summary {
414
+ font-size: 15px;
415
+ line-height: 1.5;
416
+ color: var(--hh-fg);
417
+ margin-bottom: 16px;
418
+ }
419
+
420
+ .handhold-step {
421
+ position: relative;
422
+ padding-left: 24px;
423
+ margin-bottom: 16px;
424
+ }
425
+
426
+ .handhold-step::before {
427
+ content: '';
428
+ position: absolute;
429
+ left: 7px;
430
+ top: 24px;
431
+ bottom: -24px;
432
+ width: 2px;
433
+ background: var(--hh-border);
434
+ }
435
+
436
+ .handhold-step:last-child::before {
437
+ display: none;
438
+ }
439
+
440
+ .handhold-step-card {
441
+ border: 1px solid var(--hh-border);
442
+ border-radius: 8px;
443
+ background: var(--hh-card-bg);
444
+ padding: 16px;
445
+ transition: all 0.2s;
446
+ }
447
+
448
+ .handhold-step.active .handhold-step-card {
449
+ border-color: var(--hh-primary);
450
+ box-shadow: 0 4px 12px -2px rgba(var(--hh-primary-rgb), 0.1);
451
+ }
452
+
453
+ .handhold-step.completed .handhold-step-card {
454
+ background: var(--hh-secondary);
455
+ opacity: 0.8;
456
+ }
457
+
458
+ .handhold-step-content {
459
+ display: flex;
460
+ gap: 12px;
461
+ }
462
+
463
+ .handhold-step-number {
464
+ width: 24px;
465
+ height: 24px;
466
+ border-radius: 50%;
467
+ background: var(--hh-secondary);
468
+ color: var(--hh-muted-fg);
469
+ font-size: 12px;
470
+ font-weight: 700;
471
+ display: flex;
472
+ align-items: center;
473
+ justify-content: center;
474
+ flex-shrink: 0;
475
+ }
476
+
477
+ .handhold-step.active .handhold-step-number {
478
+ background: var(--hh-primary);
479
+ color: var(--hh-primary-fg);
480
+ }
481
+
482
+ .handhold-step.completed .handhold-step-number {
483
+ background: var(--color-green-500); /* Need to ensure green is available or use var */
484
+ background: #10B981;
485
+ color: white;
486
+ }
487
+
488
+ .handhold-step-text {
489
+ font-size: 15px;
490
+ line-height: 1.5;
491
+ color: var(--hh-fg);
492
+ }
493
+
494
+ .handhold-step-actions {
495
+ display: flex;
496
+ justify-content: flex-end;
497
+ gap: 8px;
498
+ margin-top: 12px;
499
+ }
500
+
501
+ /* Article View */
502
+ .handhold-article {
503
+ display: flex;
504
+ flex-direction: column;
505
+ gap: 16px;
506
+ }
507
+
508
+ .handhold-article-query {
509
+ font-size: 13px;
510
+ color: var(--hh-muted);
511
+ font-style: italic;
512
+ padding-bottom: 12px;
513
+ border-bottom: 1px solid var(--hh-border);
514
+ }
515
+
516
+ .handhold-article-title {
517
+ font-size: 20px;
518
+ font-weight: 700;
519
+ margin: 0;
520
+ color: var(--hh-fg);
521
+ line-height: 1.3;
522
+ }
523
+
524
+ .handhold-article-summary {
525
+ font-size: 16px;
526
+ line-height: 1.6;
527
+ color: var(--hh-fg);
528
+ padding: 16px;
529
+ background: var(--hh-secondary);
530
+ border-radius: 8px;
531
+ border-left: 4px solid var(--hh-primary);
532
+ }
533
+
534
+ .handhold-article-content {
535
+ font-size: 15px;
536
+ line-height: 1.6;
537
+ color: var(--hh-fg);
538
+ }
539
+
540
+ .handhold-article-content p {
541
+ margin-bottom: 16px;
542
+ }
543
+
544
+ .handhold-article-content ul {
545
+ padding-left: 20px;
546
+ margin-bottom: 16px;
547
+ }
548
+
549
+ .handhold-article-content li {
550
+ margin-bottom: 6px;
551
+ }
552
+
553
+ .handhold-article-actions {
554
+ margin-top: 24px;
555
+ padding-top: 16px;
556
+ border-top: 1px solid var(--hh-border);
557
+ }
558
+
559
+ /* Completion Screen */
560
+ .handhold-completion {
561
+ display: flex;
562
+ flex-direction: column;
563
+ align-items: center;
564
+ justify-content: center;
565
+ text-align: center;
566
+ padding: 32px 24px;
567
+ gap: 16px;
568
+ }
569
+
570
+ .handhold-completion-icon {
571
+ display: flex;
572
+ align-items: center;
573
+ justify-content: center;
574
+ width: 72px;
575
+ height: 72px;
576
+ border-radius: 50%;
577
+ background: #d1fae5;
578
+ color: #059669;
579
+ flex-shrink: 0;
580
+ }
581
+
582
+ .handhold-completion-title {
583
+ font-size: 20px;
584
+ font-weight: 700;
585
+ color: var(--hh-fg);
586
+ margin: 0;
587
+ }
588
+
589
+ .handhold-completion-message {
590
+ font-size: 14px;
591
+ color: var(--hh-muted);
592
+ line-height: 1.6;
593
+ margin: 0;
594
+ }
595
+
596
+ .handhold-completion-message strong {
597
+ color: var(--hh-fg);
598
+ font-weight: 600;
599
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Hand Hold SDK - Logger
3
+ * Configurable logging utility with log levels
4
+ */
5
+
6
+ const LOG_LEVELS = {
7
+ error: 0,
8
+ warn: 1,
9
+ info: 2,
10
+ debug: 3,
11
+ };
12
+
13
+ class Logger {
14
+ constructor(config = {}) {
15
+ this.prefix = config.prefix || '[HandHold]';
16
+ this.level = LOG_LEVELS[config.logLevel] ?? LOG_LEVELS.warn;
17
+ this.enabled = config.debug !== false;
18
+ }
19
+
20
+ /**
21
+ * Set log level
22
+ * @param {string} level - 'error', 'warn', 'info', 'debug'
23
+ */
24
+ setLevel(level) {
25
+ if (LOG_LEVELS[level] !== undefined) {
26
+ this.level = LOG_LEVELS[level];
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Enable/disable logging
32
+ * @param {boolean} enabled
33
+ */
34
+ setEnabled(enabled) {
35
+ this.enabled = enabled;
36
+ }
37
+
38
+ /**
39
+ * Format message with timestamp
40
+ */
41
+ _format(level, ...args) {
42
+ const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
43
+ return [`${this.prefix} [${timestamp}] [${level.toUpperCase()}]`, ...args];
44
+ }
45
+
46
+ /**
47
+ * Log error message (always shown if enabled)
48
+ */
49
+ error(...args) {
50
+ if (!this.enabled) return;
51
+ if (this.level >= LOG_LEVELS.error) {
52
+ console.error(...this._format('error', ...args));
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Log warning message
58
+ */
59
+ warn(...args) {
60
+ if (!this.enabled) return;
61
+ if (this.level >= LOG_LEVELS.warn) {
62
+ console.warn(...this._format('warn', ...args));
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Log info message
68
+ */
69
+ info(...args) {
70
+ if (!this.enabled) return;
71
+ if (this.level >= LOG_LEVELS.info) {
72
+ console.info(...this._format('info', ...args));
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Log debug message
78
+ */
79
+ debug(...args) {
80
+ if (!this.enabled) return;
81
+ if (this.level >= LOG_LEVELS.debug) {
82
+ console.log(...this._format('debug', ...args));
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Log with custom level
88
+ */
89
+ log(level, ...args) {
90
+ const method = this[level];
91
+ if (typeof method === 'function') {
92
+ method.call(this, ...args);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Group logging
98
+ */
99
+ group(label) {
100
+ if (!this.enabled) return;
101
+ console.group(`${this.prefix} ${label}`);
102
+ }
103
+
104
+ /**
105
+ * End group
106
+ */
107
+ groupEnd() {
108
+ if (!this.enabled) return;
109
+ console.groupEnd();
110
+ }
111
+
112
+ /**
113
+ * Time measurement start
114
+ */
115
+ time(label) {
116
+ if (!this.enabled) return;
117
+ console.time(`${this.prefix} ${label}`);
118
+ }
119
+
120
+ /**
121
+ * Time measurement end
122
+ */
123
+ timeEnd(label) {
124
+ if (!this.enabled) return;
125
+ console.timeEnd(`${this.prefix} ${label}`);
126
+ }
127
+ }
128
+
129
+ // Export singleton with default config
130
+ const logger = new Logger();
131
+ export default logger;
132
+ export { Logger, LOG_LEVELS };