@showrun/core 0.1.0 → 0.1.1

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 (42) hide show
  1. package/dist/__tests__/config.test.d.ts +2 -0
  2. package/dist/__tests__/config.test.d.ts.map +1 -0
  3. package/dist/__tests__/config.test.js +164 -0
  4. package/dist/__tests__/httpReplay.test.d.ts +2 -0
  5. package/dist/__tests__/httpReplay.test.d.ts.map +1 -0
  6. package/dist/__tests__/httpReplay.test.js +306 -0
  7. package/dist/__tests__/requestSnapshot.test.d.ts +2 -0
  8. package/dist/__tests__/requestSnapshot.test.d.ts.map +1 -0
  9. package/dist/__tests__/requestSnapshot.test.js +323 -0
  10. package/dist/browserLauncher.d.ts.map +1 -1
  11. package/dist/browserLauncher.js +7 -1
  12. package/dist/config.d.ts +82 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +255 -0
  15. package/dist/dsl/interpreter.d.ts +9 -0
  16. package/dist/dsl/interpreter.d.ts.map +1 -1
  17. package/dist/dsl/interpreter.js +9 -3
  18. package/dist/dsl/stepHandlers.d.ts +7 -0
  19. package/dist/dsl/stepHandlers.d.ts.map +1 -1
  20. package/dist/dsl/stepHandlers.js +81 -0
  21. package/dist/dsl/types.d.ts +6 -0
  22. package/dist/dsl/types.d.ts.map +1 -1
  23. package/dist/dsl/validation.d.ts.map +1 -1
  24. package/dist/dsl/validation.js +7 -0
  25. package/dist/httpReplay.d.ts +43 -0
  26. package/dist/httpReplay.d.ts.map +1 -0
  27. package/dist/httpReplay.js +102 -0
  28. package/dist/index.d.ts +3 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +3 -0
  31. package/dist/jsonPackValidator.d.ts.map +1 -1
  32. package/dist/jsonPackValidator.js +12 -3
  33. package/dist/loader.d.ts.map +1 -1
  34. package/dist/loader.js +4 -0
  35. package/dist/requestSnapshot.d.ts +91 -0
  36. package/dist/requestSnapshot.d.ts.map +1 -0
  37. package/dist/requestSnapshot.js +200 -0
  38. package/dist/runner.d.ts.map +1 -1
  39. package/dist/runner.js +189 -10
  40. package/dist/types.d.ts +5 -0
  41. package/dist/types.d.ts.map +1 -1
  42. package/package.json +2 -2
@@ -0,0 +1,323 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { isSnapshotStale, validateResponse, applyOverrides, extractTopLevelKeys, detectSensitiveHeaders, } from '../requestSnapshot.js';
3
+ function makeSnapshot(overrides) {
4
+ return {
5
+ stepId: 'test_step',
6
+ capturedAt: Date.now(),
7
+ ttl: null,
8
+ request: {
9
+ method: 'GET',
10
+ url: 'https://api.example.com/data',
11
+ headers: { 'content-type': 'application/json' },
12
+ body: null,
13
+ },
14
+ responseValidation: {
15
+ expectedStatus: 200,
16
+ expectedContentType: 'application/json',
17
+ expectedKeys: ['results', 'total'],
18
+ },
19
+ sensitiveHeaders: [],
20
+ ...overrides,
21
+ };
22
+ }
23
+ // ---------------------------------------------------------------------------
24
+ // isSnapshotStale
25
+ // ---------------------------------------------------------------------------
26
+ describe('isSnapshotStale', () => {
27
+ it('returns false when ttl is null (indefinite)', () => {
28
+ const snap = makeSnapshot({ ttl: null });
29
+ expect(isSnapshotStale(snap)).toBe(false);
30
+ });
31
+ it('returns false when ttl has not expired', () => {
32
+ const snap = makeSnapshot({ capturedAt: Date.now(), ttl: 60_000 });
33
+ expect(isSnapshotStale(snap)).toBe(false);
34
+ });
35
+ it('returns true when ttl has expired', () => {
36
+ const snap = makeSnapshot({
37
+ capturedAt: Date.now() - 120_000,
38
+ ttl: 60_000,
39
+ });
40
+ expect(isSnapshotStale(snap)).toBe(true);
41
+ });
42
+ it('returns true when capturedAt is far in the past', () => {
43
+ const snap = makeSnapshot({
44
+ capturedAt: 0,
45
+ ttl: 1000,
46
+ });
47
+ expect(isSnapshotStale(snap)).toBe(true);
48
+ });
49
+ });
50
+ // ---------------------------------------------------------------------------
51
+ // validateResponse
52
+ // ---------------------------------------------------------------------------
53
+ describe('validateResponse', () => {
54
+ it('passes for matching response', () => {
55
+ const snap = makeSnapshot();
56
+ const response = {
57
+ status: 200,
58
+ contentType: 'application/json; charset=utf-8',
59
+ body: JSON.stringify({ results: [1, 2], total: 2 }),
60
+ };
61
+ expect(validateResponse(snap, response)).toEqual({ valid: true });
62
+ });
63
+ it('fails on mismatched status', () => {
64
+ const snap = makeSnapshot();
65
+ const response = {
66
+ status: 403,
67
+ contentType: 'application/json',
68
+ body: '{}',
69
+ };
70
+ const result = validateResponse(snap, response);
71
+ expect(result.valid).toBe(false);
72
+ expect(result.reason).toContain('Expected status 200');
73
+ });
74
+ it('fails on mismatched content type', () => {
75
+ const snap = makeSnapshot();
76
+ const response = {
77
+ status: 200,
78
+ contentType: 'text/html',
79
+ body: '<html></html>',
80
+ };
81
+ const result = validateResponse(snap, response);
82
+ expect(result.valid).toBe(false);
83
+ expect(result.reason).toContain('content-type');
84
+ });
85
+ it('fails on missing expected keys', () => {
86
+ const snap = makeSnapshot();
87
+ const response = {
88
+ status: 200,
89
+ contentType: 'application/json',
90
+ body: JSON.stringify({ results: [] }),
91
+ };
92
+ const result = validateResponse(snap, response);
93
+ expect(result.valid).toBe(false);
94
+ expect(result.reason).toContain('total');
95
+ });
96
+ it('passes when expectedKeys is empty', () => {
97
+ const snap = makeSnapshot({
98
+ responseValidation: {
99
+ expectedStatus: 200,
100
+ expectedContentType: 'application/json',
101
+ expectedKeys: [],
102
+ },
103
+ });
104
+ const response = {
105
+ status: 200,
106
+ contentType: 'application/json',
107
+ body: '{"anything": true}',
108
+ };
109
+ expect(validateResponse(snap, response)).toEqual({ valid: true });
110
+ });
111
+ it('passes when contentType is undefined (no check)', () => {
112
+ const snap = makeSnapshot();
113
+ const response = {
114
+ status: 200,
115
+ contentType: undefined,
116
+ body: JSON.stringify({ results: [], total: 0 }),
117
+ };
118
+ expect(validateResponse(snap, response)).toEqual({ valid: true });
119
+ });
120
+ it('fails when JSON parsing fails but expectedKeys are set', () => {
121
+ const snap = makeSnapshot();
122
+ const response = {
123
+ status: 200,
124
+ contentType: 'application/json',
125
+ body: 'not json',
126
+ };
127
+ const result = validateResponse(snap, response);
128
+ expect(result.valid).toBe(false);
129
+ expect(result.reason).toContain('not valid JSON');
130
+ });
131
+ });
132
+ // ---------------------------------------------------------------------------
133
+ // applyOverrides
134
+ // ---------------------------------------------------------------------------
135
+ describe('applyOverrides', () => {
136
+ it('returns original request when no overrides', () => {
137
+ const snap = makeSnapshot();
138
+ const result = applyOverrides(snap, {}, {});
139
+ expect(result.url).toBe('https://api.example.com/data');
140
+ expect(result.method).toBe('GET');
141
+ expect(result.body).toBeNull();
142
+ });
143
+ it('applies setQuery overrides with template resolution', () => {
144
+ const snap = makeSnapshot({
145
+ overrides: {
146
+ setQuery: { batch: '{{inputs.batch}}' },
147
+ },
148
+ });
149
+ const result = applyOverrides(snap, { batch: 'W24' }, {});
150
+ expect(result.url).toContain('batch=W24');
151
+ });
152
+ it('applies setHeaders overrides', () => {
153
+ const snap = makeSnapshot({
154
+ overrides: {
155
+ setHeaders: { 'x-custom': '{{vars.token}}' },
156
+ },
157
+ });
158
+ const result = applyOverrides(snap, {}, { token: 'abc123' });
159
+ expect(result.headers['x-custom']).toBe('abc123');
160
+ });
161
+ it('applies urlReplace overrides', () => {
162
+ const snap = makeSnapshot({
163
+ request: {
164
+ method: 'GET',
165
+ url: 'https://api.example.com/v1/items',
166
+ headers: {},
167
+ body: null,
168
+ },
169
+ overrides: {
170
+ urlReplace: [{ find: 'v1', replace: 'v2' }],
171
+ },
172
+ });
173
+ const result = applyOverrides(snap, {}, {});
174
+ expect(result.url).toBe('https://api.example.com/v2/items');
175
+ });
176
+ it('applies bodyReplace overrides with template', () => {
177
+ const snap = makeSnapshot({
178
+ request: {
179
+ method: 'POST',
180
+ url: 'https://api.example.com/search',
181
+ headers: { 'content-type': 'application/json' },
182
+ body: '{"query":"W24"}',
183
+ },
184
+ overrides: {
185
+ bodyReplace: [{ find: 'W24', replace: '{{inputs.batch}}' }],
186
+ },
187
+ });
188
+ const result = applyOverrides(snap, { batch: 'S25' }, {});
189
+ expect(result.body).toBe('{"query":"S25"}');
190
+ });
191
+ it('applies multiple overrides in order', () => {
192
+ const snap = makeSnapshot({
193
+ request: {
194
+ method: 'POST',
195
+ url: 'https://api.example.com/search?page=1',
196
+ headers: { 'content-type': 'application/json' },
197
+ body: '{"batch":"W24"}',
198
+ },
199
+ overrides: {
200
+ setQuery: { page: '{{inputs.page}}' },
201
+ bodyReplace: [{ find: 'W24', replace: '{{inputs.batch}}' }],
202
+ },
203
+ });
204
+ const result = applyOverrides(snap, { batch: 'S25', page: '2' }, {});
205
+ expect(result.url).toContain('page=2');
206
+ expect(result.body).toBe('{"batch":"S25"}');
207
+ });
208
+ it('applies direct url override (after urlReplace)', () => {
209
+ const snap = makeSnapshot({
210
+ request: {
211
+ method: 'GET',
212
+ url: 'https://api.example.com/v1/items',
213
+ headers: {},
214
+ body: null,
215
+ },
216
+ overrides: {
217
+ url: 'https://api.example.com/{{vars.version}}/items',
218
+ },
219
+ });
220
+ const result = applyOverrides(snap, {}, { version: 'v3' });
221
+ expect(result.url).toBe('https://api.example.com/v3/items');
222
+ });
223
+ it('applies direct body override (after bodyReplace)', () => {
224
+ const snap = makeSnapshot({
225
+ request: {
226
+ method: 'POST',
227
+ url: 'https://api.example.com/search',
228
+ headers: { 'content-type': 'application/json' },
229
+ body: '{"old":"data"}',
230
+ },
231
+ overrides: {
232
+ body: '{"query":"{{inputs.query}}"}',
233
+ },
234
+ });
235
+ const result = applyOverrides(snap, { query: 'test' }, {});
236
+ expect(result.body).toBe('{"query":"test"}');
237
+ });
238
+ it('direct url override takes precedence over urlReplace', () => {
239
+ const snap = makeSnapshot({
240
+ request: {
241
+ method: 'GET',
242
+ url: 'https://api.example.com/v1/items',
243
+ headers: {},
244
+ body: null,
245
+ },
246
+ overrides: {
247
+ urlReplace: [{ find: 'v1', replace: 'v2' }],
248
+ url: 'https://override.example.com/items',
249
+ },
250
+ });
251
+ const result = applyOverrides(snap, {}, {});
252
+ expect(result.url).toBe('https://override.example.com/items');
253
+ });
254
+ it('resolves Nunjucks filters like | urlencode in templates', () => {
255
+ const snap = makeSnapshot({
256
+ request: {
257
+ method: 'POST',
258
+ url: 'https://api.example.com/search',
259
+ headers: { 'content-type': 'application/json' },
260
+ body: '{"query":"placeholder"}',
261
+ },
262
+ overrides: {
263
+ body: '{"batch":"{{inputs.batch | urlencode}}"}',
264
+ },
265
+ });
266
+ const result = applyOverrides(snap, { batch: 'Winter 2025' }, {});
267
+ expect(result.body).toBe('{"batch":"Winter%202025"}');
268
+ });
269
+ it('resolves secrets in templates', () => {
270
+ const snap = makeSnapshot({
271
+ overrides: {
272
+ setHeaders: { 'x-api-key': '{{secret.API_KEY}}' },
273
+ },
274
+ });
275
+ const result = applyOverrides(snap, {}, {}, { API_KEY: 'my-secret-key' });
276
+ expect(result.headers['x-api-key']).toBe('my-secret-key');
277
+ });
278
+ });
279
+ // ---------------------------------------------------------------------------
280
+ // extractTopLevelKeys
281
+ // ---------------------------------------------------------------------------
282
+ describe('extractTopLevelKeys', () => {
283
+ it('extracts keys from JSON object', () => {
284
+ const keys = extractTopLevelKeys('{"a":1,"b":2,"c":3}');
285
+ expect(keys).toEqual(['a', 'b', 'c']);
286
+ });
287
+ it('returns empty for JSON array', () => {
288
+ expect(extractTopLevelKeys('[1,2,3]')).toEqual([]);
289
+ });
290
+ it('returns empty for invalid JSON', () => {
291
+ expect(extractTopLevelKeys('not json')).toEqual([]);
292
+ });
293
+ it('returns empty for null/undefined', () => {
294
+ expect(extractTopLevelKeys(null)).toEqual([]);
295
+ expect(extractTopLevelKeys(undefined)).toEqual([]);
296
+ });
297
+ });
298
+ // ---------------------------------------------------------------------------
299
+ // detectSensitiveHeaders
300
+ // ---------------------------------------------------------------------------
301
+ describe('detectSensitiveHeaders', () => {
302
+ it('detects authorization header', () => {
303
+ const result = detectSensitiveHeaders({
304
+ Authorization: 'Bearer token',
305
+ 'content-type': 'application/json',
306
+ });
307
+ expect(result).toContain('Authorization');
308
+ expect(result).not.toContain('content-type');
309
+ });
310
+ it('detects cookie header (case-insensitive)', () => {
311
+ const result = detectSensitiveHeaders({
312
+ cookie: 'session=abc',
313
+ });
314
+ expect(result).toContain('cookie');
315
+ });
316
+ it('returns empty for no sensitive headers', () => {
317
+ const result = detectSensitiveHeaders({
318
+ 'content-type': 'text/html',
319
+ accept: '*/*',
320
+ });
321
+ expect(result).toEqual([]);
322
+ });
323
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"browserLauncher.d.ts","sourceRoot":"","sources":["../src/browserLauncher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrF;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,OAAO,EAAE,cAAc,CAAC;IACxB;;OAEG;IACH,IAAI,EAAE,IAAI,CAAC;IACX;;OAEG;IACH,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB;;OAEG;IACH,MAAM,EAAE,aAAa,CAAC;IACtB;;OAEG;IACH,WAAW,EAAE,kBAAkB,CAAC;IAChC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC,CAgCxF;AA4HD;;GAEG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAgBtF"}
1
+ {"version":3,"file":"browserLauncher.d.ts","sourceRoot":"","sources":["../src/browserLauncher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAGpF,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrF;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,OAAO,EAAE,cAAc,CAAC;IACxB;;OAEG;IACH,IAAI,EAAE,IAAI,CAAC;IACX;;OAEG;IACH,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB;;OAEG;IACH,MAAM,EAAE,aAAa,CAAC;IACtB;;OAEG;IACH,WAAW,EAAE,kBAAkB,CAAC;IAChC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC,CAqCxF;AA4HD;;GAEG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAgBtF"}
@@ -5,6 +5,8 @@
5
5
  * (Chromium, Camoufox) and persistence modes.
6
6
  */
7
7
  import { chromium } from 'playwright';
8
+ import { existsSync } from 'fs';
9
+ import { join } from 'path';
8
10
  import { resolveBrowserDataDir } from './browserPersistence.js';
9
11
  /**
10
12
  * Launches a browser with the specified configuration
@@ -15,7 +17,11 @@ import { resolveBrowserDataDir } from './browserPersistence.js';
15
17
  export async function launchBrowser(config) {
16
18
  const { browserSettings = {}, headless = true, sessionId, packPath, } = config;
17
19
  const engine = browserSettings.engine ?? 'camoufox';
18
- const persistence = browserSettings.persistence ?? 'none';
20
+ let persistence = browserSettings.persistence ?? 'none';
21
+ // Auto-upgrade to profile persistence when packPath has an existing .browser-profile/
22
+ if (persistence === 'none' && packPath && existsSync(join(packPath, '.browser-profile'))) {
23
+ persistence = 'profile';
24
+ }
19
25
  // Resolve user data directory based on persistence mode
20
26
  const userDataDir = resolveBrowserDataDir({
21
27
  persistence,
@@ -0,0 +1,82 @@
1
+ /**
2
+ * System-wide configuration for ShowRun.
3
+ *
4
+ * Layered config discovery:
5
+ * Built-in defaults < global config.json < project config.json < .env < real env vars
6
+ *
7
+ * Config directory search order (lowest → highest priority):
8
+ * Linux/macOS: $XDG_CONFIG_HOME/showrun/ → ~/.showrun/ → ancestor .showrun/ → cwd/.showrun/
9
+ * Windows: %APPDATA%\showrun\ → ancestor .showrun\ → cwd\.showrun\
10
+ */
11
+ export interface LlmProviderConfig {
12
+ apiKey?: string;
13
+ model?: string;
14
+ baseUrl?: string;
15
+ }
16
+ export interface ShowRunConfig {
17
+ llm?: {
18
+ provider?: string;
19
+ anthropic?: LlmProviderConfig;
20
+ openai?: LlmProviderConfig;
21
+ };
22
+ agent?: {
23
+ maxBrowserRounds?: number;
24
+ debug?: boolean;
25
+ transcriptLogging?: boolean;
26
+ };
27
+ prompts?: {
28
+ teachChatSystemPrompt?: string;
29
+ explorationAgentPromptPath?: string;
30
+ };
31
+ }
32
+ export interface ResolvedConfigPaths {
33
+ config: ShowRunConfig;
34
+ /** Config files that were loaded, in priority order (lowest first) */
35
+ loadedFiles: string[];
36
+ /** All directories that were searched */
37
+ searchedDirs: string[];
38
+ }
39
+ /**
40
+ * Recursively merge `override` into `base`, returning a new object.
41
+ * - Primitives and arrays in override replace base values.
42
+ * - Null/undefined values in override are skipped.
43
+ * - Nested plain objects are merged recursively.
44
+ */
45
+ export declare function deepMerge<T>(base: T, override: T): T;
46
+ /**
47
+ * Returns an ordered list of config directories to search, lowest priority first.
48
+ */
49
+ export declare function discoverConfigDirs(): string[];
50
+ /**
51
+ * Load all `config.json` files from discovered directories, merge them, and
52
+ * return the result along with metadata about which files were loaded.
53
+ */
54
+ export declare function loadConfig(): ResolvedConfigPaths;
55
+ /**
56
+ * Apply config values to `process.env`, only setting vars that are not already present.
57
+ */
58
+ export declare function applyConfigToEnv(config: ShowRunConfig): void;
59
+ /**
60
+ * Resolve a filename by searching local paths first (cwd, then ancestors),
61
+ * then config directories (highest priority first).
62
+ * Local files always win over config dir copies.
63
+ * Returns the first existing path, or null.
64
+ */
65
+ export declare function resolveFilePath(filename: string): string | null;
66
+ /**
67
+ * Get the global config directory path for the current platform.
68
+ */
69
+ export declare function getGlobalConfigDir(): string;
70
+ /**
71
+ * Ensure a system prompt file exists in a config directory.
72
+ * If the prompt was found outside config dirs (e.g. repo root), copy it
73
+ * into the global config dir so it's available when running from any directory.
74
+ */
75
+ export declare function ensureSystemPromptInConfigDir(filename: string, sourcePath: string): string;
76
+ /**
77
+ * Load config, merge, and apply to process.env.
78
+ * This is the single call sites should use (e.g. CLI bootstrap).
79
+ */
80
+ export declare function initConfig(): ResolvedConfigPaths;
81
+ export declare const DEFAULT_CONFIG_TEMPLATE: ShowRunConfig;
82
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAUH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE;QACJ,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,iBAAiB,CAAC;QAC9B,MAAM,CAAC,EAAE,iBAAiB,CAAC;KAC5B,CAAC;IACF,KAAK,CAAC,EAAE;QACN,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,CAAC;IACF,OAAO,CAAC,EAAE;QACR,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAC/B,0BAA0B,CAAC,EAAE,MAAM,CAAC;KACrC,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,aAAa,CAAC;IACtB,sEAAsE;IACtE,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,yCAAyC;IACzC,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAID;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CA0BpD;AA0BD;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CA4B7C;AAMD;;;GAGG;AACH,wBAAgB,UAAU,IAAI,mBAAmB,CAmBhD;AA6BD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAQ5D;AAID;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsB/D;AAID;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAS3C;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAmB1F;AAID;;;GAGG;AACH,wBAAgB,UAAU,IAAI,mBAAmB,CAShD;AAID,eAAO,MAAM,uBAAuB,EAAE,aAerC,CAAC"}