@ojolowoblue/lamba 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.
@@ -0,0 +1,1488 @@
1
+ // src/integrations/react.ts
2
+ import { useState, useEffect } from "react";
3
+
4
+ // src/core/preset-manager.ts
5
+ var STORAGE_PRESETS_KEY = "__lamba_presets__";
6
+ var STORAGE_ACTIVE_PRESET_KEY = "__lamba_active_preset__";
7
+ var PresetManager = class {
8
+ constructor() {
9
+ this.presets = [];
10
+ this.activePresetId = null;
11
+ this.loadFromStorage();
12
+ }
13
+ loadFromStorage() {
14
+ if (typeof localStorage === "undefined") return;
15
+ try {
16
+ const rawPresets = localStorage.getItem(STORAGE_PRESETS_KEY);
17
+ if (rawPresets) {
18
+ this.presets = JSON.parse(rawPresets);
19
+ }
20
+ this.activePresetId = localStorage.getItem(STORAGE_ACTIVE_PRESET_KEY);
21
+ } catch (e) {
22
+ console.warn("[lamba] Failed to parse presets from localStorage", e);
23
+ }
24
+ }
25
+ saveToStorage() {
26
+ if (typeof localStorage === "undefined") return;
27
+ try {
28
+ localStorage.setItem(STORAGE_PRESETS_KEY, JSON.stringify(this.presets));
29
+ if (this.activePresetId) {
30
+ localStorage.setItem(STORAGE_ACTIVE_PRESET_KEY, this.activePresetId);
31
+ } else {
32
+ localStorage.removeItem(STORAGE_ACTIVE_PRESET_KEY);
33
+ }
34
+ } catch (e) {
35
+ console.warn("[lamba] Failed to save presets to localStorage", e);
36
+ }
37
+ }
38
+ getPresets() {
39
+ return [...this.presets];
40
+ }
41
+ getActivePresetId() {
42
+ return this.activePresetId;
43
+ }
44
+ createPreset(name, overrides) {
45
+ const newPreset = {
46
+ id: "preset_" + Math.random().toString(36).substring(2, 9),
47
+ name: name.trim() || "Untitled Preset",
48
+ overrides: { ...overrides },
49
+ createdAt: Date.now()
50
+ };
51
+ this.presets.push(newPreset);
52
+ this.saveToStorage();
53
+ return newPreset;
54
+ }
55
+ deletePreset(id) {
56
+ this.presets = this.presets.filter((p) => p.id !== id);
57
+ if (this.activePresetId === id) {
58
+ this.activePresetId = null;
59
+ }
60
+ this.saveToStorage();
61
+ }
62
+ setActivePreset(id) {
63
+ if (!id) {
64
+ this.activePresetId = null;
65
+ this.saveToStorage();
66
+ return null;
67
+ }
68
+ const preset = this.presets.find((p) => p.id === id);
69
+ if (preset) {
70
+ this.activePresetId = id;
71
+ this.saveToStorage();
72
+ return preset;
73
+ }
74
+ return null;
75
+ }
76
+ };
77
+
78
+ // src/core/env-parser.ts
79
+ function parseEnvString(rawText) {
80
+ const result = {};
81
+ const lines = rawText.split("\n");
82
+ for (let line of lines) {
83
+ line = line.trim();
84
+ if (!line || line.startsWith("#")) continue;
85
+ if (line.startsWith("export ")) {
86
+ line = line.slice(7).trim();
87
+ }
88
+ const equalsIdx = line.indexOf("=");
89
+ if (equalsIdx === -1) continue;
90
+ const key = line.slice(0, equalsIdx).trim();
91
+ let val = line.slice(equalsIdx + 1).trim();
92
+ if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
93
+ val = val.slice(1, -1);
94
+ }
95
+ val = val.replace(/\\n/g, "\n");
96
+ if (key) {
97
+ result[key] = val;
98
+ }
99
+ }
100
+ return result;
101
+ }
102
+ function stringifyEnv(env) {
103
+ return Object.entries(env).map(([key, val]) => {
104
+ const needsQuotes = val.includes(" ") || val.includes("\n") || val.includes("#");
105
+ const formattedVal = needsQuotes ? `"${val.replace(/"/g, '\\"')}"` : val;
106
+ return `${key}=${formattedVal}`;
107
+ }).join("\n");
108
+ }
109
+ function autoDiscoverBrowserEnv() {
110
+ const discovered = {};
111
+ if (typeof window === "undefined") return discovered;
112
+ try {
113
+ const winProcess = window.process;
114
+ if (winProcess && winProcess.env && typeof winProcess.env === "object") {
115
+ for (const [k, v] of Object.entries(winProcess.env)) {
116
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
117
+ discovered[k] = String(v);
118
+ }
119
+ }
120
+ }
121
+ } catch (e) {
122
+ }
123
+ try {
124
+ const customEnv = window.__ENV__ || window.ENV;
125
+ if (customEnv && typeof customEnv === "object") {
126
+ for (const [k, v] of Object.entries(customEnv)) {
127
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
128
+ discovered[k] = String(v);
129
+ }
130
+ }
131
+ }
132
+ } catch (e) {
133
+ }
134
+ try {
135
+ const metaTags = document.querySelectorAll("meta");
136
+ metaTags.forEach((meta) => {
137
+ const name = meta.getAttribute("name");
138
+ const content = meta.getAttribute("content");
139
+ if (!name || content === null) return;
140
+ if (name.startsWith("env:")) {
141
+ const key = name.slice(4);
142
+ discovered[key] = content;
143
+ } else if (name === "lamba-env") {
144
+ const parsed = new URLSearchParams(content);
145
+ parsed.forEach((val, key) => {
146
+ discovered[key] = val;
147
+ });
148
+ }
149
+ });
150
+ } catch (e) {
151
+ }
152
+ return discovered;
153
+ }
154
+ async function tryFetchRootEnvFile() {
155
+ const paths = ["/.env", "/.env.local", "/.env.example"];
156
+ for (const path of paths) {
157
+ try {
158
+ const res = await fetch(path, { headers: { Accept: "text/plain" } });
159
+ if (res.ok) {
160
+ const text = await res.text();
161
+ if (text && text.includes("=")) {
162
+ return parseEnvString(text);
163
+ }
164
+ }
165
+ } catch (e) {
166
+ }
167
+ }
168
+ return {};
169
+ }
170
+
171
+ // src/core/store.ts
172
+ var STORAGE_OVERRIDES_KEY = "__lamba_overrides__";
173
+ var DEFAULT_SECRET_PATTERN = /(KEY|SECRET|TOKEN|PASSWORD|AUTH|PRIVATE|CREDENTIAL|SIGNATURE)/i;
174
+ var EnvStore = class {
175
+ constructor(options = {}) {
176
+ this.variables = /* @__PURE__ */ new Map();
177
+ this.overrides = {};
178
+ this.envChangeListeners = /* @__PURE__ */ new Set();
179
+ this.storeChangeListeners = /* @__PURE__ */ new Set();
180
+ this.secretPattern = options.secretKeysPattern || DEFAULT_SECRET_PATTERN;
181
+ this.presetManager = new PresetManager();
182
+ this.loadOverridesFromStorage();
183
+ if (options.env) {
184
+ this.mergeDefaults(options.env);
185
+ }
186
+ const autoDiscovered = autoDiscoverBrowserEnv();
187
+ this.mergeDefaults(autoDiscovered);
188
+ if (options.autoFetchEnvFile === true && typeof window !== "undefined") {
189
+ tryFetchRootEnvFile().then((fileEnv) => {
190
+ if (Object.keys(fileEnv).length > 0) {
191
+ this.mergeDefaults(fileEnv);
192
+ }
193
+ });
194
+ }
195
+ this.patchProcessEnv();
196
+ }
197
+ loadOverridesFromStorage() {
198
+ if (typeof localStorage === "undefined") return;
199
+ try {
200
+ const stored = localStorage.getItem(STORAGE_OVERRIDES_KEY);
201
+ if (stored) {
202
+ this.overrides = JSON.parse(stored);
203
+ }
204
+ } catch (e) {
205
+ console.warn("[lamba] Failed to parse overrides from localStorage", e);
206
+ }
207
+ }
208
+ saveOverridesToStorage() {
209
+ if (typeof localStorage === "undefined") return;
210
+ try {
211
+ localStorage.setItem(STORAGE_OVERRIDES_KEY, JSON.stringify(this.overrides));
212
+ } catch (e) {
213
+ console.warn("[lamba] Failed to save overrides to localStorage", e);
214
+ }
215
+ }
216
+ /**
217
+ * Monkey-patches window.process.env to reactively return overridden values.
218
+ */
219
+ patchProcessEnv() {
220
+ if (typeof window === "undefined") return;
221
+ const win = window;
222
+ if (!win.process) {
223
+ win.process = { env: {} };
224
+ } else if (!win.process.env) {
225
+ win.process.env = {};
226
+ }
227
+ const self = this;
228
+ const targetEnv = win.process.env;
229
+ try {
230
+ win.process.env = new Proxy(targetEnv, {
231
+ get(target, prop) {
232
+ if (typeof prop === "string") {
233
+ const val = self.get(prop);
234
+ if (val !== void 0) return val;
235
+ }
236
+ return target[prop];
237
+ },
238
+ set(target, prop, value) {
239
+ target[prop] = value;
240
+ return true;
241
+ }
242
+ });
243
+ } catch (e) {
244
+ }
245
+ }
246
+ mergeDefaults(env) {
247
+ let changed = false;
248
+ for (const [key, defaultValue] of Object.entries(env)) {
249
+ const isOverridden = key in this.overrides;
250
+ const currentValue = isOverridden ? this.overrides[key] : defaultValue;
251
+ const isSecret = this.secretPattern.test(key);
252
+ const existing = this.variables.get(key);
253
+ if (!existing) {
254
+ this.variables.set(key, {
255
+ key,
256
+ value: currentValue,
257
+ defaultValue,
258
+ isOverridden,
259
+ isSecret
260
+ });
261
+ changed = true;
262
+ } else {
263
+ if (existing.defaultValue !== defaultValue) {
264
+ existing.defaultValue = defaultValue;
265
+ if (!existing.isOverridden) {
266
+ existing.value = defaultValue;
267
+ }
268
+ changed = true;
269
+ }
270
+ }
271
+ }
272
+ if (changed) {
273
+ this.notifyStoreChanged();
274
+ }
275
+ }
276
+ get(key, fallback) {
277
+ if (key in this.overrides) {
278
+ return this.overrides[key];
279
+ }
280
+ const item = this.variables.get(key);
281
+ if (item) return item.value;
282
+ return fallback;
283
+ }
284
+ getAll() {
285
+ const result = {};
286
+ this.variables.forEach((varObj, key) => {
287
+ result[key] = { ...varObj };
288
+ });
289
+ return result;
290
+ }
291
+ getOverrides() {
292
+ return { ...this.overrides };
293
+ }
294
+ setOverride(key, value) {
295
+ const trimmedKey = key.trim();
296
+ if (!trimmedKey) return;
297
+ this.overrides[trimmedKey] = value;
298
+ this.saveOverridesToStorage();
299
+ let item = this.variables.get(trimmedKey);
300
+ if (!item) {
301
+ item = {
302
+ key: trimmedKey,
303
+ value,
304
+ defaultValue: "",
305
+ isOverridden: true,
306
+ isSecret: this.secretPattern.test(trimmedKey)
307
+ };
308
+ this.variables.set(trimmedKey, item);
309
+ } else {
310
+ item.value = value;
311
+ item.isOverridden = true;
312
+ }
313
+ this.emitEnvChange(trimmedKey, value, true);
314
+ this.notifyStoreChanged();
315
+ }
316
+ removeOverride(key) {
317
+ if (key in this.overrides) {
318
+ delete this.overrides[key];
319
+ this.saveOverridesToStorage();
320
+ const item = this.variables.get(key);
321
+ if (item) {
322
+ item.value = item.defaultValue;
323
+ item.isOverridden = false;
324
+ this.emitEnvChange(key, item.value, false);
325
+ }
326
+ this.notifyStoreChanged();
327
+ }
328
+ }
329
+ resetAllOverrides() {
330
+ const keys = Object.keys(this.overrides);
331
+ this.overrides = {};
332
+ this.saveOverridesToStorage();
333
+ this.presetManager.setActivePreset(null);
334
+ keys.forEach((key) => {
335
+ const item = this.variables.get(key);
336
+ if (item) {
337
+ item.value = item.defaultValue;
338
+ item.isOverridden = false;
339
+ this.emitEnvChange(key, item.value, false);
340
+ }
341
+ });
342
+ this.notifyStoreChanged();
343
+ }
344
+ applyPreset(presetId) {
345
+ const preset = this.presetManager.setActivePreset(presetId);
346
+ this.overrides = preset ? { ...preset.overrides } : {};
347
+ this.saveOverridesToStorage();
348
+ this.variables.forEach((item, key) => {
349
+ if (key in this.overrides) {
350
+ item.value = this.overrides[key];
351
+ item.isOverridden = true;
352
+ } else {
353
+ item.value = item.defaultValue;
354
+ item.isOverridden = false;
355
+ }
356
+ this.emitEnvChange(key, item.value, item.isOverridden);
357
+ });
358
+ if (preset) {
359
+ for (const [key, value] of Object.entries(preset.overrides)) {
360
+ if (!this.variables.has(key)) {
361
+ this.variables.set(key, {
362
+ key,
363
+ value,
364
+ defaultValue: "",
365
+ isOverridden: true,
366
+ isSecret: this.secretPattern.test(key)
367
+ });
368
+ this.emitEnvChange(key, value, true);
369
+ }
370
+ }
371
+ }
372
+ this.notifyStoreChanged();
373
+ }
374
+ subscribeEnvChange(listener) {
375
+ this.envChangeListeners.add(listener);
376
+ return () => this.envChangeListeners.delete(listener);
377
+ }
378
+ subscribeStoreChange(listener) {
379
+ this.storeChangeListeners.add(listener);
380
+ return () => this.storeChangeListeners.delete(listener);
381
+ }
382
+ emitEnvChange(key, value, isOverridden) {
383
+ this.envChangeListeners.forEach((fn) => fn(key, value, isOverridden));
384
+ if (typeof window !== "undefined") {
385
+ window.dispatchEvent(
386
+ new CustomEvent("lamba:env-change", {
387
+ detail: { key, value, isOverridden }
388
+ })
389
+ );
390
+ }
391
+ }
392
+ notifyStoreChanged() {
393
+ const varsObj = this.getAll();
394
+ const presets = this.presetManager.getPresets();
395
+ const activePresetId = this.presetManager.getActivePresetId();
396
+ this.storeChangeListeners.forEach((fn) => fn(varsObj, presets, activePresetId));
397
+ }
398
+ };
399
+
400
+ // src/core/network-interceptor.ts
401
+ var NetworkInterceptor = class {
402
+ constructor(store) {
403
+ this.originalFetch = null;
404
+ this.originalXHRPost = null;
405
+ this.isIntercepting = false;
406
+ this.store = store;
407
+ }
408
+ enable() {
409
+ if (typeof window === "undefined" || this.isIntercepting) return;
410
+ this.patchFetch();
411
+ this.patchXHR();
412
+ this.isIntercepting = true;
413
+ }
414
+ disable() {
415
+ if (typeof window === "undefined" || !this.isIntercepting) return;
416
+ if (this.originalFetch) {
417
+ window.fetch = this.originalFetch;
418
+ this.originalFetch = null;
419
+ }
420
+ if (this.originalXHRPost) {
421
+ XMLHttpRequest.prototype.open = this.originalXHRPost;
422
+ this.originalXHRPost = null;
423
+ }
424
+ this.isIntercepting = false;
425
+ }
426
+ /**
427
+ * Rewrite a given URL by checking if any default env variable URL value matches
428
+ * and replacing it with the active overridden value.
429
+ */
430
+ rewriteUrl(url) {
431
+ if (typeof url !== "string") return url;
432
+ const allVars = this.store.getAll();
433
+ let resultUrl = url;
434
+ for (const varObj of Object.values(allVars)) {
435
+ if (varObj.isOverridden && varObj.defaultValue && varObj.value) {
436
+ const defaultBase = varObj.defaultValue.replace(/\/+$/, "");
437
+ const overrideBase = varObj.value.replace(/\/+$/, "");
438
+ if (defaultBase && resultUrl.includes(defaultBase)) {
439
+ resultUrl = resultUrl.replace(defaultBase, overrideBase);
440
+ }
441
+ }
442
+ }
443
+ return resultUrl;
444
+ }
445
+ patchFetch() {
446
+ if (!window.fetch) return;
447
+ this.originalFetch = window.fetch;
448
+ const self = this;
449
+ window.fetch = function(input, init) {
450
+ let finalInput = input;
451
+ if (typeof input === "string") {
452
+ finalInput = self.rewriteUrl(input);
453
+ } else if (input instanceof URL) {
454
+ const rewritten = self.rewriteUrl(input.toString());
455
+ finalInput = new URL(rewritten);
456
+ } else if (input instanceof Request) {
457
+ const rewrittenUrl = self.rewriteUrl(input.url);
458
+ if (rewrittenUrl !== input.url) {
459
+ finalInput = new Request(rewrittenUrl, input);
460
+ }
461
+ }
462
+ return self.originalFetch.call(this, finalInput, init);
463
+ };
464
+ }
465
+ patchXHR() {
466
+ if (!window.XMLHttpRequest) return;
467
+ this.originalXHRPost = XMLHttpRequest.prototype.open;
468
+ const self = this;
469
+ XMLHttpRequest.prototype.open = function(method, url, async = true, username, password) {
470
+ const stringUrl = typeof url === "string" ? url : url.toString();
471
+ const rewrittenUrl = self.rewriteUrl(stringUrl);
472
+ return self.originalXHRPost.call(
473
+ this,
474
+ method,
475
+ rewrittenUrl,
476
+ async,
477
+ username ?? null,
478
+ password ?? null
479
+ );
480
+ };
481
+ }
482
+ };
483
+
484
+ // src/ui/styles.ts
485
+ var LAMBA_STYLES = `
486
+ :host {
487
+ --lamba-bg-main: #0f172a;
488
+ --lamba-bg-card: #1e293b;
489
+ --lamba-bg-hover: #334155;
490
+ --lamba-border: rgba(255, 255, 255, 0.1);
491
+ --lamba-text-primary: #f8fafc;
492
+ --lamba-text-secondary: #94a3b8;
493
+ --lamba-primary: #6366f1;
494
+ --lamba-primary-hover: #4f46e5;
495
+ --lamba-accent: #06b6d4;
496
+ --lamba-success: #10b981;
497
+ --lamba-danger: #ef4444;
498
+ --lamba-warning: #f59e0b;
499
+ --lamba-font: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
500
+ --lamba-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 8px 10px -6px rgba(0, 0, 0, 0.4);
501
+
502
+ font-family: var(--lamba-font);
503
+ font-size: 14px;
504
+ line-height: 1.5;
505
+ color: var(--lamba-text-primary);
506
+ box-sizing: border-box;
507
+ z-index: 999999;
508
+ }
509
+
510
+ *, *:before, *:after {
511
+ box-sizing: border-box;
512
+ }
513
+
514
+ /* Floating Launcher Button */
515
+ .lamba-launcher {
516
+ position: fixed;
517
+ z-index: 999999;
518
+ width: 52px;
519
+ height: 52px;
520
+ border-radius: 50%;
521
+ background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
522
+ color: #ffffff;
523
+ display: flex;
524
+ align-items: center;
525
+ justify-content: center;
526
+ cursor: pointer;
527
+ box-shadow: 0 10px 15px -3px rgba(99, 102, 241, 0.4), 0 4px 6px -4px rgba(99, 102, 241, 0.2);
528
+ border: 1px solid rgba(255, 255, 255, 0.2);
529
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
530
+ user-select: none;
531
+ }
532
+
533
+ .lamba-launcher:hover {
534
+ transform: scale(1.08);
535
+ box-shadow: 0 14px 20px -3px rgba(99, 102, 241, 0.6), 0 6px 8px -4px rgba(99, 102, 241, 0.3);
536
+ }
537
+
538
+ .lamba-launcher:active {
539
+ transform: scale(0.95);
540
+ }
541
+
542
+ .lamba-launcher-bottom-right { bottom: 24px; right: 24px; }
543
+ .lamba-launcher-bottom-left { bottom: 24px; left: 24px; }
544
+ .lamba-launcher-top-right { top: 24px; right: 24px; }
545
+ .lamba-launcher-top-left { top: 24px; left: 24px; }
546
+
547
+ .lamba-badge {
548
+ position: absolute;
549
+ top: -4px;
550
+ right: -4px;
551
+ background: var(--lamba-accent);
552
+ color: #000;
553
+ font-size: 11px;
554
+ font-weight: 700;
555
+ min-width: 20px;
556
+ height: 20px;
557
+ border-radius: 10px;
558
+ display: flex;
559
+ align-items: center;
560
+ justify-content: center;
561
+ padding: 0 5px;
562
+ border: 2px solid var(--lamba-bg-main);
563
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
564
+ animation: pulse-scale 2s infinite;
565
+ }
566
+
567
+ @keyframes pulse-scale {
568
+ 0%, 100% { transform: scale(1); }
569
+ 50% { transform: scale(1.15); }
570
+ }
571
+
572
+ /* Modal Overlay */
573
+ .lamba-overlay {
574
+ position: fixed;
575
+ top: 0;
576
+ left: 0;
577
+ width: 100vw;
578
+ height: 100vh;
579
+ background: rgba(15, 23, 42, 0.7);
580
+ backdrop-filter: blur(8px);
581
+ z-index: 999998;
582
+ opacity: 0;
583
+ visibility: hidden;
584
+ transition: opacity 0.25s ease, visibility 0.25s ease;
585
+ display: flex;
586
+ align-items: center;
587
+ justify-content: center;
588
+ padding: 20px;
589
+ }
590
+
591
+ .lamba-overlay.active {
592
+ opacity: 1;
593
+ visibility: visible;
594
+ }
595
+
596
+ /* Main Modal Panel */
597
+ .lamba-modal {
598
+ width: 100%;
599
+ max-width: 820px;
600
+ max-height: 85vh;
601
+ background: var(--lamba-bg-main);
602
+ border: 1px solid var(--lamba-border);
603
+ border-radius: 16px;
604
+ box-shadow: var(--lamba-shadow);
605
+ display: flex;
606
+ flex-direction: column;
607
+ overflow: hidden;
608
+ transform: translateY(20px) scale(0.96);
609
+ transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
610
+ }
611
+
612
+ .lamba-overlay.active .lamba-modal {
613
+ transform: translateY(0) scale(1);
614
+ }
615
+
616
+ /* Header */
617
+ .lamba-header {
618
+ padding: 20px 24px;
619
+ border-bottom: 1px solid var(--lamba-border);
620
+ display: flex;
621
+ align-items: center;
622
+ justify-content: space-between;
623
+ background: rgba(30, 41, 59, 0.4);
624
+ }
625
+
626
+ .lamba-brand {
627
+ display: flex;
628
+ align-items: center;
629
+ gap: 12px;
630
+ }
631
+
632
+ .lamba-logo {
633
+ font-size: 18px;
634
+ font-weight: 800;
635
+ letter-spacing: -0.5px;
636
+ background: linear-gradient(135deg, #a5b4fc 0%, #6366f1 100%);
637
+ -webkit-background-clip: text;
638
+ -webkit-text-fill-color: transparent;
639
+ display: flex;
640
+ align-items: center;
641
+ gap: 8px;
642
+ }
643
+
644
+ .lamba-tag {
645
+ background: rgba(99, 102, 241, 0.15);
646
+ color: #a5b4fc;
647
+ font-size: 11px;
648
+ padding: 2px 8px;
649
+ border-radius: 6px;
650
+ font-weight: 600;
651
+ border: 1px solid rgba(99, 102, 241, 0.3);
652
+ }
653
+
654
+ .lamba-close-btn {
655
+ background: transparent;
656
+ border: none;
657
+ color: var(--lamba-text-secondary);
658
+ cursor: pointer;
659
+ padding: 6px;
660
+ border-radius: 8px;
661
+ display: flex;
662
+ align-items: center;
663
+ justify-content: center;
664
+ transition: background 0.15s ease, color 0.15s ease;
665
+ }
666
+
667
+ .lamba-close-btn:hover {
668
+ background: var(--lamba-bg-hover);
669
+ color: var(--lamba-text-primary);
670
+ }
671
+
672
+ /* Controls Bar (Search & Filters) */
673
+ .lamba-controls {
674
+ padding: 16px 24px;
675
+ border-bottom: 1px solid var(--lamba-border);
676
+ display: flex;
677
+ gap: 12px;
678
+ align-items: center;
679
+ flex-wrap: wrap;
680
+ background: var(--lamba-bg-main);
681
+ }
682
+
683
+ .lamba-search-box {
684
+ flex: 1;
685
+ min-width: 220px;
686
+ position: relative;
687
+ }
688
+
689
+ .lamba-search-input {
690
+ width: 100%;
691
+ padding: 10px 14px 10px 38px;
692
+ background: var(--lamba-bg-card);
693
+ border: 1px solid var(--lamba-border);
694
+ border-radius: 8px;
695
+ color: var(--lamba-text-primary);
696
+ font-size: 13px;
697
+ outline: none;
698
+ transition: border-color 0.15s ease;
699
+ }
700
+
701
+ .lamba-search-input:focus {
702
+ border-color: var(--lamba-primary);
703
+ }
704
+
705
+ .lamba-search-icon {
706
+ position: absolute;
707
+ left: 12px;
708
+ top: 50%;
709
+ transform: translateY(-50%);
710
+ color: var(--lamba-text-secondary);
711
+ pointer-events: none;
712
+ }
713
+
714
+ .lamba-tabs {
715
+ display: flex;
716
+ gap: 4px;
717
+ background: var(--lamba-bg-card);
718
+ padding: 4px;
719
+ border-radius: 8px;
720
+ border: 1px solid var(--lamba-border);
721
+ }
722
+
723
+ .lamba-tab {
724
+ background: transparent;
725
+ border: none;
726
+ color: var(--lamba-text-secondary);
727
+ padding: 6px 12px;
728
+ border-radius: 6px;
729
+ font-size: 12px;
730
+ font-weight: 600;
731
+ cursor: pointer;
732
+ transition: all 0.15s ease;
733
+ }
734
+
735
+ .lamba-tab.active {
736
+ background: var(--lamba-primary);
737
+ color: #ffffff;
738
+ }
739
+
740
+ /* Preset Bar */
741
+ .lamba-presets-bar {
742
+ padding: 12px 24px;
743
+ background: rgba(30, 41, 59, 0.3);
744
+ border-bottom: 1px solid var(--lamba-border);
745
+ display: flex;
746
+ align-items: center;
747
+ justify-content: space-between;
748
+ gap: 12px;
749
+ }
750
+
751
+ .lamba-preset-selector {
752
+ display: flex;
753
+ align-items: center;
754
+ gap: 8px;
755
+ font-size: 13px;
756
+ }
757
+
758
+ .lamba-select {
759
+ background: var(--lamba-bg-card);
760
+ color: var(--lamba-text-primary);
761
+ border: 1px solid var(--lamba-border);
762
+ padding: 6px 12px;
763
+ border-radius: 6px;
764
+ outline: none;
765
+ font-size: 13px;
766
+ cursor: pointer;
767
+ }
768
+
769
+ /* List Content */
770
+ .lamba-content {
771
+ flex: 1;
772
+ overflow-y: auto;
773
+ padding: 16px 24px;
774
+ display: flex;
775
+ flex-direction: column;
776
+ gap: 12px;
777
+ }
778
+
779
+ .lamba-content::-webkit-scrollbar {
780
+ width: 6px;
781
+ }
782
+
783
+ .lamba-content::-webkit-scrollbar-track {
784
+ background: transparent;
785
+ }
786
+
787
+ .lamba-content::-webkit-scrollbar-thumb {
788
+ background: var(--lamba-bg-hover);
789
+ border-radius: 3px;
790
+ }
791
+
792
+ /* Variable Row Card */
793
+ .lamba-var-card {
794
+ background: var(--lamba-bg-card);
795
+ border: 1px solid var(--lamba-border);
796
+ border-radius: 10px;
797
+ padding: 14px 16px;
798
+ display: flex;
799
+ flex-direction: column;
800
+ gap: 8px;
801
+ transition: border-color 0.15s ease;
802
+ }
803
+
804
+ .lamba-var-card.is-overridden {
805
+ border-color: rgba(99, 102, 241, 0.5);
806
+ background: linear-gradient(180deg, var(--lamba-bg-card) 0%, rgba(99, 102, 241, 0.05) 100%);
807
+ }
808
+
809
+ .lamba-var-meta {
810
+ display: flex;
811
+ align-items: center;
812
+ justify-content: space-between;
813
+ }
814
+
815
+ .lamba-var-key {
816
+ font-weight: 700;
817
+ font-family: monospace;
818
+ color: #a5b4fc;
819
+ font-size: 13px;
820
+ display: flex;
821
+ align-items: center;
822
+ gap: 8px;
823
+ }
824
+
825
+ .lamba-modified-pill {
826
+ font-size: 10px;
827
+ font-weight: 700;
828
+ background: rgba(16, 185, 129, 0.2);
829
+ color: #34d399;
830
+ padding: 2px 6px;
831
+ border-radius: 4px;
832
+ border: 1px solid rgba(16, 185, 129, 0.4);
833
+ }
834
+
835
+ .lamba-var-actions {
836
+ display: flex;
837
+ align-items: center;
838
+ gap: 6px;
839
+ }
840
+
841
+ .lamba-icon-btn {
842
+ background: transparent;
843
+ border: none;
844
+ color: var(--lamba-text-secondary);
845
+ cursor: pointer;
846
+ padding: 4px;
847
+ border-radius: 4px;
848
+ display: flex;
849
+ align-items: center;
850
+ justify-content: center;
851
+ transition: background 0.15s ease, color 0.15s ease;
852
+ }
853
+
854
+ .lamba-icon-btn:hover {
855
+ background: var(--lamba-bg-hover);
856
+ color: var(--lamba-text-primary);
857
+ }
858
+
859
+ .lamba-var-input-group {
860
+ display: flex;
861
+ gap: 8px;
862
+ }
863
+
864
+ .lamba-input {
865
+ flex: 1;
866
+ background: var(--lamba-bg-main);
867
+ border: 1px solid var(--lamba-border);
868
+ color: var(--lamba-text-primary);
869
+ padding: 8px 12px;
870
+ border-radius: 6px;
871
+ font-family: monospace;
872
+ font-size: 13px;
873
+ outline: none;
874
+ }
875
+
876
+ .lamba-input:focus {
877
+ border-color: var(--lamba-primary);
878
+ }
879
+
880
+ .lamba-diff {
881
+ font-size: 11px;
882
+ color: var(--lamba-text-secondary);
883
+ font-family: monospace;
884
+ padding-left: 2px;
885
+ }
886
+
887
+ .lamba-diff-val {
888
+ color: var(--lamba-warning);
889
+ }
890
+
891
+ /* Empty State */
892
+ .lamba-empty {
893
+ padding: 40px 20px;
894
+ text-align: center;
895
+ color: var(--lamba-text-secondary);
896
+ }
897
+
898
+ /* Footer Actions Bar */
899
+ .lamba-footer {
900
+ padding: 16px 24px;
901
+ border-top: 1px solid var(--lamba-border);
902
+ background: rgba(30, 41, 59, 0.4);
903
+ display: flex;
904
+ align-items: center;
905
+ justify-content: space-between;
906
+ gap: 12px;
907
+ flex-wrap: wrap;
908
+ }
909
+
910
+ .lamba-btn {
911
+ background: var(--lamba-bg-card);
912
+ color: var(--lamba-text-primary);
913
+ border: 1px solid var(--lamba-border);
914
+ padding: 8px 14px;
915
+ border-radius: 8px;
916
+ font-size: 12px;
917
+ font-weight: 600;
918
+ cursor: pointer;
919
+ display: flex;
920
+ align-items: center;
921
+ gap: 6px;
922
+ transition: all 0.15s ease;
923
+ }
924
+
925
+ .lamba-btn:hover {
926
+ background: var(--lamba-bg-hover);
927
+ }
928
+
929
+ .lamba-btn-primary {
930
+ background: var(--lamba-primary);
931
+ border-color: var(--lamba-primary);
932
+ color: #ffffff;
933
+ }
934
+
935
+ .lamba-btn-primary:hover {
936
+ background: var(--lamba-primary-hover);
937
+ }
938
+
939
+ .lamba-btn-danger {
940
+ background: rgba(239, 68, 68, 0.15);
941
+ color: #fca5a5;
942
+ border-color: rgba(239, 68, 68, 0.3);
943
+ }
944
+
945
+ .lamba-btn-danger:hover {
946
+ background: rgba(239, 68, 68, 0.3);
947
+ }
948
+ `;
949
+
950
+ // src/ui/shadow-dom.ts
951
+ function createShadowHost() {
952
+ if (typeof document === "undefined") {
953
+ throw new Error("[lamba] Document object not available.");
954
+ }
955
+ let host = document.getElementById("lamba-root");
956
+ if (!host) {
957
+ host = document.createElement("lamba-widget");
958
+ host.id = "lamba-root";
959
+ document.body.appendChild(host);
960
+ }
961
+ let shadowRoot = host.shadowRoot;
962
+ if (!shadowRoot) {
963
+ shadowRoot = host.attachShadow({ mode: "open" });
964
+ const styleEl = document.createElement("style");
965
+ styleEl.textContent = LAMBA_STYLES;
966
+ shadowRoot.appendChild(styleEl);
967
+ }
968
+ return { host, shadowRoot };
969
+ }
970
+
971
+ // src/ui/launcher.ts
972
+ var LauncherUI = class {
973
+ constructor(container, position = "bottom-right", onClick) {
974
+ this.onClickCallback = onClick;
975
+ this.element = document.createElement("div");
976
+ this.element.className = `lamba-launcher lamba-launcher-${position}`;
977
+ this.element.title = "Open lamba Environment Controls";
978
+ this.element.innerHTML = `
979
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
980
+ <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
981
+ <circle cx="12" cy="12" r="3"/>
982
+ </svg>
983
+ `;
984
+ this.badgeElement = document.createElement("div");
985
+ this.badgeElement.className = "lamba-badge";
986
+ this.badgeElement.style.display = "none";
987
+ this.element.appendChild(this.badgeElement);
988
+ this.element.addEventListener("click", () => this.onClickCallback());
989
+ container.appendChild(this.element);
990
+ }
991
+ updateBadgeCount(count) {
992
+ if (count > 0) {
993
+ this.badgeElement.textContent = String(count);
994
+ this.badgeElement.style.display = "flex";
995
+ } else {
996
+ this.badgeElement.style.display = "none";
997
+ }
998
+ }
999
+ setVisible(visible) {
1000
+ this.element.style.display = visible ? "flex" : "none";
1001
+ }
1002
+ };
1003
+
1004
+ // src/ui/modal.ts
1005
+ var ModalUI = class {
1006
+ constructor(container, store, onClose) {
1007
+ this.activeTab = "all";
1008
+ this.searchQuery = "";
1009
+ this.secretVisibilityMap = /* @__PURE__ */ new Map();
1010
+ this.isOpen = false;
1011
+ this.store = store;
1012
+ this.overlay = document.createElement("div");
1013
+ this.overlay.className = "lamba-overlay";
1014
+ this.modal = document.createElement("div");
1015
+ this.modal.className = "lamba-modal";
1016
+ const header = document.createElement("div");
1017
+ header.className = "lamba-header";
1018
+ header.innerHTML = `
1019
+ <div class="lamba-brand">
1020
+ <div class="lamba-logo">
1021
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
1022
+ <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
1023
+ </svg>
1024
+ lamba
1025
+ </div>
1026
+ <span class="lamba-tag">Dev Environment Override</span>
1027
+ </div>
1028
+ <button class="lamba-close-btn" title="Close modal">
1029
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1030
+ <line x1="18" y1="6" x2="6" y2="18"></line>
1031
+ <line x1="6" y1="6" x2="18" y2="18"></line>
1032
+ </svg>
1033
+ </button>
1034
+ `;
1035
+ header.querySelector(".lamba-close-btn")?.addEventListener("click", () => {
1036
+ this.close();
1037
+ onClose();
1038
+ });
1039
+ const controls = document.createElement("div");
1040
+ controls.className = "lamba-controls";
1041
+ const searchBox = document.createElement("div");
1042
+ searchBox.className = "lamba-search-box";
1043
+ searchBox.innerHTML = `
1044
+ <svg class="lamba-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1045
+ <circle cx="11" cy="11" r="8"></circle>
1046
+ <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
1047
+ </svg>
1048
+ <input type="text" class="lamba-search-input" placeholder="Search environment keys or values..." />
1049
+ `;
1050
+ this.searchInput = searchBox.querySelector(".lamba-search-input");
1051
+ this.searchInput.addEventListener("input", (e) => {
1052
+ this.searchQuery = e.target.value.toLowerCase();
1053
+ this.render();
1054
+ });
1055
+ const tabs = document.createElement("div");
1056
+ tabs.className = "lamba-tabs";
1057
+ tabs.innerHTML = `
1058
+ <button class="lamba-tab active" data-tab="all">All</button>
1059
+ <button class="lamba-tab" data-tab="overridden">Modified</button>
1060
+ <button class="lamba-tab" data-tab="secrets">Secrets</button>
1061
+ `;
1062
+ this.tabAllBtn = tabs.querySelector('[data-tab="all"]');
1063
+ this.tabOverriddenBtn = tabs.querySelector('[data-tab="overridden"]');
1064
+ this.tabSecretsBtn = tabs.querySelector('[data-tab="secrets"]');
1065
+ tabs.querySelectorAll(".lamba-tab").forEach((btn) => {
1066
+ btn.addEventListener("click", (e) => {
1067
+ tabs.querySelectorAll(".lamba-tab").forEach((t) => t.classList.remove("active"));
1068
+ const target = e.currentTarget;
1069
+ target.classList.add("active");
1070
+ this.activeTab = target.getAttribute("data-tab");
1071
+ this.render();
1072
+ });
1073
+ });
1074
+ controls.appendChild(searchBox);
1075
+ controls.appendChild(tabs);
1076
+ const presetsBar = document.createElement("div");
1077
+ presetsBar.className = "lamba-presets-bar";
1078
+ presetsBar.innerHTML = `
1079
+ <div class="lamba-preset-selector">
1080
+ <span style="color: var(--lamba-text-secondary); font-weight: 600;">Preset Profile:</span>
1081
+ <select class="lamba-select lamba-preset-dropdown">
1082
+ <option value="">Default (Current Overrides)</option>
1083
+ </select>
1084
+ </div>
1085
+ <button class="lamba-btn lamba-save-preset-btn">
1086
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1087
+ <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
1088
+ <polyline points="17 21 17 13 7 13 7 21"></polyline>
1089
+ <polyline points="7 3 7 8 15 8"></polyline>
1090
+ </svg>
1091
+ Save as Preset
1092
+ </button>
1093
+ `;
1094
+ this.presetSelect = presetsBar.querySelector(".lamba-preset-dropdown");
1095
+ this.presetSelect.addEventListener("change", () => {
1096
+ const selectedId = this.presetSelect.value || null;
1097
+ this.store.applyPreset(selectedId);
1098
+ });
1099
+ this.savePresetBtn = presetsBar.querySelector(".lamba-save-preset-btn");
1100
+ this.savePresetBtn.addEventListener("click", () => {
1101
+ const name = prompt("Enter a name for this Environment Preset Profile:", "Staging Environment");
1102
+ if (name) {
1103
+ const overrides = this.store.getOverrides();
1104
+ this.store.presetManager.createPreset(name, overrides);
1105
+ this.updatePresetDropdown();
1106
+ }
1107
+ });
1108
+ this.contentList = document.createElement("div");
1109
+ this.contentList.className = "lamba-content";
1110
+ const footer = document.createElement("div");
1111
+ footer.className = "lamba-footer";
1112
+ footer.innerHTML = `
1113
+ <div style="display: flex; gap: 8px;">
1114
+ <button class="lamba-btn lamba-add-var-btn">
1115
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1116
+ <line x1="12" y1="5" x2="12" y2="19"></line>
1117
+ <line x1="5" y1="12" x2="19" y2="12"></line>
1118
+ </svg>
1119
+ Add Variable
1120
+ </button>
1121
+ <button class="lamba-btn lamba-import-btn">Import .env</button>
1122
+ <button class="lamba-btn lamba-export-btn">Export .env</button>
1123
+ </div>
1124
+ <div style="display: flex; gap: 8px;">
1125
+ <button class="lamba-btn lamba-btn-danger lamba-reset-all-btn">Reset All</button>
1126
+ <button class="lamba-btn lamba-btn-primary lamba-reload-btn">Reload Page</button>
1127
+ </div>
1128
+ `;
1129
+ footer.querySelector(".lamba-add-var-btn")?.addEventListener("click", () => {
1130
+ const key = prompt("Enter new environment variable key name (e.g. VITE_API_URL):");
1131
+ if (key) {
1132
+ const value = prompt(`Enter value for ${key}:`) || "";
1133
+ this.store.setOverride(key, value);
1134
+ }
1135
+ });
1136
+ footer.querySelector(".lamba-import-btn")?.addEventListener("click", () => {
1137
+ const input = document.createElement("input");
1138
+ input.type = "file";
1139
+ input.accept = ".env,.txt";
1140
+ input.onchange = (e) => {
1141
+ const file = e.target.files[0];
1142
+ if (file) {
1143
+ const reader = new FileReader();
1144
+ reader.onload = (event) => {
1145
+ const content = event.target?.result;
1146
+ if (content) {
1147
+ const parsed = parseEnvString(content);
1148
+ for (const [k, v] of Object.entries(parsed)) {
1149
+ this.store.setOverride(k, v);
1150
+ }
1151
+ }
1152
+ };
1153
+ reader.readAsText(file);
1154
+ }
1155
+ };
1156
+ input.click();
1157
+ });
1158
+ footer.querySelector(".lamba-export-btn")?.addEventListener("click", () => {
1159
+ const allVars = this.store.getAll();
1160
+ const exportObj = {};
1161
+ for (const [k, v] of Object.entries(allVars)) {
1162
+ exportObj[k] = v.value;
1163
+ }
1164
+ const envText = stringifyEnv(exportObj);
1165
+ const blob = new Blob([envText], { type: "text/plain" });
1166
+ const url = URL.createObjectURL(blob);
1167
+ const a = document.createElement("a");
1168
+ a.href = url;
1169
+ a.download = ".env";
1170
+ a.click();
1171
+ URL.revokeObjectURL(url);
1172
+ });
1173
+ footer.querySelector(".lamba-reset-all-btn")?.addEventListener("click", () => {
1174
+ if (confirm("Are you sure you want to reset all overridden environment variables?")) {
1175
+ this.store.resetAllOverrides();
1176
+ }
1177
+ });
1178
+ footer.querySelector(".lamba-reload-btn")?.addEventListener("click", () => {
1179
+ window.location.reload();
1180
+ });
1181
+ this.modal.appendChild(header);
1182
+ this.modal.appendChild(controls);
1183
+ this.modal.appendChild(presetsBar);
1184
+ this.modal.appendChild(this.contentList);
1185
+ this.modal.appendChild(footer);
1186
+ this.overlay.appendChild(this.modal);
1187
+ this.overlay.addEventListener("click", (e) => {
1188
+ if (e.target === this.overlay) {
1189
+ this.close();
1190
+ onClose();
1191
+ }
1192
+ });
1193
+ container.appendChild(this.overlay);
1194
+ this.store.subscribeStoreChange(() => {
1195
+ if (this.isOpen) {
1196
+ this.updatePresetDropdown();
1197
+ this.render();
1198
+ }
1199
+ });
1200
+ }
1201
+ open() {
1202
+ this.isOpen = true;
1203
+ this.updatePresetDropdown();
1204
+ this.render();
1205
+ this.overlay.classList.add("active");
1206
+ }
1207
+ close() {
1208
+ this.isOpen = false;
1209
+ this.overlay.classList.remove("active");
1210
+ }
1211
+ toggle() {
1212
+ if (this.isOpen) {
1213
+ this.close();
1214
+ } else {
1215
+ this.open();
1216
+ }
1217
+ }
1218
+ updatePresetDropdown() {
1219
+ const presets = this.store.presetManager.getPresets();
1220
+ const activeId = this.store.presetManager.getActivePresetId();
1221
+ this.presetSelect.innerHTML = `<option value="">Default (Custom Overrides)</option>`;
1222
+ presets.forEach((p) => {
1223
+ const opt = document.createElement("option");
1224
+ opt.value = p.id;
1225
+ opt.textContent = p.name;
1226
+ if (p.id === activeId) opt.selected = true;
1227
+ this.presetSelect.appendChild(opt);
1228
+ });
1229
+ }
1230
+ render() {
1231
+ const allVarsMap = this.store.getAll();
1232
+ let varList = Object.values(allVarsMap);
1233
+ if (this.activeTab === "overridden") {
1234
+ varList = varList.filter((v) => v.isOverridden);
1235
+ } else if (this.activeTab === "secrets") {
1236
+ varList = varList.filter((v) => v.isSecret);
1237
+ }
1238
+ if (this.searchQuery) {
1239
+ varList = varList.filter(
1240
+ (v) => v.key.toLowerCase().includes(this.searchQuery) || v.value.toLowerCase().includes(this.searchQuery)
1241
+ );
1242
+ }
1243
+ varList.sort((a, b) => {
1244
+ if (a.isOverridden !== b.isOverridden) {
1245
+ return a.isOverridden ? -1 : 1;
1246
+ }
1247
+ return a.key.localeCompare(b.key);
1248
+ });
1249
+ this.contentList.innerHTML = "";
1250
+ if (varList.length === 0) {
1251
+ const empty = document.createElement("div");
1252
+ empty.className = "lamba-empty";
1253
+ empty.textContent = this.searchQuery || this.activeTab !== "all" ? "No environment variables match your current filter." : 'No environment variables found or registered. Click "+ Add Variable" or "Import .env" to get started.';
1254
+ this.contentList.appendChild(empty);
1255
+ return;
1256
+ }
1257
+ varList.forEach((varItem) => {
1258
+ const card = this.createVarCard(varItem);
1259
+ this.contentList.appendChild(card);
1260
+ });
1261
+ }
1262
+ createVarCard(varItem) {
1263
+ const card = document.createElement("div");
1264
+ card.className = `lamba-var-card ${varItem.isOverridden ? "is-overridden" : ""}`;
1265
+ const isVisible = this.secretVisibilityMap.get(varItem.key) ?? !varItem.isSecret;
1266
+ card.innerHTML = `
1267
+ <div class="lamba-var-meta">
1268
+ <div class="lamba-var-key">
1269
+ ${varItem.key}
1270
+ ${varItem.isOverridden ? '<span class="lamba-modified-pill">MODIFIED</span>' : ""}
1271
+ ${varItem.isSecret ? '<span style="font-size:10px; color:var(--lamba-warning); font-weight:600;">SECRET</span>' : ""}
1272
+ </div>
1273
+ <div class="lamba-var-actions">
1274
+ ${varItem.isSecret ? `<button class="lamba-icon-btn toggle-secret-btn" title="${isVisible ? "Hide secret" : "Show secret"}">
1275
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1276
+ ${isVisible ? '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>' : '<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/>'}
1277
+ </svg>
1278
+ </button>` : ""}
1279
+ ${varItem.isOverridden ? `<button class="lamba-icon-btn reset-var-btn" title="Reset to default">
1280
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1281
+ <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
1282
+ <path d="M3 3v5h5"/>
1283
+ </svg>
1284
+ </button>` : ""}
1285
+ </div>
1286
+ </div>
1287
+ <div class="lamba-var-input-group">
1288
+ <input
1289
+ type="${isVisible ? "text" : "password"}"
1290
+ class="lamba-input var-input"
1291
+ value="${this.escapeHtml(varItem.value)}"
1292
+ placeholder="Value..."
1293
+ />
1294
+ </div>
1295
+ ${varItem.isOverridden && varItem.defaultValue ? `<div class="lamba-diff">Original default: <span class="lamba-diff-val">${this.escapeHtml(
1296
+ varItem.defaultValue
1297
+ )}</span></div>` : ""}
1298
+ `;
1299
+ const inputEl = card.querySelector(".var-input");
1300
+ inputEl.addEventListener("change", () => {
1301
+ this.store.setOverride(varItem.key, inputEl.value);
1302
+ });
1303
+ card.querySelector(".toggle-secret-btn")?.addEventListener("click", () => {
1304
+ this.secretVisibilityMap.set(varItem.key, !isVisible);
1305
+ this.render();
1306
+ });
1307
+ card.querySelector(".reset-var-btn")?.addEventListener("click", () => {
1308
+ this.store.removeOverride(varItem.key);
1309
+ });
1310
+ return card;
1311
+ }
1312
+ escapeHtml(str) {
1313
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1314
+ }
1315
+ };
1316
+
1317
+ // src/index.ts
1318
+ var LambaManager = class {
1319
+ constructor() {
1320
+ this.store = null;
1321
+ this.launcher = null;
1322
+ this.modal = null;
1323
+ this.networkInterceptor = null;
1324
+ this.options = {};
1325
+ this.isInitialized = false;
1326
+ const self = this;
1327
+ this.env = new Proxy({}, {
1328
+ get(_target, prop) {
1329
+ if (typeof prop === "string") {
1330
+ return self.get(prop);
1331
+ }
1332
+ return void 0;
1333
+ },
1334
+ set(_target, prop, value) {
1335
+ if (typeof prop === "string") {
1336
+ self.set(prop, String(value));
1337
+ return true;
1338
+ }
1339
+ return false;
1340
+ }
1341
+ });
1342
+ }
1343
+ /**
1344
+ * Wraps any environment object (such as `import.meta.env` or `process.env`)
1345
+ * in a dynamic proxy that implicitly resolves live overrides from lamba.
1346
+ */
1347
+ wrap(targetEnv) {
1348
+ const self = this;
1349
+ return new Proxy(targetEnv, {
1350
+ get(target, prop) {
1351
+ if (typeof prop === "string") {
1352
+ const overrideVal = self.get(prop);
1353
+ if (overrideVal !== void 0 && overrideVal !== "") {
1354
+ return overrideVal;
1355
+ }
1356
+ }
1357
+ return Reflect.get(target, prop);
1358
+ }
1359
+ });
1360
+ }
1361
+ /**
1362
+ * Initializes lamba with custom options and mounts the floating UI.
1363
+ */
1364
+ init(options = {}) {
1365
+ if (typeof window === "undefined") return this;
1366
+ if (this.isInitialized) return this;
1367
+ this.options = {
1368
+ enabled: true,
1369
+ position: "bottom-right",
1370
+ autoFetchEnvFile: false,
1371
+ interceptNetworkRequests: true,
1372
+ ...options
1373
+ };
1374
+ if (this.options.enabled === false) return this;
1375
+ this.store = new EnvStore(this.options);
1376
+ if (this.options.interceptNetworkRequests !== false) {
1377
+ this.networkInterceptor = new NetworkInterceptor(this.store);
1378
+ this.networkInterceptor.enable();
1379
+ }
1380
+ const mountUI = () => {
1381
+ if (this.launcher || !this.store) return;
1382
+ const { shadowRoot } = createShadowHost();
1383
+ this.modal = new ModalUI(shadowRoot, this.store, () => {
1384
+ });
1385
+ this.launcher = new LauncherUI(shadowRoot, this.options.position, () => {
1386
+ this.modal?.toggle();
1387
+ });
1388
+ const initialOverridesCount = Object.keys(this.store.getOverrides()).length;
1389
+ this.launcher.updateBadgeCount(initialOverridesCount);
1390
+ this.store.subscribeStoreChange(() => {
1391
+ const count = Object.keys(this.store.getOverrides()).length;
1392
+ this.launcher?.updateBadgeCount(count);
1393
+ });
1394
+ };
1395
+ if (document.readyState === "loading") {
1396
+ document.addEventListener("DOMContentLoaded", mountUI);
1397
+ } else {
1398
+ mountUI();
1399
+ }
1400
+ this.isInitialized = true;
1401
+ return this;
1402
+ }
1403
+ /**
1404
+ * Gets the active value of an environment variable (returns overridden value if active, otherwise default).
1405
+ */
1406
+ get(key, fallback) {
1407
+ if (!this.store) {
1408
+ this.init();
1409
+ }
1410
+ return this.store?.get(key, fallback) ?? fallback ?? "";
1411
+ }
1412
+ /**
1413
+ * Overrides an environment variable live at runtime.
1414
+ */
1415
+ set(key, value) {
1416
+ if (!this.store) this.init();
1417
+ this.store?.setOverride(key, value);
1418
+ }
1419
+ /**
1420
+ * Removes an override for a specific environment variable key.
1421
+ */
1422
+ remove(key) {
1423
+ this.store?.removeOverride(key);
1424
+ }
1425
+ /**
1426
+ * Resets all environment variable overrides.
1427
+ */
1428
+ reset() {
1429
+ this.store?.resetAllOverrides();
1430
+ }
1431
+ /**
1432
+ * Subscribes to environment variable changes.
1433
+ */
1434
+ onChange(listener) {
1435
+ if (!this.store) this.init();
1436
+ return this.store?.subscribeEnvChange(listener) ?? (() => {
1437
+ });
1438
+ }
1439
+ /**
1440
+ * Programmatically opens the lamba floating modal.
1441
+ */
1442
+ open() {
1443
+ this.modal?.open();
1444
+ }
1445
+ /**
1446
+ * Programmatically closes the lamba floating modal.
1447
+ */
1448
+ close() {
1449
+ this.modal?.close();
1450
+ }
1451
+ /**
1452
+ * Programmatically toggles the lamba floating modal.
1453
+ */
1454
+ toggle() {
1455
+ this.modal?.toggle();
1456
+ }
1457
+ };
1458
+ var lamba = new LambaManager();
1459
+ if (typeof window !== "undefined") {
1460
+ window.lamba = lamba;
1461
+ const isCdnScript = document.currentScript !== null || !!document.querySelector('script[src*="lamba"]');
1462
+ if (isCdnScript) {
1463
+ lamba.init();
1464
+ }
1465
+ }
1466
+
1467
+ // src/integrations/react.ts
1468
+ function useLambaEnv(key, defaultValue = "") {
1469
+ const [value, setValue] = useState(
1470
+ () => lamba.get(key, defaultValue)
1471
+ );
1472
+ useEffect(() => {
1473
+ setValue(lamba.get(key, defaultValue));
1474
+ const unsubscribe = lamba.onChange((changedKey) => {
1475
+ if (changedKey === key) {
1476
+ setValue(lamba.get(key, defaultValue));
1477
+ }
1478
+ });
1479
+ return () => {
1480
+ unsubscribe();
1481
+ };
1482
+ }, [key, defaultValue]);
1483
+ return value;
1484
+ }
1485
+ export {
1486
+ useLambaEnv
1487
+ };
1488
+ //# sourceMappingURL=react.mjs.map