chuk-artifacts 0.1.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.
@@ -0,0 +1,519 @@
1
+ Metadata-Version: 2.4
2
+ Name: chuk-artifacts
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ License: MIT
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: chuk-tool-processor>=0.1.7
10
+ Requires-Dist: pydantic>=2.10.6
11
+ Requires-Dist: pyyaml>=6.0.2
12
+ Requires-Dist: aioboto3>=14.3.0
13
+ Requires-Dist: redis>=6.2.0
14
+ Requires-Dist: ibm-cos-sdk>=2.13.5
15
+ Requires-Dist: chuk-sessions>=0.1.0
16
+ Provides-Extra: websocket
17
+ Requires-Dist: websockets>=10.0; extra == "websocket"
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=8.3.5; extra == "dev"
20
+ Requires-Dist: pytest-asyncio>=0.26.0; extra == "dev"
21
+ Requires-Dist: ruff>=0.4.6; extra == "dev"
22
+ Dynamic: license-file
23
+
24
+ # Chuk Artifacts
25
+
26
+ [![Tests](https://img.shields.io/badge/tests-64%2F64%20passing-brightgreen)](https://github.com/your-org/chuk-artifacts)
27
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://python.org)
28
+ [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
29
+
30
+ **Asynchronous, multi-backend artifact storage with metadata caching and presigned URLs**
31
+
32
+ Chuk Artifacts provides a production-ready, modular artifact storage system that works seamlessly across multiple storage backends (memory, filesystem, AWS S3, IBM Cloud Object Storage) with Redis or memory-based metadata caching.
33
+
34
+ ## ✨ Key Features
35
+
36
+ - 🏗️ **Modular Architecture**: 5 specialized operation modules for clean separation of concerns
37
+ - 🔄 **Multi-Backend Support**: Memory, filesystem, S3, IBM COS with seamless switching
38
+ - ⚡ **Fully Async**: Built with async/await for high performance
39
+ - 🔗 **Presigned URLs**: Secure, time-limited access without credential exposure
40
+ - 📊 **Batch Operations**: Efficient multi-file uploads and processing
41
+ - 🗃️ **Metadata Caching**: Fast lookups with Redis or memory-based sessions
42
+ - 🔧 **Zero Configuration**: Works out of the box with sensible defaults
43
+ - 🌍 **Production Ready**: Battle-tested with comprehensive error handling
44
+
45
+ ## 🚀 Quick Start
46
+
47
+ ### Installation
48
+
49
+ ```bash
50
+ pip install chuk-artifacts
51
+ # or with uv
52
+ uv add chuk-artifacts
53
+ ```
54
+
55
+ ### Basic Usage
56
+
57
+ ```python
58
+ from chuk_artifacts import ArtifactStore
59
+
60
+ # Zero-config setup (uses memory provider)
61
+ store = ArtifactStore()
62
+
63
+ # Store an artifact
64
+ artifact_id = await store.store(
65
+ data=b"Hello, world!",
66
+ mime="text/plain",
67
+ summary="A simple greeting",
68
+ filename="hello.txt"
69
+ )
70
+
71
+ # Retrieve it
72
+ data = await store.retrieve(artifact_id)
73
+ print(data.decode()) # "Hello, world!"
74
+
75
+ # Generate a presigned URL
76
+ download_url = await store.presign_medium(artifact_id) # 1 hour
77
+ ```
78
+
79
+ ### With Configuration
80
+
81
+ ```python
82
+ # Production setup with S3 and Redis
83
+ store = ArtifactStore(
84
+ storage_provider="s3",
85
+ session_provider="redis",
86
+ bucket="my-artifacts"
87
+ )
88
+
89
+ # Or use environment variables
90
+ # ARTIFACT_PROVIDER=s3
91
+ # SESSION_PROVIDER=redis
92
+ # AWS_ACCESS_KEY_ID=your_key
93
+ # AWS_SECRET_ACCESS_KEY=your_secret
94
+ # ARTIFACT_BUCKET=my-artifacts
95
+
96
+ store = ArtifactStore() # Auto-loads configuration
97
+ ```
98
+
99
+ ## 🏗️ Architecture
100
+
101
+ Chuk Artifacts uses a modular architecture with specialized operation modules:
102
+
103
+ ```
104
+ ArtifactStore (Main Coordinator)
105
+ ├── CoreStorageOperations # store() and retrieve()
106
+ ├── PresignedURLOperations # URL generation and upload workflows
107
+ ├── MetadataOperations # metadata, exists, delete, update
108
+ ├── BatchOperations # store_batch() for multiple files
109
+ └── AdminOperations # validate_configuration, get_stats
110
+ ```
111
+
112
+ This design provides:
113
+ - **Better testability**: Each module can be tested independently
114
+ - **Enhanced maintainability**: Clear separation of concerns
115
+ - **Easy extensibility**: Add new operation types without touching core
116
+ - **Improved debugging**: Isolated functionality for easier troubleshooting
117
+
118
+ ## 📦 Storage Providers
119
+
120
+ ### Memory Provider
121
+ ```python
122
+ store = ArtifactStore(storage_provider="memory")
123
+ ```
124
+ - Perfect for development and testing
125
+ - Zero configuration required
126
+ - Non-persistent (data lost on restart)
127
+ - Isolation between async contexts
128
+
129
+ ### Filesystem Provider
130
+ ```python
131
+ store = ArtifactStore(storage_provider="filesystem")
132
+ # Set root directory
133
+ os.environ["ARTIFACT_FS_ROOT"] = "./my-artifacts"
134
+ ```
135
+ - Local disk storage
136
+ - Persistent across restarts
137
+ - `file://` URLs for local access
138
+ - Great for development and small deployments
139
+
140
+ ### AWS S3 Provider
141
+ ```python
142
+ store = ArtifactStore(storage_provider="s3")
143
+ # Configure via environment
144
+ os.environ.update({
145
+ "AWS_ACCESS_KEY_ID": "your_key",
146
+ "AWS_SECRET_ACCESS_KEY": "your_secret",
147
+ "AWS_REGION": "us-east-1",
148
+ "ARTIFACT_BUCKET": "my-bucket"
149
+ })
150
+ ```
151
+ - Industry-standard cloud storage
152
+ - Native presigned URL support
153
+ - Highly scalable and durable
154
+ - Perfect for production workloads
155
+
156
+ ### IBM Cloud Object Storage
157
+ ```python
158
+ # HMAC authentication
159
+ store = ArtifactStore(storage_provider="ibm_cos")
160
+ os.environ.update({
161
+ "AWS_ACCESS_KEY_ID": "your_hmac_key",
162
+ "AWS_SECRET_ACCESS_KEY": "your_hmac_secret",
163
+ "IBM_COS_ENDPOINT": "https://s3.us-south.cloud-object-storage.appdomain.cloud"
164
+ })
165
+
166
+ # IAM authentication
167
+ store = ArtifactStore(storage_provider="ibm_cos_iam")
168
+ os.environ.update({
169
+ "IBM_COS_APIKEY": "your_api_key",
170
+ "IBM_COS_INSTANCE_CRN": "crn:v1:bluemix:public:cloud-object-storage:..."
171
+ })
172
+ ```
173
+
174
+ ## 🗃️ Session Providers
175
+
176
+ ### Memory Sessions
177
+ ```python
178
+ store = ArtifactStore(session_provider="memory")
179
+ ```
180
+ - In-memory metadata storage
181
+ - Fast but non-persistent
182
+ - Perfect for testing
183
+
184
+ ### Redis Sessions
185
+ ```python
186
+ store = ArtifactStore(session_provider="redis")
187
+ os.environ["SESSION_REDIS_URL"] = "redis://localhost:6379/0"
188
+ ```
189
+ - Persistent metadata storage
190
+ - Shared across multiple instances
191
+ - Production-ready caching
192
+
193
+ ## 🎯 Common Use Cases
194
+
195
+ ### Web Framework Integration
196
+
197
+ ```python
198
+ from chuk_artifacts import ArtifactStore
199
+
200
+ # Initialize once at startup
201
+ store = ArtifactStore(
202
+ storage_provider="s3",
203
+ session_provider="redis"
204
+ )
205
+
206
+ async def upload_file(file_content: bytes, filename: str, content_type: str):
207
+ """Handle file upload in FastAPI/Flask"""
208
+ artifact_id = await store.store(
209
+ data=file_content,
210
+ mime=content_type,
211
+ summary=f"Uploaded: {filename}",
212
+ filename=filename
213
+ )
214
+
215
+ # Return download URL
216
+ download_url = await store.presign_medium(artifact_id)
217
+ return {
218
+ "artifact_id": artifact_id,
219
+ "download_url": download_url
220
+ }
221
+ ```
222
+
223
+ ### Batch Processing
224
+
225
+ ```python
226
+ # Prepare multiple files
227
+ items = [
228
+ {
229
+ "data": file1_content,
230
+ "mime": "image/png",
231
+ "summary": "Product image 1",
232
+ "filename": "product1.png"
233
+ },
234
+ {
235
+ "data": file2_content,
236
+ "mime": "image/png",
237
+ "summary": "Product image 2",
238
+ "filename": "product2.png"
239
+ }
240
+ ]
241
+
242
+ # Store all at once
243
+ artifact_ids = await store.store_batch(items, session_id="product-images")
244
+ ```
245
+
246
+ ### Advanced Metadata Management
247
+
248
+ ```python
249
+ # Store with custom metadata
250
+ artifact_id = await store.store(
251
+ data=image_data,
252
+ mime="image/png",
253
+ summary="Product photo",
254
+ filename="product.png",
255
+ meta={
256
+ "product_id": "12345",
257
+ "photographer": "John Doe",
258
+ "category": "electronics"
259
+ }
260
+ )
261
+
262
+ # Update metadata later
263
+ await store.update_metadata(
264
+ artifact_id,
265
+ summary="Updated product photo",
266
+ meta={"edited": True, "version": 2}
267
+ )
268
+
269
+ # Extend TTL
270
+ await store.extend_ttl(artifact_id, 3600) # Add 1 hour
271
+ ```
272
+
273
+ ### Context Manager Usage
274
+
275
+ ```python
276
+ async with ArtifactStore() as store:
277
+ artifact_id = await store.store(
278
+ data=b"Temporary data",
279
+ mime="text/plain",
280
+ summary="Auto-cleanup example"
281
+ )
282
+ # Store automatically closed on exit
283
+ ```
284
+
285
+ ## 🔧 Configuration
286
+
287
+ ### Environment Variables
288
+
289
+ ```bash
290
+ # Storage configuration
291
+ ARTIFACT_PROVIDER=s3 # memory, filesystem, s3, ibm_cos, ibm_cos_iam
292
+ ARTIFACT_BUCKET=my-artifacts # Bucket/container name
293
+ ARTIFACT_FS_ROOT=./artifacts # Filesystem root (filesystem provider)
294
+
295
+ # Session configuration
296
+ SESSION_PROVIDER=redis # memory, redis
297
+ SESSION_REDIS_URL=redis://localhost:6379/0
298
+
299
+ # AWS/S3 configuration
300
+ AWS_ACCESS_KEY_ID=your_key
301
+ AWS_SECRET_ACCESS_KEY=your_secret
302
+ AWS_REGION=us-east-1
303
+ S3_ENDPOINT_URL=https://custom-s3.com # Optional: custom S3 endpoint
304
+
305
+ # IBM COS configuration
306
+ IBM_COS_ENDPOINT=https://s3.us-south.cloud-object-storage.appdomain.cloud
307
+ IBM_COS_APIKEY=your_api_key # For IAM auth
308
+ IBM_COS_INSTANCE_CRN=crn:v1:... # For IAM auth
309
+ ```
310
+
311
+ ### Programmatic Configuration
312
+
313
+ ```python
314
+ from chuk_artifacts.config import configure_s3, configure_redis_session
315
+
316
+ # Configure S3 storage
317
+ configure_s3(
318
+ access_key="AKIA...",
319
+ secret_key="...",
320
+ bucket="prod-artifacts",
321
+ region="us-west-2"
322
+ )
323
+
324
+ # Configure Redis sessions
325
+ configure_redis_session("redis://prod-redis:6379/1")
326
+
327
+ # Create store with this configuration
328
+ store = ArtifactStore()
329
+ ```
330
+
331
+ ## 🧪 Testing
332
+
333
+ ### Run All Tests
334
+ ```bash
335
+ # Comprehensive smoke test (64 test scenarios)
336
+ uv run examples/artifact_smoke_test.py
337
+
338
+ # Usage examples
339
+ uv run examples/artifact_usage_examples.py
340
+ ```
341
+
342
+ ### Development Setup
343
+ ```python
344
+ from chuk_artifacts.config import development_setup
345
+
346
+ store = development_setup() # Uses memory providers
347
+ ```
348
+
349
+ ### Testing Setup
350
+ ```python
351
+ from chuk_artifacts.config import testing_setup
352
+
353
+ store = testing_setup("./test-artifacts") # Uses filesystem
354
+ ```
355
+
356
+ ## 🚀 Performance
357
+
358
+ - **Async/Await**: Non-blocking I/O for high concurrency
359
+ - **Connection Pooling**: Efficient resource usage with aioboto3
360
+ - **Metadata Caching**: Fast lookups with Redis
361
+ - **Batch Operations**: Reduced overhead for multiple files
362
+ - **Streaming**: Large file support with streaming reads/writes
363
+
364
+ ## 🔒 Security
365
+
366
+ - **Presigned URLs**: Time-limited access without credential sharing
367
+ - **Secure Defaults**: Conservative TTL and expiration settings
368
+ - **Credential Isolation**: Environment-based configuration
369
+ - **Error Handling**: No sensitive data in logs or exceptions
370
+
371
+ ## 🛠️ Advanced Features
372
+
373
+ ### Custom Providers
374
+ ```python
375
+ # Create custom storage provider
376
+ def my_custom_factory():
377
+ @asynccontextmanager
378
+ async def _ctx():
379
+ client = MyCustomClient()
380
+ try:
381
+ yield client
382
+ finally:
383
+ await client.close()
384
+ return _ctx
385
+
386
+ store = ArtifactStore(s3_factory=my_custom_factory())
387
+ ```
388
+
389
+ ### Error Handling
390
+ ```python
391
+ from chuk_artifacts import (
392
+ ArtifactNotFoundError,
393
+ ArtifactExpiredError,
394
+ ProviderError
395
+ )
396
+
397
+ try:
398
+ data = await store.retrieve("invalid-id")
399
+ except ArtifactNotFoundError:
400
+ print("Artifact not found or expired")
401
+ except ProviderError as e:
402
+ print(f"Storage provider error: {e}")
403
+ ```
404
+
405
+ ### Validation and Monitoring
406
+ ```python
407
+ # Validate configuration
408
+ config_status = await store.validate_configuration()
409
+ print(f"Storage: {config_status['storage']['status']}")
410
+ print(f"Session: {config_status['session']['status']}")
411
+
412
+ # Get statistics
413
+ stats = await store.get_stats()
414
+ print(f"Provider: {stats['storage_provider']}")
415
+ print(f"Bucket: {stats['bucket']}")
416
+ ```
417
+
418
+ ## 📝 API Reference
419
+
420
+ ### Core Methods
421
+
422
+ #### `store(data, *, mime, summary, meta=None, filename=None, session_id=None, ttl=900)`
423
+ Store artifact data with metadata.
424
+
425
+ **Parameters:**
426
+ - `data` (bytes): The artifact data
427
+ - `mime` (str): MIME type (e.g., "text/plain", "image/png")
428
+ - `summary` (str): Human-readable description
429
+ - `meta` (dict, optional): Additional metadata
430
+ - `filename` (str, optional): Original filename
431
+ - `session_id` (str, optional): Session identifier for organization
432
+ - `ttl` (int, optional): Metadata TTL in seconds (default: 900)
433
+
434
+ **Returns:** `str` - Unique artifact identifier
435
+
436
+ #### `retrieve(artifact_id)`
437
+ Retrieve artifact data by ID.
438
+
439
+ **Parameters:**
440
+ - `artifact_id` (str): The artifact identifier
441
+
442
+ **Returns:** `bytes` - The artifact data
443
+
444
+ #### `metadata(artifact_id)`
445
+ Get artifact metadata.
446
+
447
+ **Returns:** `dict` - Metadata including size, MIME type, timestamps, etc.
448
+
449
+ #### `exists(artifact_id)`
450
+ Check if artifact exists and hasn't expired.
451
+
452
+ **Returns:** `bool`
453
+
454
+ #### `delete(artifact_id)`
455
+ Delete artifact and its metadata.
456
+
457
+ **Returns:** `bool` - True if deleted, False if not found
458
+
459
+ ### Presigned URLs
460
+
461
+ #### `presign(artifact_id, expires=3600)`
462
+ Generate presigned URL for download.
463
+
464
+ #### `presign_short(artifact_id)` / `presign_medium(artifact_id)` / `presign_long(artifact_id)`
465
+ Generate URLs with predefined durations (15min/1hr/24hr).
466
+
467
+ #### `presign_upload(session_id=None, filename=None, mime_type="application/octet-stream", expires=3600)`
468
+ Generate presigned URL for upload.
469
+
470
+ **Returns:** `tuple[str, str]` - (upload_url, artifact_id)
471
+
472
+ ### Batch Operations
473
+
474
+ #### `store_batch(items, session_id=None, ttl=900)`
475
+ Store multiple artifacts efficiently.
476
+
477
+ **Parameters:**
478
+ - `items` (list): List of dicts with keys: data, mime, summary, meta, filename
479
+
480
+ **Returns:** `list[str]` - List of artifact IDs
481
+
482
+ ### Admin Operations
483
+
484
+ #### `validate_configuration()`
485
+ Validate storage and session provider connectivity.
486
+
487
+ #### `get_stats()`
488
+ Get storage statistics and configuration info.
489
+
490
+ ## 🤝 Contributing
491
+
492
+ 1. Fork the repository
493
+ 2. Create a feature branch: `git checkout -b feature-name`
494
+ 3. Make your changes
495
+ 4. Run tests: `uv run examples/artifact_smoke_test.py`
496
+ 5. Submit a pull request
497
+
498
+ ## 📄 License
499
+
500
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
501
+
502
+ ## 🔗 Links
503
+
504
+ - **Documentation**: [docs.example.com](https://docs.example.com)
505
+ - **Issue Tracker**: [github.com/your-org/chuk-artifacts/issues](https://github.com/your-org/chuk-artifacts/issues)
506
+ - **PyPI**: [pypi.org/project/chuk-artifacts](https://pypi.org/project/chuk-artifacts)
507
+
508
+ ## 🎯 Roadmap
509
+
510
+ - [ ] Azure Blob Storage provider
511
+ - [ ] Google Cloud Storage provider
512
+ - [ ] Encryption at rest
513
+ - [ ] Artifact versioning
514
+ - [ ] Webhook notifications
515
+ - [ ] Prometheus metrics export
516
+
517
+ ---
518
+
519
+ **Made with ❤️ by the Chuk team**
@@ -0,0 +1,23 @@
1
+ chuk_artifacts/__init__.py,sha256=-4S9FWKVcQSa2ZD3GVbmbpGZPcl0cTQN_TFZLSqV7lQ,3605
2
+ chuk_artifacts/admin.py,sha256=g8r1nta_cDkoy_KgScTHkeeV7ij5PR6jX0GXgUYub7Y,2662
3
+ chuk_artifacts/base.py,sha256=d3iA3AJR9wF0I3kmppkosPDxaMTgufqI6v_wW5fQfKY,2411
4
+ chuk_artifacts/batch.py,sha256=I5WgWajuzrvqTCkQYYgpzrL1WduzxJchA3Ihv3Xvhcw,4264
5
+ chuk_artifacts/config.py,sha256=MaUzHzKPoBUyERviEpv8JVvPybMzSksgLyj0b7AO3Sc,7664
6
+ chuk_artifacts/core.py,sha256=rS5VWjen6aRlnAIJIhLi639VfZ2u8ORaFxrPJMfpPYE,8296
7
+ chuk_artifacts/exceptions.py,sha256=f-s7Mg7c8vMXsbgqO2B6lMHdXcJQNvsESAY4GhJaV4g,814
8
+ chuk_artifacts/metadata.py,sha256=CXC0j-zjKSOoACmyu6yKqcQpy35otwr-Y_3ogfcuKdE,9123
9
+ chuk_artifacts/models.py,sha256=_foXlkr0DprqgztDw5WtlDc-s1OouLgYNp4XM1Ghp-g,837
10
+ chuk_artifacts/presigned.py,sha256=qonNg7WMd7VmOEXAzF6GssXuPs5be2s8IJhtuFul7JM,9638
11
+ chuk_artifacts/provider_factory.py,sha256=T0IXx1C8gygJzp417oB44_DxEaZoZR7jcdwQy8FghRE,3398
12
+ chuk_artifacts/store.py,sha256=QdNyik5EXg2NthHnWxUnG5JMM1L7v2jO7phCL5WmvGg,16719
13
+ chuk_artifacts/providers/__init__.py,sha256=3lN1lAy1ETT1mQslJo1f22PPR1W4CyxmsqJBclzH4NE,317
14
+ chuk_artifacts/providers/filesystem.py,sha256=F4EjE-_ItPg0RWe7CqameVpOMjU-b7AigEBkm_ZoNrc,15280
15
+ chuk_artifacts/providers/ibm_cos.py,sha256=K1-VAX4UVV9tA161MOeDXOKloQ0hB77jdw1-p46FwmU,4445
16
+ chuk_artifacts/providers/ibm_cos_iam.py,sha256=VtwvCi9rMMcZx6i9l21ob6wM8jXseqvjzgCnAA82RkY,3186
17
+ chuk_artifacts/providers/memory.py,sha256=B1C-tR1PcNz-UuDfGm1bhjPz3oITVATIMPekVbE7nm4,10487
18
+ chuk_artifacts/providers/s3.py,sha256=eWhBhFSaobpRbazn7ySfU_7D8rm_xCfdSVqRtzXzXRY,2858
19
+ chuk_artifacts-0.1.0.dist-info/licenses/LICENSE,sha256=SG9BmgtPBagPV0d-Fep-msdAGl-E1CeoBL7-EDRH2qA,1066
20
+ chuk_artifacts-0.1.0.dist-info/METADATA,sha256=G_DqvAOc5JzpuPDDdMqA1ULhTk6J588gjVVvQ9_5H9A,13847
21
+ chuk_artifacts-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
+ chuk_artifacts-0.1.0.dist-info/top_level.txt,sha256=1_PVMtWXR0A-ZmeH6apF9mPaMtU0i23JE6wmN4GBRDI,15
23
+ chuk_artifacts-0.1.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) 2025 Chris Hay
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
+ chuk_artifacts