@yiaany/ghostapi 0.1.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 (184) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/CONTRIBUTING.md +117 -0
  3. package/LICENSE +21 -0
  4. package/README.md +354 -0
  5. package/SECURITY.md +37 -0
  6. package/dist/agents/agentPrompt.d.ts +5 -0
  7. package/dist/agents/agentPrompt.js +30 -0
  8. package/dist/agents/agentPrompt.js.map +1 -0
  9. package/dist/ai/aiClient.d.ts +7 -0
  10. package/dist/ai/aiClient.js +30 -0
  11. package/dist/ai/aiClient.js.map +1 -0
  12. package/dist/ai/aiGenerator.d.ts +10 -0
  13. package/dist/ai/aiGenerator.js +101 -0
  14. package/dist/ai/aiGenerator.js.map +1 -0
  15. package/dist/ai/genericInference.d.ts +5 -0
  16. package/dist/ai/genericInference.js +80 -0
  17. package/dist/ai/genericInference.js.map +1 -0
  18. package/dist/ai/index.d.ts +3 -0
  19. package/dist/ai/index.js +2 -0
  20. package/dist/ai/index.js.map +1 -0
  21. package/dist/ai/jsonRepair.d.ts +2 -0
  22. package/dist/ai/jsonRepair.js +31 -0
  23. package/dist/ai/jsonRepair.js.map +1 -0
  24. package/dist/ai/prompts.d.ts +4 -0
  25. package/dist/ai/prompts.js +81 -0
  26. package/dist/ai/prompts.js.map +1 -0
  27. package/dist/behavior/behaviorStore.d.ts +12 -0
  28. package/dist/behavior/behaviorStore.js +72 -0
  29. package/dist/behavior/behaviorStore.js.map +1 -0
  30. package/dist/cache/index.d.ts +9 -0
  31. package/dist/cache/index.js +34 -0
  32. package/dist/cache/index.js.map +1 -0
  33. package/dist/cli/errors.d.ts +4 -0
  34. package/dist/cli/errors.js +9 -0
  35. package/dist/cli/errors.js.map +1 -0
  36. package/dist/cli/index.d.ts +2 -0
  37. package/dist/cli/index.js +277 -0
  38. package/dist/cli/index.js.map +1 -0
  39. package/dist/cli/parser.d.ts +56 -0
  40. package/dist/cli/parser.js +169 -0
  41. package/dist/cli/parser.js.map +1 -0
  42. package/dist/config/localConfig.d.ts +16 -0
  43. package/dist/config/localConfig.js +58 -0
  44. package/dist/config/localConfig.js.map +1 -0
  45. package/dist/config/serverConfig.d.ts +13 -0
  46. package/dist/config/serverConfig.js +44 -0
  47. package/dist/config/serverConfig.js.map +1 -0
  48. package/dist/dashboard/app.js +615 -0
  49. package/dist/dashboard/dashboard.d.ts +4 -0
  50. package/dist/dashboard/dashboard.js +20 -0
  51. package/dist/dashboard/dashboard.js.map +1 -0
  52. package/dist/dashboard/index.d.ts +1 -0
  53. package/dist/dashboard/index.html +298 -0
  54. package/dist/dashboard/index.js +2 -0
  55. package/dist/dashboard/index.js.map +1 -0
  56. package/dist/dashboard/styles.css +349 -0
  57. package/dist/errors/errorEngine.d.ts +3 -0
  58. package/dist/errors/errorEngine.js +65 -0
  59. package/dist/errors/errorEngine.js.map +1 -0
  60. package/dist/errors/index.d.ts +3 -0
  61. package/dist/errors/index.js +3 -0
  62. package/dist/errors/index.js.map +1 -0
  63. package/dist/errors/providerErrors.d.ts +3 -0
  64. package/dist/errors/providerErrors.js +6 -0
  65. package/dist/errors/providerErrors.js.map +1 -0
  66. package/dist/fault/faultLab.d.ts +27 -0
  67. package/dist/fault/faultLab.js +89 -0
  68. package/dist/fault/faultLab.js.map +1 -0
  69. package/dist/index.d.ts +2 -0
  70. package/dist/index.js +2 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/landing/assets/index-BXR41fAD.css +1 -0
  73. package/dist/landing/assets/index-D8_ggMRE.js +55 -0
  74. package/dist/landing/index.html +15 -0
  75. package/dist/landing/landing.d.ts +3 -0
  76. package/dist/landing/landing.js +12 -0
  77. package/dist/landing/landing.js.map +1 -0
  78. package/dist/mcp/server.d.ts +1 -0
  79. package/dist/mcp/server.js +52 -0
  80. package/dist/mcp/server.js.map +1 -0
  81. package/dist/providers/discord.d.ts +2 -0
  82. package/dist/providers/discord.js +11 -0
  83. package/dist/providers/discord.js.map +1 -0
  84. package/dist/providers/generic.d.ts +2 -0
  85. package/dist/providers/generic.js +13 -0
  86. package/dist/providers/generic.js.map +1 -0
  87. package/dist/providers/github.d.ts +2 -0
  88. package/dist/providers/github.js +11 -0
  89. package/dist/providers/github.js.map +1 -0
  90. package/dist/providers/index.d.ts +2 -0
  91. package/dist/providers/index.js +2 -0
  92. package/dist/providers/index.js.map +1 -0
  93. package/dist/providers/openai.d.ts +2 -0
  94. package/dist/providers/openai.js +15 -0
  95. package/dist/providers/openai.js.map +1 -0
  96. package/dist/providers/registry.d.ts +12 -0
  97. package/dist/providers/registry.js +23 -0
  98. package/dist/providers/registry.js.map +1 -0
  99. package/dist/providers/resend.d.ts +2 -0
  100. package/dist/providers/resend.js +12 -0
  101. package/dist/providers/resend.js.map +1 -0
  102. package/dist/providers/stripe.d.ts +2 -0
  103. package/dist/providers/stripe.js +15 -0
  104. package/dist/providers/stripe.js.map +1 -0
  105. package/dist/providers/twilio.d.ts +2 -0
  106. package/dist/providers/twilio.js +14 -0
  107. package/dist/providers/twilio.js.map +1 -0
  108. package/dist/providers/types.d.ts +13 -0
  109. package/dist/providers/types.js +2 -0
  110. package/dist/providers/types.js.map +1 -0
  111. package/dist/proxy/cacheKey.d.ts +2 -0
  112. package/dist/proxy/cacheKey.js +37 -0
  113. package/dist/proxy/cacheKey.js.map +1 -0
  114. package/dist/proxy/providerDetector.d.ts +11 -0
  115. package/dist/proxy/providerDetector.js +74 -0
  116. package/dist/proxy/providerDetector.js.map +1 -0
  117. package/dist/proxy/proxyHandler.d.ts +3 -0
  118. package/dist/proxy/proxyHandler.js +114 -0
  119. package/dist/proxy/proxyHandler.js.map +1 -0
  120. package/dist/proxy/requestNormalizer.d.ts +11 -0
  121. package/dist/proxy/requestNormalizer.js +30 -0
  122. package/dist/proxy/requestNormalizer.js.map +1 -0
  123. package/dist/report/safetyReport.d.ts +13 -0
  124. package/dist/report/safetyReport.js +65 -0
  125. package/dist/report/safetyReport.js.map +1 -0
  126. package/dist/rules/aiRules.d.ts +7 -0
  127. package/dist/rules/aiRules.js +31 -0
  128. package/dist/rules/aiRules.js.map +1 -0
  129. package/dist/scenarios/scenarioStore.d.ts +37 -0
  130. package/dist/scenarios/scenarioStore.js +201 -0
  131. package/dist/scenarios/scenarioStore.js.map +1 -0
  132. package/dist/security/headerSanitizer.d.ts +3 -0
  133. package/dist/security/headerSanitizer.js +12 -0
  134. package/dist/security/headerSanitizer.js.map +1 -0
  135. package/dist/security/maskSecrets.d.ts +1 -0
  136. package/dist/security/maskSecrets.js +2 -0
  137. package/dist/security/maskSecrets.js.map +1 -0
  138. package/dist/security/secrets.d.ts +4 -0
  139. package/dist/security/secrets.js +61 -0
  140. package/dist/security/secrets.js.map +1 -0
  141. package/dist/server/createServer.d.ts +8 -0
  142. package/dist/server/createServer.js +42 -0
  143. package/dist/server/createServer.js.map +1 -0
  144. package/dist/server/eventsStore.d.ts +17 -0
  145. package/dist/server/eventsStore.js +31 -0
  146. package/dist/server/eventsStore.js.map +1 -0
  147. package/dist/server/routes.d.ts +3 -0
  148. package/dist/server/routes.js +189 -0
  149. package/dist/server/routes.js.map +1 -0
  150. package/dist/server/sse.d.ts +5 -0
  151. package/dist/server/sse.js +32 -0
  152. package/dist/server/sse.js.map +1 -0
  153. package/dist/setup/sdkDetector.d.ts +9 -0
  154. package/dist/setup/sdkDetector.js +31 -0
  155. package/dist/setup/sdkDetector.js.map +1 -0
  156. package/dist/setup/setupGenerator.d.ts +28 -0
  157. package/dist/setup/setupGenerator.js +146 -0
  158. package/dist/setup/setupGenerator.js.map +1 -0
  159. package/dist/state/index.d.ts +1 -0
  160. package/dist/state/index.js +2 -0
  161. package/dist/state/index.js.map +1 -0
  162. package/dist/state/stateExtractor.d.ts +3 -0
  163. package/dist/state/stateExtractor.js +36 -0
  164. package/dist/state/stateExtractor.js.map +1 -0
  165. package/dist/state/stateResolver.d.ts +8 -0
  166. package/dist/state/stateResolver.js +95 -0
  167. package/dist/state/stateResolver.js.map +1 -0
  168. package/dist/state/stateStore.d.ts +4 -0
  169. package/dist/state/stateStore.js +72 -0
  170. package/dist/state/stateStore.js.map +1 -0
  171. package/dist/tests/testGenerator.d.ts +6 -0
  172. package/dist/tests/testGenerator.js +33 -0
  173. package/dist/tests/testGenerator.js.map +1 -0
  174. package/dist/utils/json.d.ts +2 -0
  175. package/dist/utils/json.js +10 -0
  176. package/dist/utils/json.js.map +1 -0
  177. package/docs/release-checklist.md +71 -0
  178. package/examples/agent-instructions/README.md +39 -0
  179. package/examples/generic-rest/README.md +27 -0
  180. package/examples/github-fetch/README.md +35 -0
  181. package/examples/resend-node/README.md +22 -0
  182. package/examples/stripe-node/README.md +29 -0
  183. package/examples/twilio-node/README.md +21 -0
  184. package/package.json +67 -0
@@ -0,0 +1,349 @@
1
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
2
+
3
+ :root {
4
+ --background: #09090b; /* zinc-950 */
5
+ --foreground: #fafafa;
6
+ --card: #09090b;
7
+ --card-foreground: #fafafa;
8
+ --popover: #09090b;
9
+ --popover-foreground: #fafafa;
10
+ --primary: #fafafa;
11
+ --primary-foreground: #18181b;
12
+ --secondary: #27272a;
13
+ --secondary-foreground: #fafafa;
14
+ --muted: #27272a;
15
+ --muted-foreground: #a1a1aa; /* zinc-400 */
16
+ --accent: #27272a;
17
+ --accent-foreground: #fafafa;
18
+ --border: rgba(255, 255, 255, 0.1);
19
+ --input: #27272a;
20
+ --ring: #d4d4d8;
21
+
22
+ --radius: 0.5rem;
23
+
24
+ --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
25
+ --font-mono: 'JetBrains Mono', "Geist Mono", "Fira Code", monospace;
26
+
27
+ --sys-green: #4ade80;
28
+ --sys-blue: #60a5fa;
29
+ --sys-red: #f87171;
30
+ --sys-orange: #fbbf24;
31
+ }
32
+
33
+ * {
34
+ box-sizing: border-box;
35
+ margin: 0;
36
+ padding: 0;
37
+ }
38
+
39
+ body {
40
+ background-color: var(--background);
41
+ color: var(--foreground);
42
+ font-family: var(--font-sans);
43
+ -webkit-font-smoothing: antialiased;
44
+ font-size: 13px;
45
+ overflow: hidden;
46
+ }
47
+
48
+ /* Layout Wrappers */
49
+ .app-wrapper {
50
+ display: flex;
51
+ width: 100vw;
52
+ height: 100vh;
53
+ background: var(--background);
54
+ }
55
+
56
+ /* Sidebar */
57
+ .sidebar {
58
+ width: 260px;
59
+ height: 100%;
60
+ background-color: rgba(255, 255, 255, 0.02);
61
+ border-right: 1px solid var(--border);
62
+ padding: 12px;
63
+ display: flex;
64
+ flex-direction: column;
65
+ flex-shrink: 0;
66
+ transition: width 0.3s ease, opacity 0.3s ease;
67
+ overflow: hidden;
68
+ }
69
+
70
+ .sidebar.closed {
71
+ width: 0;
72
+ padding-left: 0;
73
+ padding-right: 0;
74
+ opacity: 0;
75
+ border-right: none;
76
+ }
77
+
78
+ /* Workspace Switcher */
79
+ .ws-switcher {
80
+ position: relative;
81
+ margin-bottom: 24px;
82
+ }
83
+ .ws-btn {
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: space-between;
87
+ padding: 8px;
88
+ border-radius: var(--radius);
89
+ user-select: none;
90
+ }
91
+ .ws-btn-content { display: flex; align-items: center; gap: 12px; }
92
+ .ws-avatar {
93
+ width: 32px; height: 32px;
94
+ border-radius: 6px;
95
+ background: var(--primary);
96
+ color: var(--primary-foreground);
97
+ display: flex; align-items: center; justify-content: center;
98
+ font-weight: 600; font-size: 13px;
99
+ box-shadow: 0 1px 2px rgba(0,0,0,0.1);
100
+ }
101
+ .ws-info { display: flex; flex-direction: column; overflow: hidden; }
102
+ .ws-title { font-size: 13px; font-weight: 500; line-height: 1; margin-bottom: 4px; color: var(--foreground); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 120px; }
103
+ .ws-subtitle { font-size: 11px; color: var(--muted-foreground); line-height: 1; }
104
+ .ws-icon { color: rgba(161, 161, 170, 0.5); width: 16px; height: 16px; transition: color 0.2s; }
105
+ .ws-btn:hover .ws-icon { color: rgba(250, 250, 250, 0.7); }
106
+
107
+ /* Sidebar Nav Groups */
108
+ .nav-scroll-area {
109
+ flex: 1;
110
+ overflow-y: auto;
111
+ display: flex;
112
+ flex-direction: column;
113
+ gap: 16px;
114
+ margin-top: 8px;
115
+ }
116
+ .nav-scroll-area::-webkit-scrollbar { display: none; }
117
+ .nav-group { display: flex; flex-direction: column; gap: 2px; }
118
+ .nav-heading {
119
+ padding: 0 10px; margin-bottom: 4px;
120
+ font-size: 11px; font-weight: 600; letter-spacing: 0.05em;
121
+ color: rgba(161, 161, 170, 0.5); text-transform: uppercase;
122
+ }
123
+
124
+ /* Nav Item */
125
+ .nav-item {
126
+ display: flex; align-items: center; justify-content: space-between;
127
+ padding: 7px 10px;
128
+ border-radius: 6px;
129
+ cursor: pointer;
130
+ transition: all 0.2s;
131
+ user-select: none;
132
+ color: var(--muted-foreground);
133
+ }
134
+ .nav-item:hover { color: rgba(250, 250, 250, 0.9); background: rgba(255, 255, 255, 0.05); }
135
+ .nav-item.active { background: rgba(255, 255, 255, 0.1); color: var(--foreground); font-weight: 500; }
136
+ .nav-item-left { display: flex; align-items: center; gap: 10px; }
137
+ .nav-item-icon { width: 16px; height: 16px; color: rgba(161, 161, 170, 0.7); transition: color 0.2s; }
138
+ .nav-item:hover .nav-item-icon { color: rgba(250, 250, 250, 0.7); }
139
+ .nav-item.active .nav-item-icon { color: var(--foreground); }
140
+ .nav-item-title { font-size: 13px; tracking-wide; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
141
+ .nav-item-right { display: flex; align-items: center; gap: 8px; }
142
+
143
+ /* Status Badge/Shortcuts injected via JS natively */
144
+ .sc-badge { display: none; align-items: center; justify-content: center; height: 20px; padding: 0 6px; font-size: 10px; font-weight: 500; font-family: var(--font-mono); color: rgba(161, 161, 170, 0.6); background: rgba(0, 0, 0, 0.5); border: 1px solid var(--border); border-radius: 4px; }
145
+ .nav-item:hover .sc-badge { display: flex; }
146
+ .num-badge { display: flex; align-items: center; justify-content: center; min-width: 20px; height: 20px; padding: 0 6px; font-size: 10px; font-weight: 500; border-radius: 99px; background: rgba(250, 250, 250, 0.1); color: var(--primary); }
147
+ .fault-toggle.active { color: #fef3c7; background: rgba(251, 191, 36, 0.1); }
148
+ .fault-toggle.active .nav-item-icon { color: var(--sys-orange); }
149
+ .fault-state { font-family: var(--font-mono); font-size: 10px; color: var(--muted-foreground); border: 1px solid var(--border); border-radius: 99px; padding: 2px 6px; }
150
+ .fault-toggle.active .fault-state { color: var(--sys-orange); border-color: rgba(251, 191, 36, 0.25); background: rgba(251, 191, 36, 0.08); }
151
+
152
+ .nav-bottom {
153
+ margin-top: auto;
154
+ padding-top: 16px;
155
+ border-top: 1px solid var(--border);
156
+ display: flex;
157
+ flex-direction: column;
158
+ gap: 2px;
159
+ }
160
+
161
+ /* Main Content Area */
162
+ .main {
163
+ flex: 1;
164
+ display: flex;
165
+ flex-direction: column;
166
+ background: rgba(255, 255, 255, 0.01);
167
+ min-width: 0;
168
+ transition: all 0.3s;
169
+ }
170
+
171
+ /* Topbar */
172
+ .topbar {
173
+ height: 56px;
174
+ border-bottom: 1px solid var(--border);
175
+ display: flex;
176
+ align-items: center;
177
+ justify-content: space-between;
178
+ padding: 0 16px;
179
+ background: var(--card);
180
+ flex-shrink: 0;
181
+ }
182
+ .topbar-left { display: flex; align-items: center; gap: 12px; }
183
+ .toggle-btn {
184
+ padding: 6px; border-radius: 6px; color: var(--muted-foreground); cursor: pointer; transition: all 0.2s; background: transparent; border: none; outline: none;
185
+ }
186
+ .toggle-btn:hover { background: rgba(255,255,255,0.05); color: var(--foreground); }
187
+
188
+ .breadcrumb { display: flex; align-items: center; gap: 8px; font-size: 14px; color: var(--muted-foreground); }
189
+ .breadcrumb-active { font-weight: 500; color: var(--foreground); }
190
+
191
+ .topbar-right { display: flex; align-items: center; gap: 12px; }
192
+ .status-indicator {
193
+ display: flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 6px; font-size: 12px; background: rgba(255,255,255,0.03); color: var(--muted-foreground); border: 1px solid var(--border);
194
+ }
195
+ .status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted-foreground); }
196
+ .status-indicator.connected .status-dot { background: var(--sys-green); box-shadow: 0 0 10px rgba(74, 222, 128, 0.4); }
197
+ .status-indicator.connected { color: var(--foreground); }
198
+
199
+ /* Main Split View: Event List (Left) + Event Details (Right) */
200
+ .content-wrapper {
201
+ flex: 1;
202
+ display: flex;
203
+ overflow: hidden;
204
+ }
205
+
206
+ /* Event List Pane */
207
+ .event-list-pane {
208
+ width: 340px;
209
+ border-right: 1px solid var(--border);
210
+ display: flex;
211
+ flex-direction: column;
212
+ background: var(--background);
213
+ }
214
+ .event-list-header {
215
+ padding: 12px 16px;
216
+ border-bottom: 1px solid var(--border);
217
+ display: flex; justify-content: space-between; align-items: center;
218
+ }
219
+ .event-list-header h2 { font-size: 13px; font-weight: 600; color: var(--foreground); }
220
+ .event-count { font-family: var(--font-mono); font-size: 11px; color: var(--muted-foreground); background: rgba(255,255,255,0.05); padding: 2px 6px; border-radius: 4px;}
221
+
222
+ .events-scroll { flex: 1; overflow-y: auto; }
223
+ .events-scroll::-webkit-scrollbar { display: none; }
224
+
225
+ .event-card {
226
+ padding: 12px 16px; border-bottom: 1px solid var(--border); cursor: pointer; transition: background 0.2s;
227
+ }
228
+ .event-card:hover { background: rgba(255,255,255,0.02); }
229
+ .event-card.active { background: rgba(255,255,255,0.04); border-left: 2px solid var(--sys-blue); padding-left: 14px; }
230
+
231
+ .ec-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
232
+ .ec-method { font-family: var(--font-mono); font-size: 11px; font-weight: 600; padding: 2px 6px; border-radius: 4px; background: rgba(255,255,255,0.05); color: var(--foreground);}
233
+ .ec-time { font-size: 11px; color: var(--muted-foreground); }
234
+ .ec-path { font-family: var(--font-mono); font-size: 12px; color: var(--foreground); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 8px;}
235
+ .ec-bottom { display: flex; align-items: center; gap: 8px; font-size: 11px; }
236
+ .provider-pill { margin-left: auto; }
237
+
238
+ .pill { padding: 2px 6px; border-radius: 4px; background: rgba(255,255,255,0.05); color: var(--muted-foreground); border: 1px solid var(--border); font-family: var(--font-mono); }
239
+ .pill-status-200 { color: var(--sys-green); border-color: rgba(74, 222, 128, 0.2); background: rgba(74, 222, 128, 0.05); }
240
+ .pill-status-400, .pill-status-500 { color: var(--sys-red); border-color: rgba(248, 113, 113, 0.2); background: rgba(248, 113, 113, 0.05); }
241
+
242
+ /* Details Pane */
243
+ .details-pane {
244
+ flex: 1;
245
+ display: flex;
246
+ flex-direction: column;
247
+ background: var(--background);
248
+ min-width: 0;
249
+ }
250
+ .empty-view {
251
+ flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--muted-foreground); gap: 12px;
252
+ }
253
+ .empty-view svg { opacity: 0.3; }
254
+
255
+ .details-content { flex: 1; display: flex; flex-direction: column; display: none; }
256
+ .details-content.visible { display: flex; }
257
+
258
+ .dep-header {
259
+ padding: 16px 24px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; background: rgba(255,255,255,0.01);
260
+ }
261
+ .dep-title { display: flex; align-items: center; gap: 12px; font-family: var(--font-mono); font-size: 14px; }
262
+ .dep-badges { display: flex; gap: 8px; }
263
+ .mini-action { border: 1px solid var(--border); background: rgba(255,255,255,0.04); color: var(--foreground); border-radius: 6px; padding: 3px 8px; font: 500 11px var(--font-sans); cursor: pointer; }
264
+ .mini-action:hover { background: rgba(255,255,255,0.08); }
265
+
266
+ .json-split {
267
+ flex: 1; display: flex; overflow: hidden;
268
+ }
269
+ .json-pane {
270
+ flex: 1; display: flex; flex-direction: column; border-right: 1px solid var(--border); min-width: 0;
271
+ }
272
+ .json-pane:last-child { border-right: none; }
273
+
274
+ .json-header {
275
+ padding: 8px 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between;
276
+ font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600; color: var(--muted-foreground); background: var(--background);
277
+ }
278
+ .json-body {
279
+ flex: 1; overflow: auto; padding: 16px; background: rgba(255,255,255,0.01);
280
+ }
281
+ .json-body pre { font-family: var(--font-mono); font-size: 12px; line-height: 1.6; color: var(--foreground); white-space: pre-wrap; word-wrap: break-word; }
282
+
283
+ /* Modal / Search */
284
+ .modal-overlay {
285
+ position: fixed; inset: 0; z-index: 50; display: none; align-items: flex-start; justify-content: center; padding: 12vh 16px 24px; background: rgba(0,0,0,0.4); backdrop-filter: blur(2px); overflow: auto;
286
+ }
287
+ .modal-overlay.open { display: flex; }
288
+ .modal-content {
289
+ position: relative; width: 100%; max-width: 600px; max-height: calc(100vh - 48px); background: var(--card); border: 1px solid var(--border); border-radius: 12px; box-shadow: 0 24px 48px rgba(0,0,0,0.4); overflow: hidden; animation: zoomIn 0.15s ease-out;
290
+ }
291
+ .modal-header {
292
+ display: flex; align-items: center; padding: 0 16px; border-bottom: 1px solid var(--border);
293
+ }
294
+ .modal-header svg { color: var(--muted-foreground); margin-right: 12px; }
295
+ .modal-input {
296
+ flex: 1; background: transparent; border: none; padding: 16px 0; outline: none; font-size: 14px; color: var(--foreground); font-family: var(--font-sans);
297
+ }
298
+ .modal-input::placeholder { color: rgba(161, 161, 170, 0.5); }
299
+ .modal-body { padding: 32px 16px; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--muted-foreground); gap: 8px; }
300
+ .rules-modal-content { max-width: 860px; }
301
+ .rules-modal-header { gap: 12px; padding: 12px 16px; }
302
+ .rules-title-block { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
303
+ .rules-title-block strong { font-size: 13px; color: var(--foreground); }
304
+ .rules-title-block span { font-size: 11px; color: var(--muted-foreground); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
305
+ .modal-action { border: 1px solid rgba(250,250,250,0.16); background: rgba(250,250,250,0.92); color: #18181b; border-radius: 6px; padding: 6px 10px; font: 500 12px var(--font-sans); cursor: pointer; }
306
+ .modal-action.ghost { background: rgba(255,255,255,0.04); color: var(--muted-foreground); }
307
+ .modal-action:hover { filter: brightness(1.08); }
308
+ .rules-body { max-height: 70vh; overflow: auto; padding: 16px; background: radial-gradient(circle at top left, rgba(96,165,250,0.08), transparent 28%), rgba(255,255,255,0.01); }
309
+ .rules-body pre { font-family: var(--font-mono); font-size: 12px; line-height: 1.7; white-space: pre-wrap; color: var(--foreground); }
310
+ .setup-modal-content { max-width: 980px; }
311
+ .setup-grid, .scenario-shell { display: grid; grid-template-columns: 300px minmax(0, 1fr); min-height: 560px; max-height: 74vh; }
312
+ .setup-list, .scenario-list { border-right: 1px solid var(--border); overflow: auto; padding: 10px; background: rgba(255,255,255,0.01); }
313
+ .setup-output, .scenario-output { max-height: none; height: 100%; }
314
+ .setup-item { width: 100%; border: 1px solid transparent; background: transparent; color: var(--muted-foreground); border-radius: 8px; padding: 10px; text-align: left; cursor: pointer; display: flex; flex-direction: column; gap: 4px; }
315
+ .setup-item:hover, .setup-item.active { background: rgba(255,255,255,0.05); border-color: var(--border); color: var(--foreground); }
316
+ .setup-item strong { font: 600 12px var(--font-sans); color: inherit; }
317
+ .setup-item span { font-size: 11px; line-height: 1.4; color: var(--muted-foreground); }
318
+ .scenario-card { border: 1px solid var(--border); border-radius: 10px; padding: 12px; margin-bottom: 10px; background: rgba(255,255,255,0.02); }
319
+ .scenario-card-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
320
+ .scenario-card strong { font-size: 13px; color: var(--foreground); }
321
+ .scenario-card p { font-size: 12px; line-height: 1.5; color: var(--muted-foreground); margin-bottom: 12px; }
322
+ .scenario-actions { display: flex; gap: 6px; }
323
+ .scenario-actions button { flex: 1; border: 1px solid var(--border); background: rgba(255,255,255,0.04); color: var(--foreground); border-radius: 6px; padding: 6px; font: 500 11px var(--font-sans); cursor: pointer; }
324
+ .scenario-actions button:hover { background: rgba(255,255,255,0.08); }
325
+
326
+ @media (max-width: 900px) {
327
+ .modal-overlay { padding-top: 24px; }
328
+ .setup-grid, .scenario-shell { grid-template-columns: 1fr; min-height: auto; }
329
+ .setup-list, .scenario-list { border-right: none; border-bottom: 1px solid var(--border); max-height: 260px; }
330
+ }
331
+
332
+ @media (max-height: 720px) {
333
+ .setup-grid, .scenario-shell { min-height: 0; }
334
+ }
335
+
336
+ @keyframes zoomIn { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }
337
+
338
+ /* Method Colors */
339
+ .m-get { color: var(--sys-blue); }
340
+ .m-post { color: var(--sys-green); }
341
+ .m-delete { color: var(--sys-red); }
342
+ .m-put, .m-patch { color: var(--sys-orange); }
343
+
344
+ /* Syntax Highlight */
345
+ .j-key { color: #86efac; opacity: 0.9; }
346
+ .j-str { color: #fca5a5; opacity: 0.9; }
347
+ .j-num { color: #7dd3fc; opacity: 0.9; }
348
+ .j-bool { color: #c4b5fd; opacity: 0.9; }
349
+ .j-null { color: var(--muted-foreground); font-style: italic; }
@@ -0,0 +1,3 @@
1
+ import type { NormalizedRequest } from "../proxy/requestNormalizer.js";
2
+ import type { ProviderName, ProviderErrorDetails } from "../providers/types.js";
3
+ export declare function validateRequest(request: NormalizedRequest, provider: ProviderName): ProviderErrorDetails | null;
@@ -0,0 +1,65 @@
1
+ import { isJsonObject } from "../utils/json.js";
2
+ export function validateRequest(request, provider) {
3
+ if (provider === "stripe") {
4
+ return validateStripe(request);
5
+ }
6
+ if (provider === "twilio") {
7
+ return validateTwilio(request);
8
+ }
9
+ if (provider === "resend") {
10
+ return validateResend(request);
11
+ }
12
+ return null;
13
+ }
14
+ function validateStripe(request) {
15
+ if (request.method !== "POST")
16
+ return null;
17
+ const body = isJsonObject(request.body) ? request.body : Object.fromEntries(new URLSearchParams(String(request.body)));
18
+ if (request.path.includes("/charges") || request.path.includes("/payment_intents")) {
19
+ if (!body["amount"]) {
20
+ return { status: 400, message: "Missing required param: amount.", type: "invalid_request_error", code: "parameter_missing", param: "amount" };
21
+ }
22
+ if (!body["currency"]) {
23
+ return { status: 400, message: "Missing required param: currency.", type: "invalid_request_error", code: "parameter_missing", param: "currency" };
24
+ }
25
+ }
26
+ if (request.path.includes("/customers") && Object.keys(body).length === 0) {
27
+ return { status: 400, message: "Missing required param: email or description.", type: "invalid_request_error", code: "parameter_missing", param: "email" };
28
+ }
29
+ return null;
30
+ }
31
+ function validateTwilio(request) {
32
+ if (request.method !== "POST")
33
+ return null;
34
+ const body = Array.isArray(request.body) ? {} : isJsonObject(request.body) ? request.body : Object.fromEntries(new URLSearchParams(String(request.body)));
35
+ if (request.path.includes("/Messages.json")) {
36
+ if (!body["To"]) {
37
+ return { status: 400, message: "A 'To' phone number is required.", code: 21604 };
38
+ }
39
+ if (!body["From"] && !body["MessagingServiceSid"]) {
40
+ return { status: 400, message: "A 'From' phone number is required.", code: 21603 };
41
+ }
42
+ if (!body["Body"] && !body["MediaUrl"]) {
43
+ return { status: 400, message: "Message body is required.", code: 21602 };
44
+ }
45
+ }
46
+ return null;
47
+ }
48
+ function validateResend(request) {
49
+ if (request.method !== "POST")
50
+ return null;
51
+ const body = isJsonObject(request.body) ? request.body : {};
52
+ if (request.path === "/emails" || request.path.startsWith("/emails/")) {
53
+ if (!body["from"]) {
54
+ return { status: 400, message: "Missing required field: from", type: "validation_error" };
55
+ }
56
+ if (!body["to"]) {
57
+ return { status: 400, message: "Missing required field: to", type: "validation_error" };
58
+ }
59
+ if (!body["subject"]) {
60
+ return { status: 400, message: "Missing required field: subject", type: "validation_error" };
61
+ }
62
+ }
63
+ return null;
64
+ }
65
+ //# sourceMappingURL=errorEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errorEngine.js","sourceRoot":"","sources":["../../src/errors/errorEngine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export type { ProviderErrorDetails } from "../providers/types.js";
2
+ export { createProviderError } from "./providerErrors.js";
3
+ export { validateRequest } from "./errorEngine.js";
@@ -0,0 +1,3 @@
1
+ export { createProviderError } from "./providerErrors.js";
2
+ export { validateRequest } from "./errorEngine.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ProviderName } from "../providers/types.js";
2
+ import type { ProviderErrorDetails } from "../providers/types.js";
3
+ export declare function createProviderError(provider: ProviderName, details: ProviderErrorDetails): unknown;
@@ -0,0 +1,6 @@
1
+ import { getProviderAdapter } from "../providers/registry.js";
2
+ export function createProviderError(provider, details) {
3
+ const adapter = getProviderAdapter(provider);
4
+ return adapter.formatError(details);
5
+ }
6
+ //# sourceMappingURL=providerErrors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providerErrors.js","sourceRoot":"","sources":["../../src/errors/providerErrors.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAG9D,MAAM,UAAU,mBAAmB,CAAC,QAAsB,EAAE,OAA6B;IACvF,MAAM,OAAO,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAC7C,OAAO,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { ProviderName } from "../providers/types.js";
2
+ export type FaultLabConfig = {
3
+ enabled: boolean;
4
+ latencyMs: number;
5
+ latencyMinMs: number;
6
+ latencyMaxMs: number;
7
+ errorRate: number;
8
+ statusCode: 429 | 500 | 502 | 503;
9
+ retryAfterSeconds: number;
10
+ };
11
+ export type FaultLabDecision = {
12
+ type: "off";
13
+ } | {
14
+ type: "delay";
15
+ latencyMs: number;
16
+ } | {
17
+ type: "error";
18
+ latencyMs: number;
19
+ statusCode: FaultLabConfig["statusCode"];
20
+ retryAfterSeconds: number;
21
+ body: unknown;
22
+ };
23
+ export declare function getFaultLabConfig(): FaultLabConfig;
24
+ export declare function updateFaultLabConfig(input: unknown): FaultLabConfig;
25
+ export declare function resetFaultLabForTests(): void;
26
+ export declare function decideFault(provider: ProviderName, random?: () => number): FaultLabDecision;
27
+ export declare function waitForFault(latencyMs: number): Promise<void>;
@@ -0,0 +1,89 @@
1
+ import { createProviderError } from "../errors/index.js";
2
+ const DEFAULT_CONFIG = {
3
+ enabled: false,
4
+ latencyMs: 0,
5
+ latencyMinMs: 2_000,
6
+ latencyMaxMs: 5_000,
7
+ errorRate: 15,
8
+ statusCode: 429,
9
+ retryAfterSeconds: 2
10
+ };
11
+ let config = { ...DEFAULT_CONFIG };
12
+ export function getFaultLabConfig() {
13
+ return { ...config };
14
+ }
15
+ export function updateFaultLabConfig(input) {
16
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
17
+ throw new Error("Fault Lab config must be a JSON object.");
18
+ }
19
+ const record = input;
20
+ const nextConfig = {
21
+ enabled: readBoolean(record.enabled, config.enabled),
22
+ latencyMs: readInteger(record.latencyMs, config.latencyMs, 0, 10_000),
23
+ latencyMinMs: readInteger(record.latencyMinMs, config.latencyMinMs, 0, 10_000),
24
+ latencyMaxMs: readInteger(record.latencyMaxMs, config.latencyMaxMs, 0, 10_000),
25
+ errorRate: readInteger(record.errorRate, config.errorRate, 0, 100),
26
+ statusCode: readStatusCode(record.statusCode, config.statusCode),
27
+ retryAfterSeconds: readInteger(record.retryAfterSeconds, config.retryAfterSeconds, 0, 120)
28
+ };
29
+ if (nextConfig.latencyMaxMs < nextConfig.latencyMinMs) {
30
+ throw new Error("Fault Lab latencyMaxMs must be greater than or equal to latencyMinMs.");
31
+ }
32
+ config = nextConfig;
33
+ return getFaultLabConfig();
34
+ }
35
+ export function resetFaultLabForTests() {
36
+ config = { ...DEFAULT_CONFIG };
37
+ }
38
+ export function decideFault(provider, random = Math.random) {
39
+ if (!config.enabled)
40
+ return { type: "off" };
41
+ const shouldInjectChaos = config.errorRate > 0 && random() * 100 < config.errorRate;
42
+ if (!shouldInjectChaos)
43
+ return { type: "off" };
44
+ const action = Math.floor(random() * 3);
45
+ if (action === 2)
46
+ return { type: "delay", latencyMs: pickLatency(random) };
47
+ const statusCode = action === 0 ? 429 : config.statusCode === 429 ? 503 : config.statusCode;
48
+ return {
49
+ type: "error",
50
+ latencyMs: config.latencyMs,
51
+ statusCode,
52
+ retryAfterSeconds: config.retryAfterSeconds,
53
+ body: createProviderError(provider, createFaultDetails(statusCode))
54
+ };
55
+ }
56
+ export function waitForFault(latencyMs) {
57
+ return latencyMs > 0 ? new Promise((resolve) => setTimeout(resolve, latencyMs)) : Promise.resolve();
58
+ }
59
+ function createFaultDetails(statusCode) {
60
+ if (statusCode === 429) {
61
+ return { status: 429, message: "Chaos Mode simulated rate limit. Please retry later.", type: "rate_limit_error", code: "rate_limit" };
62
+ }
63
+ return { status: statusCode, message: `Chaos Mode simulated upstream ${statusCode} response.`, type: "api_error", code: statusCode };
64
+ }
65
+ function pickLatency(random) {
66
+ if (config.latencyMs > 0)
67
+ return config.latencyMs;
68
+ const range = config.latencyMaxMs - config.latencyMinMs;
69
+ return config.latencyMinMs + Math.round(random() * range);
70
+ }
71
+ function readBoolean(value, fallback) {
72
+ return typeof value === "boolean" ? value : fallback;
73
+ }
74
+ function readInteger(value, fallback, min, max) {
75
+ if (value === undefined)
76
+ return fallback;
77
+ if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) {
78
+ throw new Error(`Expected integer between ${min} and ${max}.`);
79
+ }
80
+ return value;
81
+ }
82
+ function readStatusCode(value, fallback) {
83
+ if (value === undefined)
84
+ return fallback;
85
+ if (value === 429 || value === 500 || value === 502 || value === 503)
86
+ return value;
87
+ throw new Error("Fault Lab statusCode must be one of 429, 500, 502, or 503.");
88
+ }
89
+ //# sourceMappingURL=faultLab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"faultLab.js","sourceRoot":"","sources":["../../src/fault/faultLab.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { createServer } from "./server/createServer.js";
2
+ export type { ServerConfig } from "./config/serverConfig.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { createServer } from "./server/createServer.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1 @@
1
+ @import"https://fonts.googleapis.com/css2?family=Inter:wght@400;500&family=Source+Code+Pro:wght@400&display=swap";:root{--color-phosphor-green: #3ecf8e;--color-mint-pulse: #00c573;--color-forest-depth: #1f4b37;--color-midnight-emerald: #006239;--color-snow: #fafafa;--color-silver-mist: #b4b4b4;--color-smoke: #898989;--color-graphite: #4d4d4d;--color-slate: #393939;--color-charcoal: #2e2e2e;--color-ash: #242424;--color-obsidian: #121212;--font-circular: "Circular", Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-source-code-pro: "Source Code Pro", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;--text-caption: 12px;--leading-caption: 1.5;--tracking-caption: -.084px;--text-body-sm: 14px;--leading-body-sm: 1.43;--tracking-body-sm: -.098px;--text-body: 16px;--leading-body: 1.5;--tracking-body: -.112px;--text-subheading: 18px;--leading-subheading: 1.38;--tracking-subheading: -.126px;--text-heading-sm: 24px;--leading-heading-sm: 1.33;--tracking-heading-sm: -.168px;--text-heading: 36px;--leading-heading: 1.2;--tracking-heading: -.252px;--text-display: 72px;--leading-display: 1;--tracking-display: -.504px;--font-weight-regular: 400;--font-weight-medium: 500;--spacing-unit: 8px;--spacing-8: 8px;--spacing-16: 16px;--spacing-24: 24px;--spacing-32: 32px;--spacing-40: 40px;--spacing-48: 48px;--spacing-64: 64px;--spacing-80: 80px;--spacing-96: 96px;--spacing-112: 112px;--spacing-128: 128px;--spacing-224: 224px;--page-max-width: 1200px;--section-gap: 64px;--card-padding: 24px;--element-gap: 16px;--radius-lg: 8px;--radius-lg-2: 11px;--radius-2xl: 16px;--radius-full: 9999px;--radius-tags: 9999px;--radius-cards: 16px;--radius-inputs: 8px;--radius-buttons: 9999px;--shadow-sm: rgba(0, 0, 0, .1) 0px 4px 6px -1px, rgba(0, 0, 0, .1) 0px 2px 4px -2px;--surface-obsidian-canvas: #121212;--surface-ash-card: #242424;--surface-charcoal-border: #2e2e2e}@theme{ --color-phosphor-green: #3ecf8e; --color-mint-pulse: #00c573; --color-forest-depth: #1f4b37; --color-midnight-emerald: #006239; --color-snow: #fafafa; --color-silver-mist: #b4b4b4; --color-smoke: #898989; --color-graphite: #4d4d4d; --color-slate: #393939; --color-charcoal: #2e2e2e; --color-ash: #242424; --color-obsidian: #121212; --font-circular: "Circular", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; --font-source-code-pro: "Source Code Pro", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; --text-caption: 12px; --leading-caption: 1.5; --tracking-caption: -.084px; --text-body-sm: 14px; --leading-body-sm: 1.43; --tracking-body-sm: -.098px; --text-body: 16px; --leading-body: 1.5; --tracking-body: -.112px; --text-subheading: 18px; --leading-subheading: 1.38; --tracking-subheading: -.126px; --text-heading-sm: 24px; --leading-heading-sm: 1.33; --tracking-heading-sm: -.168px; --text-heading: 36px; --leading-heading: 1.2; --tracking-heading: -.252px; --text-display: 72px; --leading-display: 1; --tracking-display: -.504px; --spacing-8: 8px; --spacing-16: 16px; --spacing-24: 24px; --spacing-32: 32px; --spacing-40: 40px; --spacing-48: 48px; --spacing-64: 64px; --spacing-80: 80px; --spacing-96: 96px; --spacing-112: 112px; --spacing-128: 128px; --spacing-224: 224px; --radius-lg: 8px; --radius-lg-2: 11px; --radius-2xl: 16px; --radius-full: 9999px; --shadow-sm: rgba(0, 0, 0, .1) 0px 4px 6px -1px, rgba(0, 0, 0, .1) 0px 2px 4px -2px; }*{box-sizing:border-box}html{background:var(--color-obsidian);color:var(--color-snow);scroll-behavior:smooth}body{margin:0;background:var(--color-obsidian);color:var(--color-snow);font-family:var(--font-circular);font-size:var(--text-body);font-weight:var(--font-weight-regular);letter-spacing:var(--tracking-body);line-height:var(--leading-body);-webkit-font-smoothing:antialiased}a{color:inherit;text-decoration:none}button,a{-webkit-tap-highlight-color:transparent}:focus-visible{outline:1px solid var(--color-phosphor-green);outline-offset:4px}.site-shell{min-height:100vh;overflow:hidden;background:linear-gradient(var(--color-charcoal) 1px,transparent 1px),linear-gradient(90deg,var(--color-charcoal) 1px,transparent 1px),var(--color-obsidian);background-size:96px 96px;background-position:center top}.site-shell:before{content:"";position:fixed;top:0;right:0;bottom:0;left:0;pointer-events:none;background:radial-gradient(circle at 50% 0%,rgba(62,207,142,.1),transparent 28%);opacity:.8}.nav{position:fixed;top:0;left:0;right:0;z-index:20;display:flex;align-items:center;justify-content:space-between;height:64px;padding:0 max(24px,calc((100vw - var(--page-max-width)) / 2));background:#121212e0;border-bottom:1px solid var(--color-charcoal);-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.brand,.nav-links,.nav-actions,.hero-actions,.visual-topline,.control-row,.footer{display:flex;align-items:center}.brand{gap:10px;font-size:var(--text-body-sm);font-weight:var(--font-weight-medium);letter-spacing:var(--tracking-body-sm)}.logo-mark{width:24px;height:24px;color:var(--color-phosphor-green);fill:currentColor}.logo-mark circle{fill:var(--color-obsidian)}.nav-links{gap:24px;color:var(--color-silver-mist);font-size:var(--text-body-sm);line-height:var(--leading-body-sm)}.nav-links a:hover{color:var(--color-snow)}.nav-actions{gap:8px}.stars-pill,.announcement{border:1px solid var(--color-charcoal);border-radius:var(--radius-full);background:var(--color-ash);color:var(--color-snow)}.stars-pill{padding:6px 10px;font-size:var(--text-caption);line-height:var(--leading-caption)}.btn{position:relative;display:inline-flex;align-items:center;justify-content:center;min-height:38px;padding:8px 16px;border:1px solid var(--color-slate);border-radius:var(--radius-buttons);font-size:var(--text-body-sm);font-weight:var(--font-weight-medium);line-height:var(--leading-body-sm);letter-spacing:var(--tracking-body-sm);transition:border-color .18s ease,background-color .18s ease,transform .18s ease}.btn:hover{transform:translateY(-1px)}.btn-primary{border-color:var(--color-phosphor-green);background:var(--color-phosphor-green);color:var(--color-snow)}.btn-primary:hover{border-color:var(--color-forest-depth)}.btn-ghost{background:transparent;color:var(--color-snow)}.btn-ghost:hover{border-color:var(--color-graphite);background:#ffffff0a}.section{position:relative;z-index:1;width:min(var(--page-max-width),calc(100vw - 48px));margin:0 auto;padding:96px 0}.hero{min-height:100vh;padding-top:160px;text-align:center}.announcement{display:inline-flex;align-items:center;gap:8px;padding:6px 16px;color:var(--color-silver-mist);font-size:var(--text-caption);line-height:var(--leading-caption);animation:fade-up .5s ease both}.announcement span{color:var(--color-silver-mist)}.hero h1{max-width:970px;margin:28px auto 0;color:var(--color-snow);font-size:clamp(48px,7vw,var(--text-display));font-weight:var(--font-weight-regular);letter-spacing:var(--tracking-display);line-height:var(--leading-display);animation:fade-up .62s 80ms ease both}.hero h1 span{display:block;color:var(--color-phosphor-green)}.hero-subline{max-width:760px;margin:24px auto 0;color:var(--color-silver-mist);font-size:var(--text-body);line-height:var(--leading-body);animation:fade-up .62s .16s ease both}.hero-actions{justify-content:center;gap:12px;margin-top:32px;animation:fade-up .62s .24s ease both}.hero-command{width:min(460px,100%);margin:24px auto 0;animation:fade-up .62s .32s ease both}.code{margin:0;overflow:auto;border:1px solid var(--color-charcoal);border-radius:var(--radius-inputs);background:#0d0d0d;color:var(--color-snow);padding:12px 14px;text-align:left;font-family:var(--font-source-code-pro);font-size:var(--text-caption);font-weight:var(--font-weight-regular);letter-spacing:.1em;line-height:1.33}.product-visual{position:relative;max-width:1060px;margin:56px auto 0;padding:24px;border:1px solid var(--color-charcoal);border-radius:var(--radius-cards);background:var(--color-obsidian);text-align:left;animation:fade-up .62s .4s ease both}.product-visual:before,.product-visual:after{content:"";position:absolute;width:8px;height:8px;border-radius:9999px;background:var(--color-phosphor-green);box-shadow:0 0 24px #3ecf8e80}.product-visual:before{top:18px;right:24px}.product-visual:after{right:17%;bottom:28%;animation:pulse-dot 1.6s ease-in-out infinite}.visual-topline{justify-content:space-between;padding-bottom:16px;color:var(--color-smoke);font-family:var(--font-source-code-pro);font-size:var(--text-caption);letter-spacing:.1em}.live-dot{color:var(--color-phosphor-green)}.visual-grid{display:grid;grid-template-columns:1.1fr 1fr;grid-template-rows:auto auto;gap:16px}.panel{min-height:214px;border:1px solid var(--color-charcoal);border-radius:var(--radius-cards);background:linear-gradient(var(--color-charcoal) 1px,transparent 1px),linear-gradient(90deg,var(--color-charcoal) 1px,transparent 1px),#0d0d0d;background-size:32px 32px;padding:20px}.traffic-panel{grid-row:span 2}.panel-label,.eyebrow{display:inline-flex;margin-bottom:16px;color:var(--color-phosphor-green);font-size:var(--text-caption);font-weight:var(--font-weight-medium);letter-spacing:var(--tracking-caption);line-height:var(--leading-caption)}.request{display:grid;grid-template-columns:58px minmax(0,1fr) 72px;align-items:center;gap:12px;margin-top:10px;padding:12px;border:1px solid var(--color-charcoal);border-radius:var(--radius-inputs);color:var(--color-silver-mist);animation:scan-row 4.8s linear infinite}.request.active{border-color:var(--color-phosphor-green)}.request span,.request em,.control-row b{font-family:var(--font-source-code-pro);font-size:var(--text-caption);font-style:normal;letter-spacing:.1em}.request strong{overflow:hidden;color:var(--color-snow);font-size:var(--text-body-sm);font-weight:var(--font-weight-medium);text-overflow:ellipsis;white-space:nowrap}.failure-panel .code{height:148px;border-color:var(--color-slate);color:var(--color-silver-mist)}.control-panel{min-height:180px}.control-row{justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--color-charcoal);color:var(--color-silver-mist);font-size:var(--text-body-sm)}.control-row:last-child{border-bottom:0}.control-row b{color:var(--color-phosphor-green);font-weight:var(--font-weight-regular)}.product-visual p,.killer-use-case,.section-copy p,.feature-card p,.step-card p,.audience-grid p,.faq-grid p,.install-note,.footer p{color:var(--color-silver-mist)}.product-visual p{margin:18px 0 0;text-align:center;font-size:var(--text-body-sm)}.killer-use-case{max-width:760px;margin:24px auto 0;font-size:var(--text-body-sm);line-height:var(--leading-body-sm)}.section-copy{max-width:760px}.section-copy.centered{margin:0 auto 48px;text-align:center}.section-copy h2,.proof-card h2{margin:0;color:var(--color-snow);font-size:clamp(30px,4vw,var(--text-heading));font-weight:var(--font-weight-regular);letter-spacing:var(--tracking-heading);line-height:var(--leading-heading)}.section-copy p{margin:16px 0 0}.problem{display:grid;grid-template-columns:1fr 1fr;gap:64px;align-items:start}.problem-grid,.audience-grid,.faq-grid{display:grid;gap:16px}.problem-grid article,.audience-grid article,.faq-grid article,.step-card,.feature-card,.install-card,.proof-card{border:1px solid var(--color-charcoal);border-radius:var(--radius-cards);background:var(--color-obsidian);padding:var(--card-padding)}.problem-grid article{color:var(--color-silver-mist)}.steps-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.step-number{color:var(--color-smoke);font-family:var(--font-source-code-pro);font-size:var(--text-caption);letter-spacing:.1em}.step-card h3,.feature-card h3,.audience-grid h3,.install-card h3,.faq-grid h3{margin:14px 0 8px;color:var(--color-snow);font-size:var(--text-subheading);font-weight:var(--font-weight-medium);letter-spacing:var(--tracking-subheading);line-height:var(--leading-subheading)}.step-card .code{margin:18px 0}.feature-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.feature-card{display:flex;min-height:356px;flex-direction:column}.feature-icon .logo-mark{width:26px;height:26px}.line-art{position:relative;display:grid;grid-template-columns:repeat(3,1fr);gap:10px;min-height:116px;margin-top:auto;overflow:hidden;border:1px solid var(--color-charcoal);border-radius:var(--radius-inputs);background:#0d0d0d;padding:18px}.line-art span{border:1px solid var(--color-graphite);border-radius:var(--radius-inputs);transform:skewY(-8deg)}.line-art span:nth-child(2){border-color:var(--color-phosphor-green);animation:pulse-border 1.8s ease-in-out infinite}.feature-card .code{margin-top:16px}.audience-section{display:grid;grid-template-columns:.9fr 1.1fr;gap:48px;align-items:start}.install-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px}.install-card h3{margin-top:0}.install-card .code{min-height:74px}.install-note{margin:16px 0 0;text-align:center;font-size:var(--text-body-sm)}.proof-card{display:grid;grid-template-columns:.9fr 1.1fr;gap:48px;align-items:center;padding:40px}.storyboard{display:grid;gap:10px}.storyboard span{position:relative;display:block;padding:12px 14px 12px 36px;border:1px solid var(--color-charcoal);border-radius:var(--radius-inputs);color:var(--color-silver-mist);font-size:var(--text-body-sm)}.storyboard span:before{content:"";position:absolute;top:17px;left:14px;width:8px;height:8px;border-radius:9999px;background:var(--color-phosphor-green)}.faq-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.footer{position:relative;z-index:1;justify-content:space-between;width:min(var(--page-max-width),calc(100vw - 48px));margin:0 auto;padding:48px 0 64px;border-top:1px solid var(--color-charcoal)}.footer p{margin:0;font-size:var(--text-body-sm)}@keyframes fade-up{0%{opacity:0;transform:translateY(18px)}to{opacity:1;transform:translateY(0)}}@keyframes pulse-dot{0%,to{opacity:.45;transform:scale(1)}50%{opacity:1;transform:scale(1.6)}}@keyframes pulse-border{0%,to{opacity:.4}50%{opacity:1}}@keyframes scan-row{0%,38%,to{background:transparent}42%,50%{background:#3ecf8e0a}}@media (prefers-reduced-motion: reduce){*,*:before,*:after{animation-duration:1ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:1ms!important}}@media (max-width: 1060px){.nav-links,.stars-pill{display:none}.feature-grid,.steps-grid,.install-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.problem,.audience-section,.proof-card{grid-template-columns:1fr}}@media (max-width: 760px){.nav{height:auto;min-height:64px;padding:10px 16px}.nav-actions .btn-ghost{display:none}.section{width:min(100% - 32px,var(--page-max-width));padding:72px 0}.hero{padding-top:128px}.hero-actions{flex-direction:column}.hero-actions .btn{width:100%}.visual-grid,.feature-grid,.steps-grid,.install-grid,.faq-grid{grid-template-columns:1fr}.request{grid-template-columns:54px minmax(0,1fr)}.request em{display:none}.product-visual,.proof-card{padding:16px}.footer{align-items:flex-start;flex-direction:column;gap:18px;width:min(100% - 32px,var(--page-max-width))}}@media (max-width: 440px){.brand span{display:none}.nav-actions .btn{min-height:34px;padding:7px 12px}.hero h1{font-size:44px}}