promptcapsule 0.1.0__tar.gz

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Udaya Nirogi
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,609 @@
1
+ Metadata-Version: 2.4
2
+ Name: promptcapsule
3
+ Version: 0.1.0
4
+ Summary: Open-source prompt compression & retrieval library
5
+ Author-email: Udaya Nirogi <udaya@example.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/UdayaNirogi/promptcapsule
8
+ Project-URL: Bug Tracker, https://github.com/UdayaNirogi/promptcapsule/issues
9
+ Keywords: prompt,compression,retrieval,ai,llm
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=7.0; extra == "dev"
24
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
25
+ Requires-Dist: black>=23.0; extra == "dev"
26
+ Requires-Dist: flake8>=6.0; extra == "dev"
27
+ Requires-Dist: mypy>=1.0; extra == "dev"
28
+ Requires-Dist: twine>=4.0; extra == "dev"
29
+ Provides-Extra: vault
30
+ Requires-Dist: boto3>=1.26; extra == "vault"
31
+ Requires-Dist: pygithub>=1.59; extra == "vault"
32
+ Dynamic: license-file
33
+
34
+ # PromptCapsule 📦
35
+
36
+ > Open-source prompt compression & retrieval library that solves a real problem for developers and AI enthusiasts.
37
+
38
+ ## Overview
39
+
40
+ **PromptCapsule** is a generic, reusable, open-source tool for compressing and reliably reconstructing LLM prompts. It bridges the gap between short-prompt compression and long-prompt storage, giving developers an honest, transparent way to share, version-control, and manage their AI prompts.
41
+
42
+ ### The Problem
43
+
44
+ When people talk about "prompt compression," they usually mean two fundamentally different things:
45
+
46
+ | Type | What It Does | Trade-off |
47
+ |------|-------------|-----------|
48
+ | **Type 1: True Compression** | Mathematically limited to ~500-word prompts → 8 characters (lossless) | No external storage needed, but very limited scope |
49
+ | **Type 2: Key-Based Retrieval** | 2,000-word prompt → 8-character hash that reconstructs the original (from any device, any account) | Requires a storage backend, but solves the real problem |
50
+
51
+ **PromptCapsule** gives you **BOTH**, transparently and automatically.
52
+
53
+ ### The Solution
54
+
55
+ PromptCapsule uses a **hybrid approach**:
56
+
57
+ ```
58
+ Short prompts (<500 chars):
59
+ INPUT PROMPT → ZLIB COMPRESS → BASE85 ENCODE → Portable capsule string
60
+ (self-contained, no storage needed)
61
+
62
+ Long prompts (>500 chars):
63
+ INPUT PROMPT → STORE IN VAULT (SQLite, GitHub Gist, S3) → Generate short hash key
64
+ (retrieves from configured backend)
65
+ ```
66
+
67
+ Both modes are **automatic** — just compress once and the library picks the best strategy. Both are **verified** — checksums ensure byte-for-byte exact reconstruction.
68
+
69
+ ---
70
+
71
+ ## Features
72
+
73
+ ✨ **Hybrid Compression**
74
+ - Short prompts: Inline zlib + Base85 compression
75
+ - Long prompts: Vault storage with automatic fallback
76
+ - Automatic mode selection based on size
77
+
78
+ 🔒 **Integrity Verification**
79
+ - SHA256 checksums for all capsules
80
+ - Byte-for-byte exact reconstruction guarantee
81
+ - Verification status in decompression results
82
+
83
+ 🎯 **Honest Positioning**
84
+ - Transparent about what it actually does (capsule strings, not LLM output compression)
85
+ - Clear comparisons vs. existing tools (LLMLingua, LangChain Hub, gzip)
86
+ - No false claims of novelty
87
+
88
+ 🌍 **Pluggable Backends**
89
+ - **In-Memory**: Great for testing and prototyping
90
+ - **SQLite**: Local storage, no external dependencies
91
+ - **GitHub Gist**: Cloud storage, version control friendly
92
+ - **AWS S3**: Enterprise-grade scalability (optional)
93
+
94
+ ⚙️ **Python 3.8+**
95
+ - Pure Python, minimal dependencies
96
+ - Cross-platform compatible
97
+ - Type hints throughout
98
+
99
+ ---
100
+
101
+ ## Installation
102
+
103
+ ```bash
104
+ # Basic installation (includes in-memory and SQLite backends)
105
+ pip install promptcapsule
106
+
107
+ # With cloud storage support
108
+ pip install promptcapsule[vault]
109
+ ```
110
+
111
+ ### Requirements
112
+ - Python 3.8+
113
+ - No external dependencies for core functionality
114
+ - Optional: `boto3` for S3 backend, `PyGithub` for GitHub Gist backend
115
+
116
+ ---
117
+
118
+ ## Quick Start
119
+
120
+ ### Basic Usage (Short Prompts)
121
+
122
+ ```python
123
+ from promptcapsule import PromptCapsule
124
+
125
+ pc = PromptCapsule()
126
+
127
+ # Compress a prompt
128
+ prompt = "You are a helpful Python coding assistant."
129
+ capsule = pc.compress(prompt)
130
+ # capsule: "cap_i_a1b2c3d4_K*i0?5Z7....."
131
+
132
+ # Decompress anywhere, on any device/account
133
+ result = pc.decompress(capsule)
134
+ assert result.text == prompt
135
+ assert result.verified is True # Integrity verified ✓
136
+ ```
137
+
138
+ ### Long Prompts with Vault Backend
139
+
140
+ ```python
141
+ from promptcapsule import PromptCapsule
142
+ from promptcapsule.backends import SQLiteBackend
143
+
144
+ pc = PromptCapsule()
145
+ vault = SQLiteBackend("prompts.db")
146
+
147
+ # Compress a long, carefully-crafted prompt
148
+ long_prompt = """
149
+ You are an expert in machine learning...
150
+ [2000+ characters of detailed context]
151
+ """
152
+
153
+ capsule = pc.compress(long_prompt, vault_backend=vault)
154
+ # capsule: "cap_v_a1b2c3d4_sql_20240921_120000_0001"
155
+
156
+ # Decompress later (data retrieves from SQLite)
157
+ result = pc.decompress(capsule, vault_backend=vault)
158
+ assert result.text == long_prompt
159
+ assert result.verified is True
160
+ ```
161
+
162
+ ### Real-World Scenario: Version-Controlling Prompts
163
+
164
+ ```python
165
+ import json
166
+ from promptcapsule import PromptCapsule
167
+ from promptcapsule.backends import SQLiteBackend
168
+
169
+ pc = PromptCapsule()
170
+ vault = SQLiteBackend("prompts.db")
171
+
172
+ # Iterate on your prompts over time
173
+ prompts_history = {
174
+ "v1.0": pc.compress(
175
+ "Generate a blog post about AI",
176
+ vault_backend=vault
177
+ ),
178
+ "v1.1": pc.compress(
179
+ "Generate a blog post about AI, focused on practical applications",
180
+ vault_backend=vault
181
+ ),
182
+ "v1.2": pc.compress(
183
+ "Generate a technical blog post about AI/ML, 2000+ words, with code examples",
184
+ vault_backend=vault
185
+ ),
186
+ }
187
+
188
+ # Save to git
189
+ with open("prompts.json", "w") as f:
190
+ json.dump(prompts_history, f)
191
+
192
+ # Later (or different branch), retrieve and decompress
193
+ with open("prompts.json", "r") as f:
194
+ history = json.load(f)
195
+
196
+ for version, capsule in history.items():
197
+ result = pc.decompress(capsule, vault_backend=vault)
198
+ print(f"{version}: {result.text[:50]}...")
199
+ ```
200
+
201
+ ---
202
+
203
+ ## How It Works
204
+
205
+ ### Inline Mode (Short Prompts)
206
+
207
+ 1. **Compress**: `prompt` → zlib (level 9) → Base85 encode → capsule
208
+ 2. **Format**: `cap_i_<8-char checksum>_<encoded data>`
209
+ 3. **Verify**: Decompress → compare checksum prefix
210
+ 4. **Result**: Portable, self-contained capsule string
211
+
212
+ **Example**:
213
+ ```
214
+ Original: "Hello world!" (12 bytes)
215
+ Capsule: "cap_i_2f575c63_EZxgxD/'" (28 bytes)
216
+ Ratio: ~2.3x (expected for very short content)
217
+ ```
218
+
219
+ ### Vault Mode (Long Prompts)
220
+
221
+ 1. **Store**: `prompt` → Save to backend (SQLite/Gist/S3) → Generate key
222
+ 2. **Format**: `cap_v_<8-char checksum>_<backend key>`
223
+ 3. **Retrieve**: On decompress → fetch from backend using key
224
+ 4. **Verify**: Compare checksum prefix with retrieved content
225
+
226
+ **Example**:
227
+ ```
228
+ Original: "You are a senior software architect..." (2,000 chars)
229
+ Capsule: "cap_v_a1b2c3d4_sql_20240921_120000_0001" (41 chars)
230
+ Ratio: ~48x compression (key is permanent pointer to vault)
231
+ ```
232
+
233
+ ### Automatic Mode Selection
234
+
235
+ ```python
236
+ pc = PromptCapsule()
237
+
238
+ # Anything ≤ 500 bytes uses inline
239
+ pc.compress("short prompt") # cap_i_...
240
+
241
+ # Anything > 500 bytes needs vault
242
+ pc.compress("A" * 600) # Error! Need vault_backend=
243
+
244
+ # With vault, auto-selects best mode
245
+ pc.compress("short prompt", vault_backend=backend) # Still cap_i_...
246
+ pc.compress("A" * 600, vault_backend=backend) # cap_v_...
247
+ ```
248
+
249
+ ---
250
+
251
+ ## Backends
252
+
253
+ ### In-Memory Backend
254
+
255
+ Perfect for testing and prototyping:
256
+
257
+ ```python
258
+ from promptcapsule.backends import InMemoryBackend
259
+
260
+ backend = InMemoryBackend()
261
+ capsule = pc.compress("long prompt" * 100, vault_backend=backend)
262
+ ```
263
+
264
+ ### SQLite Backend
265
+
266
+ Local, file-based storage:
267
+
268
+ ```python
269
+ from promptcapsule.backends import SQLiteBackend
270
+
271
+ backend = SQLiteBackend("prompts.db")
272
+ # Auto-creates schema, stores prompts locally
273
+ capsule = pc.compress("long prompt" * 100, vault_backend=backend)
274
+ ```
275
+
276
+ ### GitHub Gist Backend
277
+
278
+ Cloud storage with version control:
279
+
280
+ ```python
281
+ from promptcapsule.backends import GitHubGistBackend
282
+
283
+ backend = GitHubGistBackend(token="github_pat_...")
284
+ # Stores as private gist, returns gist ID
285
+ capsule = pc.compress("long prompt" * 100, vault_backend=backend)
286
+ ```
287
+
288
+ Requires: `pip install promptcapsule[vault]`
289
+
290
+ ### AWS S3 Backend
291
+
292
+ Enterprise-grade cloud storage:
293
+
294
+ ```python
295
+ from promptcapsule.backends import S3Backend
296
+
297
+ backend = S3Backend(bucket="my-prompts", region="us-east-1")
298
+ # Stores in S3, returns S3 key
299
+ capsule = pc.compress("long prompt" * 100, vault_backend=backend)
300
+ ```
301
+
302
+ Requires: `pip install promptcapsule[vault]`
303
+
304
+ ---
305
+
306
+ ## Command-Line Interface
307
+
308
+ ```bash
309
+ # Compress a prompt
310
+ echo "Your prompt here" | promptcapsule compress
311
+ # Output: cap_i_a1b2c3d4_...
312
+
313
+ # Decompress a capsule
314
+ promptcapsule decompress "cap_i_a1b2c3d4_..."
315
+ # Output: Your prompt here
316
+
317
+ # With vault backend
318
+ promptcapsule compress --vault sqlite:prompts.db < prompt.txt
319
+ promptcapsule decompress --vault sqlite:prompts.db "cap_v_..."
320
+ ```
321
+
322
+ ---
323
+
324
+ ## Comparison with Existing Tools
325
+
326
+ | Tool | What It Does | Strength | Limitation |
327
+ |------|-------------|----------|-----------|
328
+ | **LLMLingua** | Lossy semantic compression of prompts | High compression ratios | Approximate reconstruction, no exact guarantee |
329
+ | **LangChain Hub** | Cloud-hosted prompt template sharing | Easy sharing, versioning | Locked into LangChain ecosystem |
330
+ | **gzip/zlib directly** | General-purpose compression | Simple, standard | Can't store large prompts, no key-based retrieval |
331
+ | **PromptCapsule** | Hybrid lossless + vault-based retrieval | Exact reconstruction, honest, pluggable | Requires storage backend for long prompts |
332
+
333
+ ---
334
+
335
+ ## Use Cases
336
+
337
+ ### 1️⃣ Share Prompts Across Accounts
338
+ Move carefully-crafted prompts between work and personal accounts without copy-pasting:
339
+
340
+ ```python
341
+ capsule = pc.compress(my_favorite_prompt)
342
+ # Share via email, Slack, message, etc.
343
+ # Later, paste in personal account
344
+ result = pc.decompress(capsule)
345
+ ```
346
+
347
+ ### 2️⃣ Version-Control Your Prompts
348
+ Keep prompts in git alongside your code:
349
+
350
+ ```
351
+ prompts/
352
+ ├── article-writer-v1.0.cap
353
+ ├── article-writer-v1.1.cap
354
+ └── article-writer-v2.0.cap
355
+ ```
356
+
357
+ ### 3️⃣ Portable Prompt Library
358
+ Share a repo of prompts that works on any machine, any account:
359
+
360
+ ```python
361
+ prompts = load_capsule_library("prompts.json")
362
+ for name, capsule in prompts.items():
363
+ result = pc.decompress(capsule, vault_backend=my_vault)
364
+ print(f"{name}: {result.text}")
365
+ ```
366
+
367
+ ### 4️⃣ Automated Prompt Management
368
+ Build CI/CD pipelines that validate and archive prompts:
369
+
370
+ ```python
371
+ # Compress before commit
372
+ for prompt_file in glob("*.txt"):
373
+ with open(prompt_file) as f:
374
+ prompt = f.read()
375
+ capsule = pc.compress(prompt, vault_backend=vault)
376
+ save_to_metadata(capsule)
377
+
378
+ # Retrieve during deployment
379
+ capsule = load_from_metadata()
380
+ prompt = pc.decompress(capsule, vault_backend=vault).text
381
+ ```
382
+
383
+ ---
384
+
385
+ ## API Reference
386
+
387
+ ### `PromptCapsule` Class
388
+
389
+ ```python
390
+ class PromptCapsule:
391
+ def compress(
392
+ self,
393
+ text: str,
394
+ vault_backend: Optional[VaultBackend] = None,
395
+ ) -> str:
396
+ """
397
+ Compress a prompt into a capsule string.
398
+
399
+ Args:
400
+ text: The prompt to compress
401
+ vault_backend: Backend for storing long prompts (required if > 500 bytes)
402
+
403
+ Returns:
404
+ Capsule string (starts with "cap_")
405
+
406
+ Raises:
407
+ ValueError: If text is empty or > 500 bytes without vault
408
+ TypeError: If text is not a string
409
+ """
410
+
411
+ def decompress(
412
+ self,
413
+ capsule: str,
414
+ vault_backend: Optional[VaultBackend] = None,
415
+ ) -> CapsuleResult:
416
+ """
417
+ Decompress a capsule back to the original prompt.
418
+
419
+ Args:
420
+ capsule: The capsule string to decompress
421
+ vault_backend: Backend for retrieving long prompts
422
+
423
+ Returns:
424
+ CapsuleResult with:
425
+ - text: Original prompt
426
+ - verified: Integrity check passed
427
+ - mode: "inline" or "vault"
428
+ - checksum: SHA256 of original
429
+ - original_size: Bytes
430
+ - capsule_size: Bytes
431
+
432
+ Raises:
433
+ ValueError: If capsule format is invalid
434
+ KeyError: If vault key not found
435
+ """
436
+ ```
437
+
438
+ ### `CapsuleResult` Named Tuple
439
+
440
+ ```python
441
+ class CapsuleResult(NamedTuple):
442
+ text: str # Original prompt
443
+ verified: bool # Checksum matched
444
+ mode: str # "inline" or "vault"
445
+ checksum: str # SHA256 hash
446
+ original_size: int # Bytes
447
+ capsule_size: int # Bytes
448
+ ```
449
+
450
+ ### `VaultBackend` Abstract Class
451
+
452
+ Implement to create custom backends:
453
+
454
+ ```python
455
+ class VaultBackend:
456
+ def store(self, text: str, checksum: str) -> str:
457
+ """Store text, return a key."""
458
+ raise NotImplementedError
459
+
460
+ def retrieve(self, key: str) -> str:
461
+ """Retrieve text by key."""
462
+ raise NotImplementedError
463
+ ```
464
+
465
+ ---
466
+
467
+ ## Testing
468
+
469
+ Run the full regression test suite:
470
+
471
+ ```bash
472
+ python run_tests.py
473
+ ```
474
+
475
+ Expected output:
476
+ ```
477
+ ======================================================================
478
+ PromptCapsule - Regression Test Suite
479
+ ======================================================================
480
+
481
+ Core Functionality Tests:
482
+ ✓ Compress short prompt (inline mode)
483
+ ✓ Reject empty strings
484
+ ✓ Reject invalid types
485
+ [... 24 more tests ...]
486
+
487
+ Test Results: 27/27 passed
488
+ ======================================================================
489
+ ```
490
+
491
+ ---
492
+
493
+ ## Architecture Notes
494
+
495
+ ### Why This Design?
496
+
497
+ 1. **Hybrid approach**: Best of both worlds — inline compression for portability, vault for scale
498
+ 2. **Honest positioning**: We don't claim to be better than lossy compression at reducing LLM cost — we solve a different problem (portability + exact reconstruction)
499
+ 3. **Pluggable backends**: Future-proof; use SQLite today, S3 tomorrow, custom backend next week
500
+ 4. **Integrity by default**: Every capsule includes a checksum; verification is automatic
501
+ 5. **Zero external dependencies** for core functionality — just zlib and base64, both stdlib
502
+
503
+ ### Checksum Strategy
504
+
505
+ - Uses SHA256 (64 hex chars)
506
+ - Stores first 8 chars in capsule for quick verification
507
+ - Prevents accidental corruption detection
508
+ - Does **not** provide cryptographic authentication (future: optional HMAC signing)
509
+
510
+ ### Compression Levels
511
+
512
+ - **Zlib level 9**: Maximum compression
513
+ - **Base85**: Better human readability than Base64 (4-char savings per 80 bytes)
514
+ - **Trade-off**: ~2-3x size increase for very short content (overhead of capsule format)
515
+
516
+ ---
517
+
518
+ ## Roadmap
519
+
520
+ - [ ] CLI tool with full feature parity
521
+ - [ ] HMAC signing for optional authentication
522
+ - [ ] Async backend support
523
+ - [ ] Compression format versioning (for future improvements)
524
+ - [ ] Web UI for managing vaults
525
+ - [ ] Prompt templates + variable interpolation
526
+ - [ ] Analytics: track prompt reuse, version adoption
527
+
528
+ ---
529
+
530
+ ## Security Considerations
531
+
532
+ - **Integrity**: ✅ Checksums detect corruption
533
+ - **Authenticity**: ⚠️ No signing (roadmap)
534
+ - **Confidentiality**: ⚠️ Vault contents transmitted/stored in plaintext (use HTTPS, encrypted S3, private gists)
535
+ - **Access Control**: Depends on backend (GitHub: private gists, S3: IAM policies)
536
+
537
+ **Recommendation**: Treat capsule strings like URLs — they're short but semantically empty. Don't rely on them for security-critical operations.
538
+
539
+ ---
540
+
541
+ ## License
542
+
543
+ MIT License - see LICENSE file
544
+
545
+ ---
546
+
547
+ ## Contributing
548
+
549
+ Contributions welcome! Please:
550
+
551
+ 1. Write tests for new features
552
+ 2. Follow PEP 8 style guide
553
+ 3. Add docstrings
554
+ 4. Update README with examples
555
+
556
+ ---
557
+
558
+ ## Citation
559
+
560
+ If you use PromptCapsule in your research or project, please cite:
561
+
562
+ ```bibtex
563
+ @software{promptcapsule2024,
564
+ title={PromptCapsule: Open-source prompt compression and retrieval library},
565
+ author={Nirogi, Udaya},
566
+ year={2024},
567
+ url={https://github.com/UdayaNirogi/promptcapsule}
568
+ }
569
+ ```
570
+
571
+ ---
572
+
573
+ ## FAQ
574
+
575
+ **Q: How is this different from just using a URL shortener?**
576
+ A: URL shorteners store data on a third-party server. PromptCapsule lets you choose your own backend (SQLite locally, S3 privately, GitHub Gist for sharing, etc.). Plus, checksums guarantee integrity.
577
+
578
+ **Q: Can I use this to compress LLM outputs?**
579
+ A: No — that's a different problem (lossy compression). PromptCapsule is for *inputs* (prompts), not outputs.
580
+
581
+ **Q: Is the capsule string secure?**
582
+ A: No — treat it like a URL. The 8-char checksum prefix is for integrity, not authentication. If you need signing, that's a roadmap item.
583
+
584
+ **Q: What about very old Python versions?**
585
+ A: We support Python 3.8+. Older versions should still work (no fancy syntax), but we don't test them.
586
+
587
+ **Q: Can I use multiple backends at once?**
588
+ A: Yes! Just pass different backends to different compress/decompress calls. Each backend is independent.
589
+
590
+ ---
591
+
592
+ ## See Also
593
+
594
+ - [LLMLingua](https://github.com/microsoft/LLMLingua) — Lossy prompt compression
595
+ - [LangChain Hub](https://smith.langchain.com/) — Prompt management platform
596
+ - [zlib Documentation](https://www.zlib.net/) — Compression format
597
+
598
+ ---
599
+
600
+ ## Feedback & Support
601
+
602
+ - 📖 [Documentation](https://github.com/UdayaNirogi/promptcapsule)
603
+ - 🐛 [Report Issues](https://github.com/UdayaNirogi/promptcapsule/issues)
604
+ - 💬 [Discussions](https://github.com/UdayaNirogi/promptcapsule/discussions)
605
+ - 📧 Email: udaya@example.com
606
+
607
+ ---
608
+
609
+ **Made with ❤️ for developers who care about their prompts.**