cuemap 0.2.0__py3-none-any.whl

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.
cuemap/__init__.py ADDED
@@ -0,0 +1,33 @@
1
+ """
2
+ CueMap: Redis for AI Agents
3
+
4
+ A high-performance, temporal-associative memory store.
5
+ You give us the cues, we give you the right memory at the right time.
6
+
7
+ Example:
8
+ >>> from cuemap import CueMap
9
+ >>>
10
+ >>> client = CueMap()
11
+ >>>
12
+ >>> # Add a memory with cues
13
+ >>> client.add("The server password is abc123", cues=["server", "password"])
14
+ >>>
15
+ >>> # Recall by cues
16
+ >>> results = client.recall(["server", "password"])
17
+ >>> print(results[0].content)
18
+ """
19
+
20
+ from .client import CueMap, AsyncCueMap
21
+ from .models import Memory, RecallResult
22
+ from .exceptions import CueMapError, ConnectionError, AuthenticationError
23
+
24
+ __version__ = "0.2.0"
25
+ __all__ = [
26
+ "CueMap",
27
+ "AsyncCueMap",
28
+ "Memory",
29
+ "RecallResult",
30
+ "CueMapError",
31
+ "ConnectionError",
32
+ "AuthenticationError",
33
+ ]
cuemap/client.py ADDED
@@ -0,0 +1,316 @@
1
+ """Pure CueMap client - no magic, just speed."""
2
+
3
+ import httpx
4
+ from typing import List, Optional, Dict, Any
5
+
6
+ from .models import Memory, RecallResult
7
+ from .exceptions import CueMapError, ConnectionError, AuthenticationError
8
+
9
+
10
+ class CueMap:
11
+ """
12
+ Pure CueMap client.
13
+
14
+ No auto-cue extraction. No semantic matching. Just fast memory storage.
15
+
16
+ Example:
17
+ >>> client = CueMap()
18
+ >>> client.add("Important note", cues=["work", "urgent"])
19
+ >>> results = client.recall(["work"])
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ url: str = "http://localhost:8080",
25
+ api_key: Optional[str] = None,
26
+ project_id: Optional[str] = None,
27
+ timeout: float = 30.0
28
+ ):
29
+ """
30
+ Initialize CueMap client.
31
+
32
+ Args:
33
+ url: CueMap server URL
34
+ api_key: Optional API key for authentication
35
+ project_id: Optional project ID for multi-tenancy
36
+ timeout: Request timeout in seconds
37
+ """
38
+ self.url = url
39
+ self.api_key = api_key
40
+ self.project_id = project_id
41
+
42
+ self.client = httpx.Client(
43
+ base_url=url,
44
+ timeout=timeout
45
+ )
46
+
47
+ def _headers(self) -> Dict[str, str]:
48
+ """Get request headers."""
49
+ headers = {}
50
+ if self.api_key:
51
+ headers["x-api-key"] = self.api_key
52
+ return headers
53
+
54
+ def _endpoint(self, path: str) -> str:
55
+ """Get endpoint with optional project_id."""
56
+ if self.project_id:
57
+ return f"/v1/{self.project_id}{path}"
58
+ return path
59
+
60
+ def add(
61
+ self,
62
+ content: str,
63
+ cues: List[str],
64
+ metadata: Optional[Dict[str, Any]] = None
65
+ ) -> str:
66
+ """
67
+ Add a memory.
68
+
69
+ Args:
70
+ content: Memory content
71
+ cues: List of cues (tags) for retrieval
72
+ metadata: Optional metadata
73
+
74
+ Returns:
75
+ Memory ID
76
+
77
+ Example:
78
+ >>> client.add(
79
+ ... "Meeting with John at 3pm",
80
+ ... cues=["meeting", "john", "calendar"]
81
+ ... )
82
+ """
83
+ response = self.client.post(
84
+ self._endpoint("/memories"),
85
+ json={
86
+ "content": content,
87
+ "cues": cues,
88
+ "metadata": metadata or {}
89
+ },
90
+ headers=self._headers()
91
+ )
92
+
93
+ if response.status_code == 401:
94
+ raise AuthenticationError("Invalid API key")
95
+ elif response.status_code != 200:
96
+ raise CueMapError(f"Failed to add memory: {response.status_code}")
97
+
98
+ return response.json()["id"]
99
+
100
+ def recall(
101
+ self,
102
+ cues: List[str],
103
+ limit: int = 10,
104
+ auto_reinforce: bool = False
105
+ ) -> List[RecallResult]:
106
+ """
107
+ Recall memories by cues.
108
+
109
+ Args:
110
+ cues: List of cues to search for
111
+ limit: Maximum results to return
112
+ auto_reinforce: Automatically reinforce retrieved memories
113
+
114
+ Returns:
115
+ List of recall results
116
+
117
+ Example:
118
+ >>> results = client.recall(["meeting", "john"])
119
+ >>> for r in results:
120
+ ... print(r.content)
121
+ """
122
+ response = self.client.post(
123
+ self._endpoint("/recall"),
124
+ json={
125
+ "cues": cues,
126
+ "limit": limit,
127
+ "auto_reinforce": auto_reinforce
128
+ },
129
+ headers=self._headers()
130
+ )
131
+
132
+ if response.status_code != 200:
133
+ raise CueMapError(f"Failed to recall: {response.status_code}")
134
+
135
+ results = response.json()["results"]
136
+ return [RecallResult(**r) for r in results]
137
+
138
+ def reinforce(self, memory_id: str, cues: List[str]) -> bool:
139
+ """
140
+ Reinforce a memory on specific cue pathways.
141
+
142
+ Args:
143
+ memory_id: Memory ID
144
+ cues: Cues to reinforce on
145
+
146
+ Returns:
147
+ Success status
148
+ """
149
+ response = self.client.patch(
150
+ self._endpoint(f"/memories/{memory_id}/reinforce"),
151
+ json={"cues": cues},
152
+ headers=self._headers()
153
+ )
154
+
155
+ return response.status_code == 200
156
+
157
+ def get(self, memory_id: str) -> Memory:
158
+ """Get a memory by ID."""
159
+ response = self.client.get(
160
+ self._endpoint(f"/memories/{memory_id}"),
161
+ headers=self._headers()
162
+ )
163
+
164
+ if response.status_code == 404:
165
+ raise CueMapError(f"Memory not found: {memory_id}")
166
+ elif response.status_code != 200:
167
+ raise CueMapError(f"Failed to get memory: {response.status_code}")
168
+
169
+ return Memory(**response.json())
170
+
171
+ def stats(self) -> Dict[str, Any]:
172
+ """Get server statistics."""
173
+ response = self.client.get(
174
+ self._endpoint("/stats"),
175
+ headers=self._headers()
176
+ )
177
+
178
+ return response.json()
179
+
180
+ def close(self):
181
+ """Close the client."""
182
+ self.client.close()
183
+
184
+ def __enter__(self):
185
+ return self
186
+
187
+ def __exit__(self, exc_type, exc_val, exc_tb):
188
+ self.close()
189
+
190
+
191
+ class AsyncCueMap:
192
+ """
193
+ Async CueMap client.
194
+
195
+ Example:
196
+ >>> async with AsyncCueMap() as client:
197
+ ... await client.add("Note", cues=["work"])
198
+ ... results = await client.recall(["work"])
199
+ """
200
+
201
+ def __init__(
202
+ self,
203
+ url: str = "http://localhost:8080",
204
+ api_key: Optional[str] = None,
205
+ project_id: Optional[str] = None,
206
+ timeout: float = 30.0
207
+ ):
208
+ self.url = url
209
+ self.api_key = api_key
210
+ self.project_id = project_id
211
+
212
+ self.client = httpx.AsyncClient(
213
+ base_url=url,
214
+ timeout=timeout
215
+ )
216
+
217
+ def _headers(self) -> Dict[str, str]:
218
+ headers = {}
219
+ if self.api_key:
220
+ headers["x-api-key"] = self.api_key
221
+ return headers
222
+
223
+ def _endpoint(self, path: str) -> str:
224
+ if self.project_id:
225
+ return f"/v1/{self.project_id}{path}"
226
+ return path
227
+
228
+ async def add(
229
+ self,
230
+ content: str,
231
+ cues: List[str],
232
+ metadata: Optional[Dict[str, Any]] = None
233
+ ) -> str:
234
+ """Add a memory (async)."""
235
+ response = await self.client.post(
236
+ self._endpoint("/memories"),
237
+ json={
238
+ "content": content,
239
+ "cues": cues,
240
+ "metadata": metadata or {}
241
+ },
242
+ headers=self._headers()
243
+ )
244
+
245
+ if response.status_code == 401:
246
+ raise AuthenticationError("Invalid API key")
247
+ elif response.status_code != 200:
248
+ raise CueMapError(f"Failed to add memory: {response.status_code}")
249
+
250
+ return response.json()["id"]
251
+
252
+ async def recall(
253
+ self,
254
+ cues: List[str],
255
+ limit: int = 10,
256
+ auto_reinforce: bool = False
257
+ ) -> List[RecallResult]:
258
+ """Recall memories (async)."""
259
+ response = await self.client.post(
260
+ self._endpoint("/recall"),
261
+ json={
262
+ "cues": cues,
263
+ "limit": limit,
264
+ "auto_reinforce": auto_reinforce
265
+ },
266
+ headers=self._headers()
267
+ )
268
+
269
+ if response.status_code != 200:
270
+ raise CueMapError(f"Failed to recall: {response.status_code}")
271
+
272
+ results = response.json()["results"]
273
+ return [RecallResult(**r) for r in results]
274
+
275
+ async def reinforce(self, memory_id: str, cues: List[str]) -> bool:
276
+ """Reinforce a memory (async)."""
277
+ response = await self.client.patch(
278
+ self._endpoint(f"/memories/{memory_id}/reinforce"),
279
+ json={"cues": cues},
280
+ headers=self._headers()
281
+ )
282
+
283
+ return response.status_code == 200
284
+
285
+ async def get(self, memory_id: str) -> Memory:
286
+ """Get a memory by ID (async)."""
287
+ response = await self.client.get(
288
+ self._endpoint(f"/memories/{memory_id}"),
289
+ headers=self._headers()
290
+ )
291
+
292
+ if response.status_code == 404:
293
+ raise CueMapError(f"Memory not found: {memory_id}")
294
+ elif response.status_code != 200:
295
+ raise CueMapError(f"Failed to get memory: {response.status_code}")
296
+
297
+ return Memory(**response.json())
298
+
299
+ async def stats(self) -> Dict[str, Any]:
300
+ """Get server statistics (async)."""
301
+ response = await self.client.get(
302
+ self._endpoint("/stats"),
303
+ headers=self._headers()
304
+ )
305
+
306
+ return response.json()
307
+
308
+ async def close(self):
309
+ """Close the client."""
310
+ await self.client.aclose()
311
+
312
+ async def __aenter__(self):
313
+ return self
314
+
315
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
316
+ await self.close()
cuemap/exceptions.py ADDED
@@ -0,0 +1,16 @@
1
+ """Exceptions."""
2
+
3
+
4
+ class CueMapError(Exception):
5
+ """Base exception."""
6
+ pass
7
+
8
+
9
+ class ConnectionError(CueMapError):
10
+ """Connection failed."""
11
+ pass
12
+
13
+
14
+ class AuthenticationError(CueMapError):
15
+ """Authentication failed."""
16
+ pass
cuemap/models.py ADDED
@@ -0,0 +1,24 @@
1
+ """Data models."""
2
+
3
+ from typing import Dict, List, Any
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class Memory(BaseModel):
8
+ """A memory object."""
9
+
10
+ id: str
11
+ content: str
12
+ cues: List[str] = Field(default_factory=list)
13
+ metadata: Dict[str, Any] = Field(default_factory=dict)
14
+
15
+
16
+ class RecallResult(BaseModel):
17
+ """Result from a recall operation."""
18
+
19
+ memory_id: str
20
+ content: str
21
+ score: float
22
+ intersection_count: int
23
+ recency_score: float
24
+ metadata: Dict[str, Any] = Field(default_factory=dict)
@@ -0,0 +1,274 @@
1
+ Metadata-Version: 2.4
2
+ Name: cuemap
3
+ Version: 0.2.0
4
+ Summary: Redis for AI Agents - High-performance temporal-associative memory
5
+ License: MIT
6
+ Project-URL: Homepage, https://cuemap.dev
7
+ Project-URL: Documentation, https://docs.cuemap.dev
8
+ Project-URL: Repository, https://github.com/cuemap/cuemap
9
+ Keywords: ai,memory,agents,redis,temporal
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: httpx>=0.25.0
14
+ Requires-Dist: pydantic>=2.0.0
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
17
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
18
+ Dynamic: license-file
19
+
20
+ # CueMap: Redis for AI Agents
21
+
22
+ **High-performance, temporal-associative memory store.**
23
+
24
+ You give us the cues, we give you the right memory at the right time.
25
+
26
+ ## Why CueMap?
27
+
28
+ Redis doesn't auto-serialize your data. It doesn't guess what you mean. It just stores keys and values **blazing fast**.
29
+
30
+ CueMap is the same philosophy for AI agent memory:
31
+ - ✅ **You control the cues** (tags)
32
+ - ✅ **We handle the speed** (sub-millisecond)
33
+ - ✅ **No magic** (predictable behavior)
34
+ - ✅ **No dependencies** (5KB SDK)
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install cuemap
40
+ ```
41
+
42
+ That's it. No ML models. No transformers. Just pure speed.
43
+
44
+ ## Quick Start
45
+
46
+ ```python
47
+ from cuemap import CueMap
48
+
49
+ client = CueMap()
50
+
51
+ # Add a memory with cues
52
+ client.add(
53
+ "The server password is abc123",
54
+ cues=["server", "password", "credentials"]
55
+ )
56
+
57
+ # Recall by cues
58
+ results = client.recall(["server", "password"])
59
+ print(results[0].content)
60
+ # Output: "The server password is abc123"
61
+ ```
62
+
63
+ ## Core API
64
+
65
+ ### Add Memory
66
+
67
+ ```python
68
+ memory_id = client.add(
69
+ content="Meeting with John at 3pm",
70
+ cues=["meeting", "john", "calendar", "today"]
71
+ )
72
+ ```
73
+
74
+ ### Recall Memories
75
+
76
+ ```python
77
+ results = client.recall(
78
+ cues=["meeting", "john"],
79
+ limit=10
80
+ )
81
+
82
+ for result in results:
83
+ print(f"{result.content} (score: {result.score})")
84
+ ```
85
+
86
+ ### Reinforce Memory
87
+
88
+ ```python
89
+ # Make a memory more accessible
90
+ client.reinforce(memory_id, cues=["important", "urgent"])
91
+ ```
92
+
93
+ ## How It Works
94
+
95
+ ### Temporal-Associative Retrieval
96
+
97
+ CueMap uses **Iterative Deepening Intersection**:
98
+
99
+ 1. **Intersection**: Memories matching multiple cues rank higher
100
+ 2. **Recency**: Recent memories are more accessible
101
+ 3. **Reinforcement**: Frequently accessed memories stay "front of mind"
102
+
103
+ ```python
104
+ # Add memories
105
+ client.add("Pizza recipe", cues=["food", "italian"])
106
+ client.add("Pasta recipe", cues=["food", "italian"])
107
+ client.add("Sushi recipe", cues=["food", "japanese"])
108
+
109
+ # Query with multiple cues
110
+ results = client.recall(["food", "italian"])
111
+ # Returns: Pizza and Pasta (both match 2 cues)
112
+ # Sushi is filtered out (only matches 1 cue)
113
+ ```
114
+
115
+ ### Performance
116
+
117
+ - **Write P99**: 0.33ms
118
+ - **Read P99**: 0.37ms
119
+ - **Throughput**: 2,900+ ops/sec
120
+ - **Accuracy**: 100% (validated on 120 test scenarios)
121
+
122
+ ## Recipes
123
+
124
+ ### Recipe 1: Use with OpenAI
125
+
126
+ ```python
127
+ from cuemap import CueMap
128
+ import openai
129
+
130
+ client = CueMap()
131
+
132
+ def store_with_ai_tags(content: str):
133
+ # Let OpenAI extract the cues
134
+ response = openai.chat.completions.create(
135
+ model="gpt-4",
136
+ messages=[{
137
+ "role": "system",
138
+ "content": "Extract 3-5 search tags from the text. Return as JSON array."
139
+ }, {
140
+ "role": "user",
141
+ "content": content
142
+ }]
143
+ )
144
+
145
+ cues = response.choices[0].message.content # ["tag1", "tag2", ...]
146
+
147
+ # Store in CueMap
148
+ return client.add(content, cues=cues)
149
+
150
+ # Usage
151
+ store_with_ai_tags("I need to buy groceries this weekend")
152
+ # OpenAI extracts: ["shopping", "groceries", "weekend", "todo"]
153
+ ```
154
+
155
+ ### Recipe 2: Use with LangChain
156
+
157
+ ```python
158
+ from cuemap import CueMap
159
+ from langchain.memory import BaseMemory
160
+
161
+ class CueMapMemory(BaseMemory):
162
+ def __init__(self, cue_extractor):
163
+ self.client = CueMap()
164
+ self.extract_cues = cue_extractor
165
+
166
+ def save_context(self, inputs, outputs):
167
+ context = f"User: {inputs['input']}\nAI: {outputs['output']}"
168
+ cues = self.extract_cues(context)
169
+ self.client.add(context, cues=cues)
170
+
171
+ def load_memory_variables(self, inputs):
172
+ cues = self.extract_cues(inputs['input'])
173
+ results = self.client.recall(cues, limit=5)
174
+ return {"history": "\n".join([r.content for r in results])}
175
+ ```
176
+
177
+ ### Recipe 3: Manual Cues (Production)
178
+
179
+ ```python
180
+ # For production: explicit, predictable cues
181
+ client.add(
182
+ "Deploy command: kubectl apply -f deployment.yaml",
183
+ cues=["deployment", "kubernetes", "commands", "devops"]
184
+ )
185
+
186
+ client.add(
187
+ "API endpoint: https://api.example.com/v1/users",
188
+ cues=["api", "endpoint", "users", "documentation"]
189
+ )
190
+
191
+ # Query with specific cues
192
+ client.recall(["deployment", "kubernetes"])
193
+ client.recall(["api", "users"])
194
+ ```
195
+
196
+ ## Configuration
197
+
198
+ ### Authentication
199
+
200
+ ```python
201
+ client = CueMap(
202
+ url="https://api.cuemap.dev",
203
+ api_key="your-api-key"
204
+ )
205
+ ```
206
+
207
+ ### Multi-tenancy
208
+
209
+ ```python
210
+ client = CueMap(
211
+ url="https://api.cuemap.dev",
212
+ api_key="your-api-key",
213
+ project_id="my-project"
214
+ )
215
+ ```
216
+
217
+ ### Async Support
218
+
219
+ ```python
220
+ from cuemap import AsyncCueMap
221
+
222
+ async with AsyncCueMap() as client:
223
+ await client.add("Note", cues=["work"])
224
+ results = await client.recall(["work"])
225
+ ```
226
+
227
+ ## Philosophy
228
+
229
+ ### What CueMap Does
230
+
231
+ ✅ **Fast storage** - Sub-millisecond retrieval
232
+ ✅ **Temporal ordering** - Recent memories prioritized
233
+ ✅ **Intersection scoring** - Multi-cue matching
234
+ ✅ **Reinforcement** - Move-to-front operation
235
+
236
+ ### What CueMap Doesn't Do
237
+
238
+ ❌ **Auto-tagging** - You provide the cues
239
+ ❌ **Semantic search** - Use your own embeddings
240
+ ❌ **LLM integration** - Bring your own model
241
+ ❌ **Magic** - Explicit and predictable
242
+
243
+ ## Why This Approach?
244
+
245
+ **Redis Philosophy**: Don't guess what the user wants. Provide primitives. Let them build.
246
+
247
+ **CueMap Philosophy**: Don't auto-extract cues. Don't auto-embed. Just store and retrieve **fast**.
248
+
249
+ **Benefits**:
250
+ - 🚀 **5KB SDK** (vs 500MB with ML models)
251
+ - ⚡ **Instant install** (1 second vs 5 minutes)
252
+ - 🎯 **Predictable** (no ML black boxes)
253
+ - 🔧 **Flexible** (works with any LLM/embedding model)
254
+
255
+ ## Comparison
256
+
257
+ | Feature | CueMap | Vector DBs |
258
+ |---------|--------|------------|
259
+ | **Speed** | 0.37ms P99 | 200-500ms |
260
+ | **SDK Size** | 5KB | 500MB+ |
261
+ | **Dependencies** | 2 | 50+ |
262
+ | **Install Time** | 1 sec | 5 min |
263
+ | **Cue Control** | Explicit | Auto (black box) |
264
+ | **Temporal** | ✅ Built-in | ❌ None |
265
+
266
+ ## Documentation
267
+
268
+ - [API Reference](https://docs.cuemap.dev/api)
269
+ - [Recipes](https://docs.cuemap.dev/recipes)
270
+ - [Deployment](https://docs.cuemap.dev/deployment)
271
+
272
+ ## License
273
+
274
+ MIT
@@ -0,0 +1,9 @@
1
+ cuemap/__init__.py,sha256=jy_21twtKaRLJq2AunVstNgFCIfe8QCjPpH5quxTz3c,817
2
+ cuemap/client.py,sha256=g7FZRsZLxF_cCPjdchrdcpDdNDHuIlgkzthYLGMaFsA,9094
3
+ cuemap/exceptions.py,sha256=LPwk-pXDBWP-aA1m6w9BNqfM-zmT3-ZPZcyXdPDE9PE,245
4
+ cuemap/models.py,sha256=siMi-dBYbsnuAR-PTkZp8lXS0I86_6m6GVyCWrfx26Y,535
5
+ cuemap-0.2.0.dist-info/licenses/LICENSE,sha256=4m2PTPvuczqwdhplhdaUTeTh_XJSjkS0HVbl90SYN5g,1063
6
+ cuemap-0.2.0.dist-info/METADATA,sha256=y2Y-ZDWpSEp4F1BiOpLyFuQi9ZpAxYvIzu-VhXt8ANU,6596
7
+ cuemap-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ cuemap-0.2.0.dist-info/top_level.txt,sha256=bZ0CmDM_glT73Eeo0pHCyjpEThjesQ0Zx9r3D4_N9LU,7
9
+ cuemap-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 CueMap
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.
@@ -0,0 +1 @@
1
+ cuemap