@mp-consulting/homebridge-ai-kit 2.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 (118) hide show
  1. package/README.md +273 -0
  2. package/config.schema.json +131 -0
  3. package/dist/agent/run-agent.d.ts +61 -0
  4. package/dist/agent/run-agent.d.ts.map +1 -0
  5. package/dist/agent/run-agent.js +92 -0
  6. package/dist/agent/run-agent.js.map +1 -0
  7. package/dist/bin/homebridge-ai-kit.d.ts +3 -0
  8. package/dist/bin/homebridge-ai-kit.d.ts.map +1 -0
  9. package/dist/bin/homebridge-ai-kit.js +61 -0
  10. package/dist/bin/homebridge-ai-kit.js.map +1 -0
  11. package/dist/bin/homebridge-mcp-server.d.ts +3 -0
  12. package/dist/bin/homebridge-mcp-server.d.ts.map +1 -0
  13. package/dist/bin/homebridge-mcp-server.js +5 -0
  14. package/dist/bin/homebridge-mcp-server.js.map +1 -0
  15. package/dist/index.d.ts +9 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +12 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/mcp/create-server.d.ts +17 -0
  20. package/dist/mcp/create-server.d.ts.map +1 -0
  21. package/dist/mcp/create-server.js +42 -0
  22. package/dist/mcp/create-server.js.map +1 -0
  23. package/dist/mcp/homebridge-client.d.ts +105 -0
  24. package/dist/mcp/homebridge-client.d.ts.map +1 -0
  25. package/dist/mcp/homebridge-client.js +341 -0
  26. package/dist/mcp/homebridge-client.js.map +1 -0
  27. package/dist/mcp/http.d.ts +26 -0
  28. package/dist/mcp/http.d.ts.map +1 -0
  29. package/dist/mcp/http.js +124 -0
  30. package/dist/mcp/http.js.map +1 -0
  31. package/dist/mcp/index.d.ts +12 -0
  32. package/dist/mcp/index.d.ts.map +1 -0
  33. package/dist/mcp/index.js +7 -0
  34. package/dist/mcp/index.js.map +1 -0
  35. package/dist/mcp/live.d.ts +46 -0
  36. package/dist/mcp/live.d.ts.map +1 -0
  37. package/dist/mcp/live.js +105 -0
  38. package/dist/mcp/live.js.map +1 -0
  39. package/dist/mcp/prompts.d.ts +4 -0
  40. package/dist/mcp/prompts.d.ts.map +1 -0
  41. package/dist/mcp/prompts.js +21 -0
  42. package/dist/mcp/prompts.js.map +1 -0
  43. package/dist/mcp/regex-search.d.ts +11 -0
  44. package/dist/mcp/regex-search.d.ts.map +1 -0
  45. package/dist/mcp/regex-search.js +53 -0
  46. package/dist/mcp/regex-search.js.map +1 -0
  47. package/dist/mcp/resources.d.ts +16 -0
  48. package/dist/mcp/resources.d.ts.map +1 -0
  49. package/dist/mcp/resources.js +65 -0
  50. package/dist/mcp/resources.js.map +1 -0
  51. package/dist/mcp/stdio.d.ts +11 -0
  52. package/dist/mcp/stdio.d.ts.map +1 -0
  53. package/dist/mcp/stdio.js +56 -0
  54. package/dist/mcp/stdio.js.map +1 -0
  55. package/dist/mcp/tools/accessories.d.ts +15 -0
  56. package/dist/mcp/tools/accessories.d.ts.map +1 -0
  57. package/dist/mcp/tools/accessories.js +179 -0
  58. package/dist/mcp/tools/accessories.js.map +1 -0
  59. package/dist/mcp/tools/child-bridges.d.ts +3 -0
  60. package/dist/mcp/tools/child-bridges.d.ts.map +1 -0
  61. package/dist/mcp/tools/child-bridges.js +28 -0
  62. package/dist/mcp/tools/child-bridges.js.map +1 -0
  63. package/dist/mcp/tools/config.d.ts +7 -0
  64. package/dist/mcp/tools/config.d.ts.map +1 -0
  65. package/dist/mcp/tools/config.js +111 -0
  66. package/dist/mcp/tools/config.js.map +1 -0
  67. package/dist/mcp/tools/helpers.d.ts +31 -0
  68. package/dist/mcp/tools/helpers.d.ts.map +1 -0
  69. package/dist/mcp/tools/helpers.js +49 -0
  70. package/dist/mcp/tools/helpers.js.map +1 -0
  71. package/dist/mcp/tools/logs.d.ts +7 -0
  72. package/dist/mcp/tools/logs.d.ts.map +1 -0
  73. package/dist/mcp/tools/logs.js +122 -0
  74. package/dist/mcp/tools/logs.js.map +1 -0
  75. package/dist/mcp/tools/plugin-jobs.d.ts +11 -0
  76. package/dist/mcp/tools/plugin-jobs.d.ts.map +1 -0
  77. package/dist/mcp/tools/plugin-jobs.js +75 -0
  78. package/dist/mcp/tools/plugin-jobs.js.map +1 -0
  79. package/dist/mcp/tools/plugins.d.ts +3 -0
  80. package/dist/mcp/tools/plugins.d.ts.map +1 -0
  81. package/dist/mcp/tools/plugins.js +74 -0
  82. package/dist/mcp/tools/plugins.js.map +1 -0
  83. package/dist/mcp/tools/server.d.ts +3 -0
  84. package/dist/mcp/tools/server.d.ts.map +1 -0
  85. package/dist/mcp/tools/server.js +69 -0
  86. package/dist/mcp/tools/server.js.map +1 -0
  87. package/dist/mcp/tools/system.d.ts +3 -0
  88. package/dist/mcp/tools/system.d.ts.map +1 -0
  89. package/dist/mcp/tools/system.js +9 -0
  90. package/dist/mcp/tools/system.js.map +1 -0
  91. package/dist/mcp/types.d.ts +75 -0
  92. package/dist/mcp/types.d.ts.map +1 -0
  93. package/dist/mcp/types.js +2 -0
  94. package/dist/mcp/types.js.map +1 -0
  95. package/dist/plugin/index.d.ts +7 -0
  96. package/dist/plugin/index.d.ts.map +1 -0
  97. package/dist/plugin/index.js +5 -0
  98. package/dist/plugin/index.js.map +1 -0
  99. package/dist/plugin/platform.d.ts +28 -0
  100. package/dist/plugin/platform.d.ts.map +1 -0
  101. package/dist/plugin/platform.js +76 -0
  102. package/dist/plugin/platform.js.map +1 -0
  103. package/dist/plugin/snippets.d.ts +27 -0
  104. package/dist/plugin/snippets.d.ts.map +1 -0
  105. package/dist/plugin/snippets.js +33 -0
  106. package/dist/plugin/snippets.js.map +1 -0
  107. package/homebridge-ui/public/index.html +164 -0
  108. package/homebridge-ui/public/js/app.js +200 -0
  109. package/homebridge-ui/public/lib/ai.css +738 -0
  110. package/homebridge-ui/public/lib/bootstrap-icons.min.css +5 -0
  111. package/homebridge-ui/public/lib/bootstrap.bundle.min.js +6 -0
  112. package/homebridge-ui/public/lib/bootstrap.min.css +5 -0
  113. package/homebridge-ui/public/lib/fonts/bootstrap-icons.woff +0 -0
  114. package/homebridge-ui/public/lib/fonts/bootstrap-icons.woff2 +0 -0
  115. package/homebridge-ui/public/lib/kit.css +994 -0
  116. package/homebridge-ui/public/lib/kit.js +794 -0
  117. package/homebridge-ui/server.js +28 -0
  118. package/package.json +94 -0
@@ -0,0 +1,994 @@
1
+ /* @mp-consulting/homebridge-ui-kit v1.2.0 */
2
+
3
+ /* ── Design tokens ──────────────────────────────────────────────────────────
4
+ CSS custom properties used throughout the design system.
5
+ Override these in your plugin's styles.css to customize per-plugin.
6
+ Light values live on :root; dark values follow Bootstrap's data-bs-theme.
7
+ ────────────────────────────────────────────────────────────────────────── */
8
+
9
+ :root {
10
+ /* Brand */
11
+ --mp-primary: #4f46e5;
12
+ --mp-primary-rgb: 79, 70, 229;
13
+ --mp-primary-hover: #4338ca;
14
+ --mp-primary-text: #4f46e5; /* brand color for text; meets WCAG AA on the body bg */
15
+
16
+ /* Status */
17
+ --mp-status-online: #10b981;
18
+ --mp-status-online-glow: rgba(16, 185, 129, 0.35);
19
+ --mp-status-offline: #ef4444;
20
+ --mp-status-checking: #94a3b8;
21
+
22
+ /* Surfaces */
23
+ --mp-surface: rgba(0, 0, 0, 0.02);
24
+ --mp-border: rgba(0, 0, 0, 0.1);
25
+
26
+ /* Shape */
27
+ --mp-radius: 0.5rem;
28
+ --mp-shadow-hover: 0 0.5rem 1rem rgba(0, 0, 0, 0.12);
29
+
30
+ /* Assistant (AI) halo palette. Only ever used for borders, glows and
31
+ decorative dots: text never sits on the gradient. */
32
+ --mp-ai-1: #BC82F3;
33
+ --mp-ai-2: #F5B9EA;
34
+ --mp-ai-3: #8D9FFF;
35
+ --mp-ai-4: #FF6778;
36
+ --mp-ai-5: #FFBA71;
37
+ --mp-ai-6: #C686FF;
38
+ --mp-ai-stops: var(--mp-ai-1), var(--mp-ai-2), var(--mp-ai-3), var(--mp-ai-4),
39
+ var(--mp-ai-5), var(--mp-ai-6), var(--mp-ai-1);
40
+ /* Halo gradient. Custom properties resolve where they are declared, so this
41
+ token is static (angle 0) when used as-is; the AI components rebuild it
42
+ from --mp-ai-stops on the animated element so it rotates (see ai.css). */
43
+ --mp-ai-gradient: conic-gradient(from var(--mp-ai-angle), var(--mp-ai-stops));
44
+ --mp-ai-gradient-linear: linear-gradient(90deg, var(--mp-ai-stops));
45
+ --mp-ai-text: #7e22ce; /* accent text; 7.1:1 on white */
46
+ --mp-ai-surface: var(--bs-body-bg, #fff);
47
+ /* Colours the AI components take from the host. They default to Bootstrap's,
48
+ and are repeated in the dark block so they resolve on the element that
49
+ switches theme. A host with its own theming (Glass UI) overrides them. */
50
+ --mp-ai-fg: var(--bs-body-color, #212529);
51
+ --mp-ai-fg-muted: var(--bs-secondary-color, #6c757d);
52
+ --mp-ai-bg: var(--bs-body-bg, transparent);
53
+ --mp-ai-border-color: var(--bs-border-color, #dee2e6);
54
+ --mp-ai-success-text: var(--bs-success-text-emphasis, #0a3622);
55
+ --mp-ai-danger-text: var(--bs-danger-text-emphasis, #b02a37);
56
+ --mp-ai-border-width: 2px;
57
+ --mp-ai-glow-blur: 12px;
58
+ --mp-ai-glow-opacity: 0.45;
59
+ --mp-ai-edge-glow-opacity: 0.55;
60
+ --mp-ai-duration: 4s;
61
+ --mp-ai-edge-width: 28px;
62
+
63
+ /* Assistant diff lines (text/background pairs meet WCAG AA) */
64
+ --mp-ai-diff-add-bg: #dcfce7;
65
+ --mp-ai-diff-add-text: #14532d;
66
+ --mp-ai-diff-del-bg: #fee2e2;
67
+ --mp-ai-diff-del-text: #7f1d1d;
68
+ }
69
+
70
+ /* Animatable angle for the rotating halo. Browsers without @property simply
71
+ show a static gradient. */
72
+ @property --mp-ai-angle {
73
+ syntax: '<angle>';
74
+ inherits: false;
75
+ initial-value: 0deg;
76
+ }
77
+
78
+ [data-bs-theme="dark"] {
79
+ --mp-primary-text: #818cf8;
80
+ --mp-surface: rgba(255, 255, 255, 0.03);
81
+ --mp-border: rgba(255, 255, 255, 0.08);
82
+ --mp-shadow-hover: 0 0.5rem 1rem rgba(0, 0, 0, 0.3);
83
+ --mp-ai-text: #d8b4fe; /* 9.6:1 on #212529 */
84
+ --mp-ai-surface: var(--bs-body-bg, #212529);
85
+ --mp-ai-fg: var(--bs-body-color, #dee2e6);
86
+ --mp-ai-fg-muted: var(--bs-secondary-color, rgba(222, 226, 230, 0.75));
87
+ --mp-ai-bg: var(--bs-body-bg, transparent);
88
+ --mp-ai-border-color: var(--bs-border-color, #495057);
89
+ --mp-ai-success-text: var(--bs-success-text-emphasis, #75b798);
90
+ --mp-ai-danger-text: var(--bs-danger-text-emphasis, #ea868f);
91
+ --mp-ai-glow-opacity: 0.7;
92
+ --mp-ai-edge-glow-opacity: 0.8;
93
+ --mp-ai-diff-add-bg: rgba(34, 197, 94, 0.16);
94
+ --mp-ai-diff-add-text: #bbf7d0;
95
+ --mp-ai-diff-del-bg: rgba(239, 68, 68, 0.18);
96
+ --mp-ai-diff-del-text: #fecaca;
97
+ }
98
+
99
+ /* ── Bootstrap 5 overrides ──────────────────────────────────────────────────
100
+ Remap Bootstrap components to use brand tokens.
101
+ ────────────────────────────────────────────────────────────────────────── */
102
+
103
+ /* Primary button */
104
+ .btn-primary {
105
+ --bs-btn-bg: var(--mp-primary);
106
+ --bs-btn-border-color: var(--mp-primary);
107
+ --bs-btn-hover-bg: var(--mp-primary-hover);
108
+ --bs-btn-hover-border-color: var(--mp-primary-hover);
109
+ --bs-btn-active-bg: var(--mp-primary-hover);
110
+ --bs-btn-active-border-color: var(--mp-primary-hover);
111
+ --bs-btn-focus-shadow-rgb: var(--mp-primary-rgb);
112
+ --bs-btn-disabled-bg: var(--mp-primary);
113
+ --bs-btn-disabled-border-color: var(--mp-primary);
114
+ }
115
+
116
+ /* Primary spinner */
117
+ .spinner-border.text-primary,
118
+ .spinner-grow.text-primary {
119
+ color: var(--mp-primary) !important;
120
+ }
121
+
122
+ /* ── Components ─────────────────────────────────────────────────────────────
123
+ Reusable UI components used across all Homebridge plugins.
124
+ ────────────────────────────────────────────────────────────────────────── */
125
+
126
+ /* ── Plugin header ──────────────────────────────────────── */
127
+
128
+ .mp-header {
129
+ display: flex;
130
+ align-items: center;
131
+ gap: 0.875rem;
132
+ margin-bottom: 0.5rem;
133
+ }
134
+
135
+ .mp-header-icon {
136
+ font-size: 2rem;
137
+ flex-shrink: 0;
138
+ line-height: 1;
139
+ }
140
+
141
+ .mp-header-title {
142
+ font-size: 1.375rem;
143
+ font-weight: 600;
144
+ margin: 0;
145
+ line-height: 1.3;
146
+ }
147
+
148
+ .mp-header-subtitle {
149
+ font-size: 0.875rem;
150
+ color: var(--bs-secondary-color);
151
+ margin: 0;
152
+ }
153
+
154
+ /* ── Status indicator dot ───────────────────────────────── */
155
+
156
+ .mp-status {
157
+ width: 8px;
158
+ height: 8px;
159
+ border-radius: 50%;
160
+ display: inline-block;
161
+ flex-shrink: 0;
162
+ vertical-align: middle;
163
+ }
164
+
165
+ .mp-status-online {
166
+ background-color: var(--mp-status-online);
167
+ box-shadow: 0 0 5px var(--mp-status-online-glow);
168
+ }
169
+
170
+ .mp-status-offline {
171
+ background-color: var(--mp-status-offline);
172
+ }
173
+
174
+ .mp-status-checking {
175
+ background-color: var(--mp-status-checking);
176
+ animation: mp-pulse 1.5s ease-in-out infinite;
177
+ }
178
+
179
+ @keyframes mp-pulse {
180
+ 0%, 100% { opacity: 1; }
181
+ 50% { opacity: 0.35; }
182
+ }
183
+
184
+ /* ── Device card ────────────────────────────────────────── */
185
+
186
+ .mp-device-card {
187
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
188
+ cursor: pointer;
189
+ }
190
+
191
+ .mp-device-card:hover {
192
+ transform: translateY(-1px);
193
+ box-shadow: var(--mp-shadow-hover) !important;
194
+ }
195
+
196
+ .mp-device-card:focus-visible {
197
+ outline: 2px solid var(--mp-primary);
198
+ outline-offset: 2px;
199
+ }
200
+
201
+ /* ── Settings surface card ──────────────────────────────── */
202
+
203
+ .mp-settings-card {
204
+ background: var(--mp-surface);
205
+ border: 1px solid var(--mp-border) !important;
206
+ border-radius: var(--mp-radius);
207
+ }
208
+
209
+ /* ── Empty state ────────────────────────────────────────── */
210
+
211
+ .mp-empty-state {
212
+ display: flex;
213
+ flex-direction: column;
214
+ align-items: center;
215
+ justify-content: center;
216
+ padding: 3rem 1.5rem;
217
+ text-align: center;
218
+ }
219
+
220
+ .mp-empty-state-icon {
221
+ font-size: 2.5rem;
222
+ color: var(--bs-secondary-color);
223
+ margin-bottom: 0.75rem;
224
+ line-height: 1;
225
+ }
226
+
227
+ .mp-empty-state-title {
228
+ font-size: 0.9375rem;
229
+ font-weight: 500;
230
+ color: var(--bs-secondary-color);
231
+ margin: 0 0 0.25rem;
232
+ }
233
+
234
+ .mp-empty-state-hint {
235
+ font-size: 0.8125rem;
236
+ color: var(--bs-secondary-color);
237
+ margin: 0;
238
+ opacity: 0.7;
239
+ }
240
+
241
+ /* ── Loading state ──────────────────────────────────────── */
242
+
243
+ .mp-loading {
244
+ display: flex;
245
+ flex-direction: column;
246
+ align-items: center;
247
+ justify-content: center;
248
+ padding: 2.5rem 1.5rem;
249
+ gap: 0.75rem;
250
+ color: var(--bs-secondary-color);
251
+ font-size: 0.875rem;
252
+ }
253
+
254
+ /* ── Nav tabs ───────────────────────────────────────────── */
255
+
256
+ .mp-tabs {
257
+ border-bottom: 1px solid var(--mp-border);
258
+ }
259
+
260
+ .mp-tabs .nav-link {
261
+ color: var(--bs-secondary-color);
262
+ border: none;
263
+ border-bottom: 2px solid transparent; /* reserve space so the active tab doesn't shift */
264
+ padding: 0.75rem 1.25rem;
265
+ font-weight: 500;
266
+ }
267
+
268
+ .mp-tabs .nav-link:hover {
269
+ color: var(--bs-body-color);
270
+ border-color: transparent;
271
+ }
272
+
273
+ .mp-tabs .nav-link.active {
274
+ color: var(--mp-primary-text);
275
+ background: transparent;
276
+ border-bottom-color: var(--mp-primary);
277
+ }
278
+
279
+ /* ── Metadata label ─────────────────────────────────────── */
280
+
281
+ .mp-label {
282
+ font-size: 0.75rem;
283
+ text-transform: uppercase;
284
+ letter-spacing: 0.5px;
285
+ color: var(--bs-secondary-color);
286
+ }
287
+
288
+ /* ── View toggling ──────────────────────────────────────── */
289
+
290
+ .mp-view {
291
+ display: none;
292
+ }
293
+
294
+ .mp-view.active {
295
+ display: block;
296
+ }
297
+
298
+ /* ── Support footer ─────────────────────────────────────── */
299
+
300
+ .mp-footer {
301
+ display: flex;
302
+ align-items: center;
303
+ justify-content: center;
304
+ flex-wrap: wrap;
305
+ gap: 0.75rem 1rem;
306
+ padding-top: 1.5rem;
307
+ margin-top: 2rem;
308
+ border-top: 1px solid var(--mp-border);
309
+ font-size: 0.8125rem;
310
+ color: var(--bs-secondary-color);
311
+ }
312
+
313
+ .mp-footer a {
314
+ color: var(--bs-secondary-color);
315
+ text-decoration: none;
316
+ display: inline-flex;
317
+ align-items: center;
318
+ gap: 0.3rem;
319
+ transition: color 0.15s ease;
320
+ }
321
+
322
+ .mp-footer a:hover {
323
+ color: var(--mp-primary-text);
324
+ }
325
+
326
+ .mp-footer-icon {
327
+ width: 1em;
328
+ height: 1em;
329
+ flex-shrink: 0;
330
+ fill: currentColor;
331
+ }
332
+
333
+ .mp-footer-sep {
334
+ color: var(--mp-border);
335
+ user-select: none;
336
+ }
337
+
338
+ /* ── Reduced motion ─────────────────────────────────────── */
339
+
340
+ @media (prefers-reduced-motion: reduce) {
341
+ .mp-status-checking {
342
+ animation: none;
343
+ }
344
+
345
+ .mp-device-card,
346
+ .mp-footer a {
347
+ transition: none;
348
+ }
349
+
350
+ .mp-device-card:hover {
351
+ transform: none;
352
+ }
353
+ }
354
+
355
+ /* ── Assistant (AI) components ──────────────────────────────────────────────
356
+ Halo-styled surfaces for AI features. Text always sits on a neutral surface
357
+ (--mp-ai-surface); the gradient is only used for rings, glows and dots.
358
+ Bootstrap is optional: every colour has a fallback, so this file also works
359
+ standalone as dist/ai.css (with the tokens).
360
+ ────────────────────────────────────────────────────────────────────────── */
361
+
362
+ @keyframes mp-ai-spin {
363
+ to { --mp-ai-angle: 360deg; }
364
+ }
365
+
366
+ @keyframes mp-ai-glow-pulse {
367
+ 0%, 100% { opacity: calc(var(--mp-ai-glow-opacity) * 0.35); }
368
+ 50% { opacity: var(--mp-ai-glow-opacity); }
369
+ }
370
+
371
+ @keyframes mp-ai-orb-pulse {
372
+ 0%, 100% { transform: scale(0.8); opacity: 0.65; }
373
+ 50% { transform: scale(1.1); opacity: 1; }
374
+ }
375
+
376
+ @keyframes mp-ai-blink {
377
+ 50% { opacity: 0; }
378
+ }
379
+
380
+ @keyframes mp-ai-fade-in {
381
+ from { opacity: 0; }
382
+ }
383
+
384
+ /* ── Shared halo ring ───────────────────────────────────── */
385
+ /* ::before paints the neutral surface plus a gradient border (padding-box /
386
+ border-box layers); ::after is the blurred glow behind it. Both sit below
387
+ the content inside the element's own stacking context. */
388
+
389
+ .mp-ai-halo,
390
+ .mp-ai-button,
391
+ .mp-ai-badge,
392
+ .mp-ai-panel {
393
+ position: relative;
394
+ isolation: isolate;
395
+ background: transparent;
396
+ }
397
+
398
+ .mp-ai-halo::before,
399
+ .mp-ai-button::before,
400
+ .mp-ai-badge::before,
401
+ .mp-ai-panel::before {
402
+ content: '';
403
+ position: absolute;
404
+ inset: 0;
405
+ z-index: -1;
406
+ border: var(--mp-ai-border-width) solid transparent;
407
+ border-radius: inherit;
408
+ background:
409
+ linear-gradient(var(--mp-ai-surface), var(--mp-ai-surface)) padding-box,
410
+ conic-gradient(from var(--mp-ai-angle), var(--mp-ai-stops)) border-box;
411
+ pointer-events: none;
412
+ }
413
+
414
+ .mp-ai-halo::after,
415
+ .mp-ai-button::after,
416
+ .mp-ai-panel::after {
417
+ content: '';
418
+ position: absolute;
419
+ inset: 0;
420
+ z-index: -2;
421
+ border-radius: inherit;
422
+ background: conic-gradient(from var(--mp-ai-angle), var(--mp-ai-stops));
423
+ filter: blur(var(--mp-ai-glow-blur));
424
+ opacity: var(--mp-ai-glow-opacity);
425
+ pointer-events: none;
426
+ transition: opacity 0.2s ease;
427
+ }
428
+
429
+ /* ── Halo container ─────────────────────────────────────── */
430
+
431
+ .mp-ai-halo {
432
+ display: block;
433
+ border-radius: var(--mp-radius, 0.5rem);
434
+ }
435
+
436
+ .mp-ai-halo::before,
437
+ .mp-ai-halo::after {
438
+ animation: mp-ai-spin var(--mp-ai-duration) linear infinite;
439
+ }
440
+
441
+ /* ── Badge ──────────────────────────────────────────────── */
442
+
443
+ .mp-ai-badge {
444
+ --mp-ai-border-width: 1.5px;
445
+ display: inline-flex;
446
+ align-items: center;
447
+ gap: 0.3em;
448
+ padding: 0.2em 0.65em;
449
+ border-radius: 999px;
450
+ color: var(--mp-ai-text);
451
+ font-size: 0.75rem;
452
+ font-weight: 600;
453
+ line-height: 1.2;
454
+ white-space: nowrap;
455
+ }
456
+
457
+ .mp-ai-icon {
458
+ width: 1em;
459
+ height: 1em;
460
+ flex-shrink: 0;
461
+ fill: currentColor;
462
+ color: var(--mp-ai-text);
463
+ }
464
+
465
+ /* ── Button ─────────────────────────────────────────────── */
466
+
467
+ .mp-ai-button {
468
+ display: inline-flex;
469
+ align-items: center;
470
+ justify-content: center;
471
+ gap: 0.4rem;
472
+ padding: 0.375rem 0.95rem;
473
+ border: 0;
474
+ border-radius: 999px;
475
+ color: var(--mp-ai-fg);
476
+ font: inherit;
477
+ font-size: 0.875rem;
478
+ font-weight: 500;
479
+ line-height: 1.5;
480
+ text-decoration: none;
481
+ cursor: pointer;
482
+ }
483
+
484
+ .mp-ai-button::after {
485
+ opacity: 0;
486
+ }
487
+
488
+ .mp-ai-button:hover::before,
489
+ .mp-ai-button:hover::after,
490
+ .mp-ai-button:focus-visible::before,
491
+ .mp-ai-button:focus-visible::after,
492
+ .mp-ai-button[aria-busy="true"]::before,
493
+ .mp-ai-button[aria-busy="true"]::after {
494
+ animation: mp-ai-spin var(--mp-ai-duration) linear infinite;
495
+ }
496
+
497
+ .mp-ai-button:hover::after,
498
+ .mp-ai-button:focus-visible::after,
499
+ .mp-ai-button[aria-busy="true"]::after {
500
+ opacity: var(--mp-ai-glow-opacity);
501
+ }
502
+
503
+ .mp-ai-button:focus-visible {
504
+ outline: 2px solid var(--mp-ai-text);
505
+ outline-offset: 3px;
506
+ }
507
+
508
+ .mp-ai-button:disabled:not([aria-busy="true"]) {
509
+ cursor: not-allowed;
510
+ opacity: 0.6;
511
+ }
512
+
513
+ .mp-ai-button-sm {
514
+ padding: 0.25rem 0.7rem;
515
+ font-size: 0.8125rem;
516
+ }
517
+
518
+ .mp-ai-button-lg {
519
+ padding: 0.55rem 1.25rem;
520
+ font-size: 1rem;
521
+ }
522
+
523
+ /* ── Thinking indicator ─────────────────────────────────── */
524
+ /* Standalone: a pulsing gradient orb before the label.
525
+ On a halo/button/panel: the glow pulses instead. */
526
+
527
+ .mp-ai-thinking:not(.mp-ai-halo, .mp-ai-button, .mp-ai-badge, .mp-ai-panel) {
528
+ display: inline-flex;
529
+ align-items: center;
530
+ gap: 0.5rem;
531
+ color: var(--mp-ai-fg-muted);
532
+ font-size: 0.875rem;
533
+ }
534
+
535
+ .mp-ai-thinking:not(.mp-ai-halo, .mp-ai-button, .mp-ai-badge, .mp-ai-panel)::before {
536
+ content: '';
537
+ width: 0.875em;
538
+ height: 0.875em;
539
+ flex-shrink: 0;
540
+ border-radius: 50%;
541
+ background: conic-gradient(from var(--mp-ai-angle), var(--mp-ai-stops));
542
+ animation:
543
+ mp-ai-spin 1.5s linear infinite,
544
+ mp-ai-orb-pulse 1.4s ease-in-out infinite;
545
+ }
546
+
547
+ .mp-ai-thinking:is(.mp-ai-halo, .mp-ai-button, .mp-ai-panel)::before {
548
+ animation: mp-ai-spin var(--mp-ai-duration) linear infinite;
549
+ }
550
+
551
+ .mp-ai-thinking:is(.mp-ai-halo, .mp-ai-button, .mp-ai-panel)::after {
552
+ animation:
553
+ mp-ai-spin var(--mp-ai-duration) linear infinite,
554
+ mp-ai-glow-pulse 1.6s ease-in-out infinite;
555
+ }
556
+
557
+ /* ── Answer panel ───────────────────────────────────────── */
558
+
559
+ .mp-ai-panel {
560
+ display: block;
561
+ padding: 1rem 1.125rem;
562
+ border-radius: var(--mp-radius, 0.5rem);
563
+ color: var(--mp-ai-fg);
564
+ font-size: 0.9375rem;
565
+ }
566
+
567
+ .mp-ai-panel::after {
568
+ opacity: calc(var(--mp-ai-glow-opacity) * 0.4);
569
+ }
570
+
571
+ .mp-ai-panel.is-streaming::before,
572
+ .mp-ai-panel.is-streaming::after {
573
+ animation: mp-ai-spin var(--mp-ai-duration) linear infinite;
574
+ }
575
+
576
+ .mp-ai-panel.is-streaming::after {
577
+ opacity: var(--mp-ai-glow-opacity);
578
+ }
579
+
580
+ .mp-ai-panel-header {
581
+ display: flex;
582
+ align-items: center;
583
+ gap: 0.5rem;
584
+ margin-bottom: 0.625rem;
585
+ }
586
+
587
+ .mp-ai-panel-title {
588
+ margin: 0;
589
+ font-size: 0.875rem;
590
+ font-weight: 600;
591
+ }
592
+
593
+ .mp-ai-panel-note {
594
+ margin: 0.75rem 0 0;
595
+ font-size: 0.75rem;
596
+ color: var(--mp-ai-fg-muted);
597
+ }
598
+
599
+ .mp-ai-error {
600
+ margin: 0.5rem 0 0;
601
+ color: var(--mp-ai-danger-text);
602
+ font-size: 0.875rem;
603
+ }
604
+
605
+ .mp-ai-caret {
606
+ display: inline-block;
607
+ width: 0.5em;
608
+ height: 1.1em;
609
+ margin-left: 2px;
610
+ vertical-align: text-bottom;
611
+ border-radius: 1px;
612
+ background: var(--mp-ai-text);
613
+ animation: mp-ai-blink 1s steps(1) infinite;
614
+ }
615
+
616
+ /* ── Rendered answer text (safe markdown subset) ────────── */
617
+
618
+ .mp-ai-prose > :first-child {
619
+ margin-top: 0;
620
+ }
621
+
622
+ .mp-ai-prose > :last-child {
623
+ margin-bottom: 0;
624
+ }
625
+
626
+ .mp-ai-prose p,
627
+ .mp-ai-prose ul,
628
+ .mp-ai-prose ol,
629
+ .mp-ai-prose pre {
630
+ margin: 0 0 0.625rem;
631
+ }
632
+
633
+ .mp-ai-prose ul,
634
+ .mp-ai-prose ol {
635
+ padding-left: 1.25rem;
636
+ }
637
+
638
+ .mp-ai-prose code {
639
+ padding: 0.1em 0.35em;
640
+ border-radius: 0.25rem;
641
+ background: var(--mp-surface, rgba(0, 0, 0, 0.04));
642
+ border: 1px solid var(--mp-border, rgba(0, 0, 0, 0.1));
643
+ color: inherit;
644
+ font-family: var(--bs-font-monospace, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
645
+ font-size: 0.85em;
646
+ }
647
+
648
+ .mp-ai-prose pre {
649
+ padding: 0.625rem 0.75rem;
650
+ overflow-x: auto;
651
+ border-radius: 0.375rem;
652
+ background: var(--mp-surface, rgba(0, 0, 0, 0.04));
653
+ border: 1px solid var(--mp-border, rgba(0, 0, 0, 0.1));
654
+ font-size: 0.8125rem;
655
+ }
656
+
657
+ .mp-ai-prose pre code {
658
+ padding: 0;
659
+ border: 0;
660
+ background: none;
661
+ font-size: inherit;
662
+ }
663
+
664
+ /* ── Chat ───────────────────────────────────────────────── */
665
+
666
+ .mp-ai-chat {
667
+ display: flex;
668
+ flex-direction: column;
669
+ min-height: 16rem;
670
+ max-height: var(--mp-ai-chat-height, 28rem);
671
+ overflow: hidden;
672
+ border: 1px solid var(--mp-border, rgba(0, 0, 0, 0.1));
673
+ border-radius: var(--mp-radius, 0.5rem);
674
+ background: var(--mp-ai-surface);
675
+ color: var(--mp-ai-fg);
676
+ }
677
+
678
+ .mp-ai-chat-header {
679
+ display: flex;
680
+ align-items: center;
681
+ gap: 0.5rem;
682
+ padding: 0.625rem 0.875rem;
683
+ border-bottom: 1px solid var(--mp-border, rgba(0, 0, 0, 0.1));
684
+ }
685
+
686
+ .mp-ai-chat-log {
687
+ display: flex;
688
+ flex: 1;
689
+ flex-direction: column;
690
+ gap: 0.75rem;
691
+ padding: 1rem;
692
+ overflow-y: auto;
693
+ }
694
+
695
+ .mp-ai-chat-empty {
696
+ margin: auto;
697
+ color: var(--mp-ai-fg-muted);
698
+ font-size: 0.875rem;
699
+ text-align: center;
700
+ }
701
+
702
+ .mp-ai-chat-msg {
703
+ display: flex;
704
+ }
705
+
706
+ .mp-ai-chat-msg.is-user {
707
+ justify-content: flex-end;
708
+ }
709
+
710
+ .mp-ai-chat-bubble {
711
+ max-width: 85%;
712
+ padding: 0.5rem 0.75rem;
713
+ border-radius: 0.875rem;
714
+ font-size: 0.9rem;
715
+ overflow-wrap: anywhere;
716
+ }
717
+
718
+ .is-user > .mp-ai-chat-bubble {
719
+ border-bottom-right-radius: 0.25rem;
720
+ background: var(--mp-primary, #4f46e5);
721
+ color: #fff;
722
+ }
723
+
724
+ .is-assistant > .mp-ai-chat-bubble {
725
+ border: 1px solid var(--mp-border, rgba(0, 0, 0, 0.1));
726
+ border-bottom-left-radius: 0.25rem;
727
+ background: var(--mp-surface, rgba(0, 0, 0, 0.02));
728
+ }
729
+
730
+ .mp-ai-chat-form {
731
+ display: flex;
732
+ align-items: flex-end;
733
+ gap: 0.5rem;
734
+ padding: 0.75rem;
735
+ border-top: 1px solid var(--mp-border, rgba(0, 0, 0, 0.1));
736
+ }
737
+
738
+ .mp-ai-chat-input {
739
+ flex: 1;
740
+ min-height: 2.375rem;
741
+ max-height: 8rem;
742
+ padding: 0.45rem 0.75rem;
743
+ resize: vertical;
744
+ border: 1px solid var(--mp-ai-border-color);
745
+ border-radius: 0.5rem;
746
+ background: var(--mp-ai-bg);
747
+ color: inherit;
748
+ font: inherit;
749
+ font-size: 0.9rem;
750
+ }
751
+
752
+ .mp-ai-chat-input:focus {
753
+ outline: 2px solid var(--mp-ai-text);
754
+ outline-offset: 1px;
755
+ border-color: transparent;
756
+ }
757
+
758
+ /* ── Diff ───────────────────────────────────────────────── */
759
+
760
+ .mp-ai-diff {
761
+ overflow: hidden;
762
+ border: 1px solid var(--mp-border, rgba(0, 0, 0, 0.1));
763
+ border-radius: var(--mp-radius, 0.5rem);
764
+ background: var(--mp-ai-surface);
765
+ color: var(--mp-ai-fg);
766
+ }
767
+
768
+ .mp-ai-diff-header {
769
+ display: flex;
770
+ align-items: center;
771
+ gap: 0.5rem;
772
+ padding: 0.5rem 0.75rem;
773
+ border-bottom: 1px solid var(--mp-border, rgba(0, 0, 0, 0.1));
774
+ font-size: 0.8125rem;
775
+ }
776
+
777
+ .mp-ai-diff-title {
778
+ font-weight: 500;
779
+ }
780
+
781
+ .mp-ai-diff-stats {
782
+ display: inline-flex;
783
+ margin-left: auto;
784
+ gap: 0.5rem;
785
+ font-family: var(--bs-font-monospace, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
786
+ }
787
+
788
+ .mp-ai-diff-stat-add {
789
+ color: var(--mp-ai-success-text);
790
+ }
791
+
792
+ .mp-ai-diff-stat-del {
793
+ color: var(--mp-ai-danger-text);
794
+ }
795
+
796
+ .mp-ai-diff-body {
797
+ max-height: 24rem;
798
+ margin: 0;
799
+ padding: 0.375rem 0;
800
+ overflow: auto;
801
+ font-family: var(--bs-font-monospace, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
802
+ font-size: 0.8125rem;
803
+ line-height: 1.55;
804
+ }
805
+
806
+ .mp-ai-diff-line {
807
+ display: block;
808
+ min-width: max-content;
809
+ padding-right: 0.75rem;
810
+ white-space: pre;
811
+ }
812
+
813
+ .mp-ai-diff-sign {
814
+ display: inline-block;
815
+ width: 1.75rem;
816
+ color: var(--mp-ai-fg-muted);
817
+ text-align: center;
818
+ user-select: none;
819
+ }
820
+
821
+ .mp-ai-diff-line.is-add {
822
+ background: var(--mp-ai-diff-add-bg);
823
+ color: var(--mp-ai-diff-add-text);
824
+ }
825
+
826
+ .mp-ai-diff-line.is-del {
827
+ background: var(--mp-ai-diff-del-bg);
828
+ color: var(--mp-ai-diff-del-text);
829
+ }
830
+
831
+ .mp-ai-diff-line.is-add .mp-ai-diff-sign,
832
+ .mp-ai-diff-line.is-del .mp-ai-diff-sign {
833
+ color: inherit;
834
+ }
835
+
836
+ .mp-ai-diff-actions {
837
+ display: flex;
838
+ align-items: center;
839
+ justify-content: flex-end;
840
+ gap: 0.5rem;
841
+ padding: 0.5rem 0.75rem;
842
+ border-top: 1px solid var(--mp-border, rgba(0, 0, 0, 0.1));
843
+ }
844
+
845
+ .mp-ai-diff-status {
846
+ margin-right: auto;
847
+ color: var(--mp-ai-fg-muted);
848
+ font-size: 0.8125rem;
849
+ }
850
+
851
+ .mp-ai-diff.is-applied .mp-ai-diff-status {
852
+ color: var(--mp-ai-success-text);
853
+ }
854
+
855
+ /* Plain action buttons, so the diff works without Bootstrap */
856
+ .mp-ai-action {
857
+ padding: 0.3rem 0.85rem;
858
+ border: 1px solid var(--mp-ai-border-color);
859
+ border-radius: 0.375rem;
860
+ background: transparent;
861
+ color: var(--mp-ai-fg);
862
+ font: inherit;
863
+ font-size: 0.8125rem;
864
+ font-weight: 500;
865
+ cursor: pointer;
866
+ }
867
+
868
+ .mp-ai-action:hover {
869
+ background: var(--mp-surface, rgba(0, 0, 0, 0.04));
870
+ }
871
+
872
+ .mp-ai-action-primary {
873
+ border-color: var(--mp-primary, #4f46e5);
874
+ background: var(--mp-primary, #4f46e5);
875
+ color: #fff;
876
+ }
877
+
878
+ .mp-ai-action-primary:hover {
879
+ border-color: var(--mp-primary-hover, #4338ca);
880
+ background: var(--mp-primary-hover, #4338ca);
881
+ }
882
+
883
+ .mp-ai-action:focus-visible {
884
+ outline: 2px solid var(--mp-ai-text);
885
+ outline-offset: 2px;
886
+ }
887
+
888
+ .mp-ai-action:disabled {
889
+ cursor: not-allowed;
890
+ opacity: 0.6;
891
+ }
892
+
893
+ /* ── Page edge glow ─────────────────────────────────────── */
894
+ /* A fixed, click-through glow around the viewport while the Assistant works. */
895
+
896
+ .mp-ai-edge-glow {
897
+ position: fixed;
898
+ inset: 0;
899
+ z-index: 1090;
900
+ pointer-events: none;
901
+ animation: mp-ai-fade-in 0.4s ease-out;
902
+ }
903
+
904
+ .mp-ai-edge-glow[hidden] {
905
+ display: none;
906
+ }
907
+
908
+ .mp-ai-edge-glow::before,
909
+ .mp-ai-edge-glow::after {
910
+ content: '';
911
+ position: absolute;
912
+ inset: 0;
913
+ background: conic-gradient(from var(--mp-ai-angle) at 50% 50%, var(--mp-ai-stops));
914
+ animation: mp-ai-spin var(--mp-ai-duration) linear infinite;
915
+ }
916
+
917
+ /* Crisp 3px line along the edges */
918
+ .mp-ai-edge-glow::before {
919
+ padding: 3px;
920
+ -webkit-mask:
921
+ linear-gradient(#000 0 0) content-box,
922
+ linear-gradient(#000 0 0);
923
+ -webkit-mask-composite: xor;
924
+ mask:
925
+ linear-gradient(#000 0 0) content-box exclude,
926
+ linear-gradient(#000 0 0);
927
+ }
928
+
929
+ /* Soft glow fading inwards */
930
+ .mp-ai-edge-glow::after {
931
+ opacity: var(--mp-ai-edge-glow-opacity);
932
+ -webkit-mask-image:
933
+ linear-gradient(to right, #000, transparent var(--mp-ai-edge-width),
934
+ transparent calc(100% - var(--mp-ai-edge-width)), #000),
935
+ linear-gradient(to bottom, #000, transparent var(--mp-ai-edge-width),
936
+ transparent calc(100% - var(--mp-ai-edge-width)), #000);
937
+ mask-image:
938
+ linear-gradient(to right, #000, transparent var(--mp-ai-edge-width),
939
+ transparent calc(100% - var(--mp-ai-edge-width)), #000),
940
+ linear-gradient(to bottom, #000, transparent var(--mp-ai-edge-width),
941
+ transparent calc(100% - var(--mp-ai-edge-width)), #000);
942
+ }
943
+
944
+ /* ── Utilities ──────────────────────────────────────────── */
945
+
946
+ .mp-ai-sr-only {
947
+ position: absolute !important;
948
+ width: 1px !important;
949
+ height: 1px !important;
950
+ padding: 0 !important;
951
+ margin: -1px !important;
952
+ overflow: hidden !important;
953
+ clip: rect(0, 0, 0, 0) !important;
954
+ white-space: nowrap !important;
955
+ border: 0 !important;
956
+ }
957
+
958
+ /* ── Reduced motion: static gradient, no pulse ──────────── */
959
+
960
+ @media (prefers-reduced-motion: reduce) {
961
+ .mp-ai-halo::before,
962
+ .mp-ai-halo::after,
963
+ .mp-ai-button::before,
964
+ .mp-ai-button::after,
965
+ .mp-ai-panel::before,
966
+ .mp-ai-panel::after,
967
+ .mp-ai-thinking::before,
968
+ .mp-ai-thinking::after,
969
+ .mp-ai-edge-glow,
970
+ .mp-ai-edge-glow::before,
971
+ .mp-ai-edge-glow::after,
972
+ .mp-ai-caret {
973
+ animation: none !important;
974
+ }
975
+
976
+ .mp-ai-button::after {
977
+ transition: none;
978
+ }
979
+ }
980
+
981
+ /* ── Forced colours: rings and glows vanish, keep an outline ── */
982
+
983
+ @media (forced-colors: active) {
984
+ .mp-ai-halo,
985
+ .mp-ai-button,
986
+ .mp-ai-badge,
987
+ .mp-ai-panel {
988
+ border: 1px solid CanvasText;
989
+ }
990
+
991
+ .mp-ai-edge-glow {
992
+ display: none;
993
+ }
994
+ }