@nacho-labs/nachos-embeddings 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nacho Labs LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,527 @@
1
+ # @nacho-labs/nachos-embeddings
2
+
3
+ Local, privacy-first vector embeddings and semantic search. Runs entirely locally using [Transformers.js](https://huggingface.co/docs/transformers.js) — no API keys, no cloud services, no costs.
4
+
5
+ ## Prerequisites
6
+
7
+ - **Node.js 18+** (uses ESM and top-level await)
8
+ - **Internet connection on first run** to download the embedding model (~25MB, cached permanently after that)
9
+
10
+ ## Features
11
+
12
+ - **Local embeddings** — No API calls, no costs, no rate limits
13
+ - **Semantic search** — Find similar text even with different wording
14
+ - **Privacy-first** — Data never leaves your machine
15
+ - **Lightweight** — ~25MB model download, then runs offline
16
+ - **Fast** — In-memory vector index with cosine similarity
17
+ - **Standalone** — Zero framework dependencies, use anywhere
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @nacho-labs/nachos-embeddings
23
+ ```
24
+
25
+ ## Quick start
26
+
27
+ ### Semantic search
28
+
29
+ ```typescript
30
+ import { SemanticSearch } from '@nacho-labs/nachos-embeddings';
31
+
32
+ const search = new SemanticSearch();
33
+ await search.init(); // Downloads model on first run (~25MB)
34
+
35
+ // Add documents
36
+ await search.addDocument({
37
+ id: 'pref-1',
38
+ text: 'User loves breakfast tacos',
39
+ metadata: { kind: 'preference' },
40
+ });
41
+
42
+ await search.addDocument({
43
+ id: 'pref-2',
44
+ text: 'User dislikes mushrooms',
45
+ metadata: { kind: 'preference' },
46
+ });
47
+
48
+ // Semantic search — finds results even with different wording
49
+ const results = await search.search('What does user like for morning meals?');
50
+ // [{ id: 'pref-1', similarity: 0.87, text: 'User loves breakfast tacos', metadata: { kind: 'preference' } }]
51
+ ```
52
+
53
+ ### Low-level API
54
+
55
+ For more control, use `Embedder` and `VectorStore` directly:
56
+
57
+ ```typescript
58
+ import { Embedder, VectorStore } from '@nacho-labs/nachos-embeddings';
59
+
60
+ const embedder = new Embedder();
61
+ await embedder.init();
62
+
63
+ // Generate embeddings
64
+ const vec1 = await embedder.embed('Hello world');
65
+ const vec2 = await embedder.embed('Hi there');
66
+ console.log(vec1.length); // 384 (vector dimensions)
67
+
68
+ // Store and search vectors
69
+ const store = new VectorStore();
70
+ store.add('doc1', vec1, { content: 'Hello world' });
71
+ store.add('doc2', vec2, { content: 'Hi there' });
72
+
73
+ const queryVec = await embedder.embed('Greetings');
74
+ const results = store.search(queryVec, { limit: 5 });
75
+ // [{ id: 'doc2', similarity: 0.92, metadata: { content: 'Hi there' } }, ...]
76
+ ```
77
+
78
+ ## Configuration
79
+
80
+ ### Model selection
81
+
82
+ ```typescript
83
+ const search = new SemanticSearch({
84
+ model: 'Xenova/all-MiniLM-L6-v2', // Default — fast, 384 dimensions
85
+ // model: 'Xenova/all-mpnet-base-v2', // Higher quality, slower
86
+ cacheDir: '.cache/transformers',
87
+ progressLogging: true,
88
+ });
89
+ ```
90
+
91
+ ### Similarity threshold
92
+
93
+ ```typescript
94
+ const search = new SemanticSearch({
95
+ minSimilarity: 0.7, // Default (range: 0-1)
96
+ });
97
+
98
+ // Or override per search
99
+ const results = await search.search('query', {
100
+ minSimilarity: 0.8,
101
+ limit: 10,
102
+ });
103
+ ```
104
+
105
+ ### Filtering
106
+
107
+ ```typescript
108
+ const results = await search.search('query', {
109
+ filter: (metadata) => metadata?.kind === 'preference',
110
+ });
111
+ ```
112
+
113
+ ## Persistence
114
+
115
+ Export and import for saving to disk:
116
+
117
+ ```typescript
118
+ import { readFile, writeFile } from 'node:fs/promises';
119
+
120
+ // Export
121
+ const data = search.export();
122
+ await writeFile('embeddings.json', JSON.stringify(data));
123
+
124
+ // Import
125
+ const saved = JSON.parse(await readFile('embeddings.json', 'utf-8'));
126
+ search.import(saved);
127
+ ```
128
+
129
+ ## Using with Claude Code
130
+
131
+ Give Claude Code semantic memory over your project — decisions, patterns, and
132
+ context that persists across sessions and gets recalled by meaning, not keywords.
133
+
134
+ ### Why this matters
135
+
136
+ Claude Code can read files, but it doesn't *remember* what you discussed
137
+ yesterday or *know* which files are most relevant to your current question.
138
+ nachos-embeddings adds a local semantic search layer through
139
+ [MCP](https://modelcontextprotocol.io) (Model Context Protocol):
140
+
141
+ - **Semantic recall** — "How do we handle auth?" finds your auth docs even if
142
+ no file is literally named "auth"
143
+ - **Project memory** — Index decisions, patterns, and context that survive
144
+ across sessions
145
+ - **Zero token spend** — Search happens locally, not in the LLM context window
146
+
147
+ ### 1. Create the MCP server
148
+
149
+ ```bash
150
+ mkdir my-semantic-search && cd my-semantic-search
151
+ npm init -y
152
+ npm install @nacho-labs/nachos-embeddings @modelcontextprotocol/sdk zod tsx
153
+ ```
154
+
155
+ Create `server.ts`:
156
+
157
+ ```typescript
158
+ import { McpServer, StdioServerTransport } from '@modelcontextprotocol/sdk/server/index.js';
159
+ import { z } from 'zod';
160
+ import { SemanticSearch } from '@nacho-labs/nachos-embeddings';
161
+ import { readFile, writeFile } from 'node:fs/promises';
162
+ import { existsSync } from 'node:fs';
163
+
164
+ const STORE_PATH = '.semantic-store.json';
165
+
166
+ // Initialize search engine
167
+ const search = new SemanticSearch({ minSimilarity: 0.6 });
168
+
169
+ try {
170
+ await search.init();
171
+ } catch (err) {
172
+ console.error('Failed to load embedding model. Is this the first run? An internet connection is required to download the model (~25MB).', err);
173
+ process.exit(1);
174
+ }
175
+
176
+ // Load persisted index if it exists
177
+ if (existsSync(STORE_PATH)) {
178
+ const data = JSON.parse(await readFile(STORE_PATH, 'utf-8'));
179
+ search.import(data);
180
+ }
181
+
182
+ async function persist() {
183
+ await writeFile(STORE_PATH, JSON.stringify(search.export()));
184
+ }
185
+
186
+ // Create MCP server
187
+ const server = new McpServer(
188
+ { name: 'semantic-search', version: '1.0.0' },
189
+ { capabilities: { logging: {} } }
190
+ );
191
+
192
+ // Tool: Search for semantically similar content
193
+ server.registerTool(
194
+ 'semantic_search',
195
+ {
196
+ title: 'Semantic Search',
197
+ description: 'Search indexed documents by meaning. Use this to find relevant context, past decisions, code patterns, or any previously indexed content.',
198
+ inputSchema: z.object({
199
+ query: z.string().describe('Natural language search query'),
200
+ limit: z.number().optional().default(5).describe('Max results to return'),
201
+ }),
202
+ },
203
+ async ({ query, limit }) => {
204
+ const results = await search.search(query, { limit });
205
+ if (results.length === 0) {
206
+ return { content: [{ type: 'text', text: 'No relevant results found.' }] };
207
+ }
208
+ const formatted = results.map((r, i) =>
209
+ `${i + 1}. [${(r.similarity * 100).toFixed(0)}%] ${r.text}${r.metadata ? `\n metadata: ${JSON.stringify(r.metadata)}` : ''}`
210
+ ).join('\n\n');
211
+ return { content: [{ type: 'text', text: formatted }] };
212
+ }
213
+ );
214
+
215
+ // Tool: Add a document to the index
216
+ server.registerTool(
217
+ 'semantic_index',
218
+ {
219
+ title: 'Index Document',
220
+ description: 'Add a document to the semantic search index. Use this to remember decisions, patterns, file summaries, or any context worth recalling later.',
221
+ inputSchema: z.object({
222
+ id: z.string().describe('Unique document ID'),
223
+ text: z.string().describe('The text content to index'),
224
+ metadata: z.record(z.string()).optional().describe('Optional key-value metadata'),
225
+ }),
226
+ },
227
+ async ({ id, text, metadata }) => {
228
+ await search.addDocument({ id, text, metadata });
229
+ await persist();
230
+ return { content: [{ type: 'text', text: `Indexed "${id}" (${search.size()} total documents)` }] };
231
+ }
232
+ );
233
+
234
+ // Tool: Remove a document
235
+ server.registerTool(
236
+ 'semantic_remove',
237
+ {
238
+ title: 'Remove Document',
239
+ description: 'Remove a document from the semantic search index by ID.',
240
+ inputSchema: z.object({
241
+ id: z.string().describe('Document ID to remove'),
242
+ }),
243
+ },
244
+ async ({ id }) => {
245
+ const removed = search.remove(id);
246
+ if (removed) await persist();
247
+ return {
248
+ content: [{ type: 'text', text: removed ? `Removed "${id}"` : `"${id}" not found` }],
249
+ };
250
+ }
251
+ );
252
+
253
+ // Tool: Get index stats
254
+ server.registerTool(
255
+ 'semantic_stats',
256
+ {
257
+ title: 'Index Stats',
258
+ description: 'Get the number of documents currently in the semantic search index.',
259
+ inputSchema: z.object({}),
260
+ },
261
+ async () => ({
262
+ content: [{ type: 'text', text: `${search.size()} documents indexed` }],
263
+ })
264
+ );
265
+
266
+ // Start
267
+ const transport = new StdioServerTransport();
268
+ await server.connect(transport);
269
+ ```
270
+
271
+ ### 2. Register with Claude Code
272
+
273
+ ```bash
274
+ claude mcp add --transport stdio semantic-search -- npx tsx /absolute/path/to/server.ts
275
+ ```
276
+
277
+ Or add to your project's `.mcp.json`:
278
+
279
+ ```json
280
+ {
281
+ "mcpServers": {
282
+ "semantic-search": {
283
+ "type": "stdio",
284
+ "command": "npx",
285
+ "args": ["tsx", "/absolute/path/to/server.ts"]
286
+ }
287
+ }
288
+ }
289
+ ```
290
+
291
+ ### 3. Use it
292
+
293
+ Once registered, Claude Code gains four new tools:
294
+
295
+ | Tool | What it does |
296
+ | ------ | ------------- |
297
+ | `semantic_search` | Find relevant content by meaning |
298
+ | `semantic_index` | Add content to the index |
299
+ | `semantic_remove` | Remove content by ID |
300
+ | `semantic_stats` | Check index size |
301
+
302
+ Claude Code will use these automatically when relevant. You can also prompt it:
303
+
304
+ ```text
305
+ > Search my indexed context for how we handle authentication
306
+ > Index this decision: we chose JWT over sessions because...
307
+ > What do we know about the database schema?
308
+ ```
309
+
310
+ ### What to index
311
+
312
+ The power comes from what you put in. High-value patterns:
313
+
314
+ **Architecture decisions** — "ADR-012: We separated embeddings into a standalone
315
+ repo to prove they're a market differentiator."
316
+
317
+ **File summaries** — "gateway.ts: Main entry point. Initializes NATS, loads the
318
+ policy engine, sets up routing, manages context via ContextManager."
319
+
320
+ **Conventions** — "All containers use node:22-alpine, non-root user, read-only
321
+ filesystem, dropped capabilities."
322
+
323
+ **Debugging insights** — "NATS timeouts are usually the bus container not being
324
+ ready. Check docker compose health checks first."
325
+
326
+ ### How it works
327
+
328
+ ```text
329
+ You ask: "How do we handle rate limiting?"
330
+ |
331
+ Claude Code calls: semantic_search("rate limiting")
332
+ |
333
+ nachos-embeddings converts query to a 384-dimension vector
334
+ |
335
+ Cosine similarity search against all indexed vectors
336
+ |
337
+ Returns: "We throttle API requests using sliding windows..."
338
+ (matched by meaning, not keywords)
339
+ ```
340
+
341
+ The model understands meaning, not just keywords:
342
+
343
+ | Query | Finds |
344
+ | ------- | ------- |
345
+ | "rate limiting" | "We throttle API requests using sliding windows" |
346
+ | "how to deploy" | "Production runs via docker compose up with..." |
347
+ | "error handling pattern" | "We use Result types instead of try/catch for..." |
348
+
349
+ ## Use cases
350
+
351
+ - **Give your chatbot memory across sessions** — Index facts, preferences, and decisions for semantic recall
352
+ - **Search documents by meaning** — Find relevant content even when the wording is completely different
353
+ - **Match user questions to known answers** — Build FAQ systems without keyword engineering
354
+ - **Detect near-duplicate content** — Find semantically similar text for deduplication
355
+ - **Build "more like this" features** — Recommend similar items based on text similarity
356
+
357
+ ## Performance
358
+
359
+ | Operation | Time (approx) |
360
+ |-----------|---------------|
361
+ | Model init (first time) | ~2-5 seconds |
362
+ | Model init (cached) | ~500ms |
363
+ | Embed single text | ~10-50ms |
364
+ | Embed batch (100 texts) | ~500ms-2s |
365
+ | Search 1000 vectors | ~5-10ms |
366
+
367
+ **Memory:**
368
+ - Model: ~100MB (loaded once, reused)
369
+ - Each vector: ~1.5KB (384 floats)
370
+ - 1000 documents: ~1.5MB vectors + original text
371
+
372
+ The in-memory store works well up to ~10K documents. Beyond that, consider a
373
+ dedicated vector database like [Qdrant](https://qdrant.tech) or
374
+ [Milvus](https://milvus.io).
375
+
376
+ ## Comparison
377
+
378
+ | Feature | nachos-embeddings | OpenAI | Pinecone |
379
+ |---------|-------------------|--------|----------|
380
+ | Cost | Free | ~$0.0001/1k chars | ~$70/month |
381
+ | Setup | `npm install` | API key | Account + API |
382
+ | Privacy | 100% local | Cloud | Cloud |
383
+ | Offline | Yes | No | No |
384
+ | Quality | Good (85-90%) | Excellent (95%) | N/A (database) |
385
+
386
+ ## API reference
387
+
388
+ ### SemanticSearch
389
+
390
+ High-level API combining embedder and vector store.
391
+
392
+ | Method | Description |
393
+ | -------- | ------------- |
394
+ | `new SemanticSearch(config?)` | Create instance. Config: `model`, `minSimilarity`, `cacheDir`, `progressLogging` |
395
+ | `init()` | Load the embedding model. **Must be called before any other method.** |
396
+ | `addDocument(doc)` | Add `{ id, text, metadata? }` to the index |
397
+ | `addDocuments(docs)` | Batch add (more efficient for multiple documents) |
398
+ | `search(query, opts?)` | Search by meaning. Options: `limit`, `minSimilarity`, `filter` |
399
+ | `remove(id)` | Remove a document by ID |
400
+ | `clear()` | Remove all documents |
401
+ | `size()` | Get document count |
402
+ | `export()` | Export all documents and vectors for persistence |
403
+ | `import(data)` | Import previously exported data |
404
+ | `isInitialized()` | Check if the model is loaded |
405
+
406
+ ### Embedder
407
+
408
+ Low-level text-to-vector conversion.
409
+
410
+ | Method | Description |
411
+ | -------- | ------------- |
412
+ | `new Embedder(config?)` | Create instance. Config: `model`, `cacheDir`, `progressLogging` |
413
+ | `init()` | Load the model |
414
+ | `embed(text)` | Convert text to a 384-dimension vector |
415
+ | `embedBatch(texts)` | Convert multiple texts (batched for efficiency) |
416
+ | `getDimension()` | Get vector dimension (384 for default model) |
417
+ | `isInitialized()` | Check if ready |
418
+ | `getConfig()` | Get current configuration |
419
+
420
+ ### VectorStore
421
+
422
+ In-memory vector storage and similarity search.
423
+
424
+ | Method | Description |
425
+ | -------- | ------------- |
426
+ | `new VectorStore(config?)` | Create instance. Config: `minSimilarity`, `defaultLimit` |
427
+ | `add(id, vector, metadata?)` | Store a vector |
428
+ | `addBatch(entries)` | Store multiple vectors |
429
+ | `search(queryVector, opts?)` | Find similar vectors. Options: `limit`, `minSimilarity`, `filter` |
430
+ | `get(id)` | Retrieve a vector by ID |
431
+ | `remove(id)` | Remove by ID |
432
+ | `clear()` | Remove all |
433
+ | `size()` | Get count |
434
+ | `keys()` | Get all IDs |
435
+ | `export()` / `import(entries)` | Persistence |
436
+
437
+ ### Utilities
438
+
439
+ | Function | Description |
440
+ | ---------- | ------------- |
441
+ | `cosineSimilarity(a, b)` | Cosine similarity between two vectors (-1 to 1) |
442
+ | `normalizeVector(v)` | Normalize to unit length |
443
+ | `getGlobalEmbedder(config?)` | Shared singleton instance (avoids loading model twice) |
444
+ | `resetGlobalEmbedder()` | Reset the singleton (useful for testing) |
445
+
446
+ ## Advanced
447
+
448
+ ### Batch processing
449
+
450
+ ```typescript
451
+ const texts = ['Document 1', 'Document 2', /* ... */];
452
+ const embeddings = await embedder.embedBatch(texts);
453
+ store.addBatch(texts.map((text, i) => ({
454
+ id: `doc-${i}`,
455
+ vector: embeddings[i],
456
+ metadata: { text },
457
+ })));
458
+ ```
459
+
460
+ ### Global singleton
461
+
462
+ ```typescript
463
+ import { getGlobalEmbedder } from '@nacho-labs/nachos-embeddings';
464
+
465
+ const embedder = getGlobalEmbedder();
466
+ await embedder.init(); // Only loads model once
467
+ ```
468
+
469
+ ### Vector utilities
470
+
471
+ ```typescript
472
+ import { cosineSimilarity, normalizeVector } from '@nacho-labs/nachos-embeddings';
473
+
474
+ const similarity = cosineSimilarity([0.5, 0.3, 0.8], [0.6, 0.4, 0.7]);
475
+ const normalized = normalizeVector([3, 4]); // Unit vector
476
+ ```
477
+
478
+ ## Troubleshooting
479
+
480
+ ### Model download fails
481
+
482
+ The embedding model (~25MB) is downloaded on first run and cached at
483
+ `.cache/transformers/`. If the download fails:
484
+
485
+ - Check your internet connection
486
+ - Check disk space
487
+ - Try setting a custom cache directory: `new SemanticSearch({ cacheDir: '/tmp/models' })`
488
+
489
+ ### "Embedder not initialized" error
490
+
491
+ You must call `init()` before `embed()`, `embedBatch()`, `search()`, or
492
+ `addDocument()`. This is an async operation that loads the model:
493
+
494
+ ```typescript
495
+ const search = new SemanticSearch();
496
+ await search.init(); // Don't forget this
497
+ ```
498
+
499
+ ### Out of memory with large batches
500
+
501
+ Reduce batch size by processing in chunks:
502
+
503
+ ```typescript
504
+ const CHUNK = 100;
505
+ for (let i = 0; i < texts.length; i += CHUNK) {
506
+ const batch = texts.slice(i, i + CHUNK);
507
+ const vecs = await embedder.embedBatch(batch);
508
+ store.addBatch(batch.map((text, j) => ({
509
+ id: `doc-${i + j}`,
510
+ vector: vecs[j],
511
+ metadata: { text },
512
+ })));
513
+ }
514
+ ```
515
+
516
+ ## Development
517
+
518
+ ```bash
519
+ npm install
520
+ npm run build
521
+ npm test
522
+ npm run typecheck
523
+ ```
524
+
525
+ ## License
526
+
527
+ MIT
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Text embedding using Transformers.js (local, no API needed)
3
+ */
4
+ export interface EmbedderConfig {
5
+ /**
6
+ * Model to use for embeddings
7
+ * @default 'Xenova/all-MiniLM-L6-v2'
8
+ */
9
+ model?: string;
10
+ /**
11
+ * Cache directory for downloaded models
12
+ * @default '.cache/transformers'
13
+ */
14
+ cacheDir?: string;
15
+ /**
16
+ * Enable progress logging during model download
17
+ * @default false
18
+ */
19
+ progressLogging?: boolean;
20
+ }
21
+ /**
22
+ * Text embedder using local transformer models
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const embedder = new Embedder();
27
+ * await embedder.init();
28
+ *
29
+ * const vector = await embedder.embed('Hello world');
30
+ * console.log(vector); // [0.23, -0.15, 0.87, ...] (384 dimensions)
31
+ *
32
+ * const batch = await embedder.embedBatch(['Text 1', 'Text 2', 'Text 3']);
33
+ * console.log(batch.length); // 3
34
+ * ```
35
+ */
36
+ export declare class Embedder {
37
+ private pipeline;
38
+ private config;
39
+ private initialized;
40
+ constructor(config?: EmbedderConfig);
41
+ /**
42
+ * Initialize the embedder (downloads model on first run)
43
+ * Call this before using embed() or embedBatch()
44
+ */
45
+ init(): Promise<void>;
46
+ /**
47
+ * Generate embedding vector for a single text
48
+ *
49
+ * @throws Error if not initialized (call init() first)
50
+ */
51
+ embed(text: string): Promise<number[]>;
52
+ /**
53
+ * Generate embeddings for multiple texts in batch
54
+ * More efficient than calling embed() multiple times
55
+ */
56
+ embedBatch(texts: string[]): Promise<number[][]>;
57
+ /**
58
+ * Get the dimension of the embedding vectors
59
+ * Returns null if not initialized
60
+ */
61
+ getDimension(): Promise<number | null>;
62
+ /**
63
+ * Check if the embedder is ready to use
64
+ */
65
+ isInitialized(): boolean;
66
+ /**
67
+ * Get current configuration
68
+ */
69
+ getConfig(): Readonly<Required<EmbedderConfig>>;
70
+ }
71
+ export declare function getGlobalEmbedder(config?: EmbedderConfig): Embedder;
72
+ /**
73
+ * Reset the global embedder (useful for testing)
74
+ */
75
+ export declare function resetGlobalEmbedder(): void;
76
+ //# sourceMappingURL=embedder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedder.d.ts","sourceRoot":"","sources":["../src/embedder.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,WAAW,CAAS;gBAEhB,MAAM,GAAE,cAAmB;IAYvC;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B;;;;OAIG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAa5C;;;OAGG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IAwBtD;;;OAGG;IACG,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAS5C;;OAEG;IACH,aAAa,IAAI,OAAO;IAIxB;;OAEG;IACH,SAAS,IAAI,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;CAGhD;AAQD,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,QAAQ,CAKnE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C"}