klickd 3.0.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.
klickd/__init__.py ADDED
@@ -0,0 +1,35 @@
1
+ # klickd — Official Python library for .klickd portable AI context files
2
+ # SPDX-License-Identifier: CC0-1.0
3
+ #
4
+ # One soul. Any model. Any body.
5
+ #
6
+ # Repository: https://github.com/Davincc77/klickdskill
7
+ # DOI: https://doi.org/10.5281/zenodo.20262530
8
+
9
+ from .decode import load_klickd
10
+ from .encode import save_klickd
11
+ from .errors import KlickdError, KlickdErrorCode, HTTP_STATUS
12
+ from ._types import (
13
+ KlickdPayload,
14
+ KlickdEnvelope,
15
+ KlickdMemoryEntry,
16
+ KlickdIdentity,
17
+ KlickdContext,
18
+ KlickdKnowledge,
19
+ )
20
+
21
+ __version__ = "3.0.0"
22
+ __all__ = [
23
+ "load_klickd",
24
+ "save_klickd",
25
+ "KlickdError",
26
+ "KlickdErrorCode",
27
+ "HTTP_STATUS",
28
+ "KlickdPayload",
29
+ "KlickdEnvelope",
30
+ "KlickdMemoryEntry",
31
+ "KlickdIdentity",
32
+ "KlickdContext",
33
+ "KlickdKnowledge",
34
+ "__version__",
35
+ ]
klickd/_types.py ADDED
@@ -0,0 +1,84 @@
1
+ # .klickd v3 — Python TypedDict definitions
2
+ # SPDX-License-Identifier: CC0-1.0
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, List, Literal, Optional
7
+ from typing_extensions import TypedDict, Required
8
+
9
+
10
+ class KlickdKdfArgon2idParams(TypedDict):
11
+ m: int
12
+ t: int
13
+ p: int
14
+
15
+
16
+ class KlickdKdfArgon2id(TypedDict):
17
+ name: Literal["argon2id"]
18
+ params: KlickdKdfArgon2idParams
19
+ salt: str # base64
20
+
21
+
22
+ class KlickdKdfPbkdf2Params(TypedDict):
23
+ iterations: int
24
+
25
+
26
+ class KlickdKdfPbkdf2(TypedDict):
27
+ name: Literal["pbkdf2-sha256"]
28
+ params: KlickdKdfPbkdf2Params
29
+ salt: str # base64
30
+
31
+
32
+ class KlickdCipher(TypedDict):
33
+ name: Literal["AES-256-GCM"]
34
+ iv: str # base64
35
+
36
+
37
+ class KlickdEnvelope(TypedDict, total=False):
38
+ klickd_version: Required[str]
39
+ encrypted: Required[bool]
40
+ domain: Required[str]
41
+ created_at: Required[str] # RFC 3339 UTC Z
42
+ kdf: Required[dict] # KlickdKdfArgon2id | KlickdKdfPbkdf2
43
+ cipher: Required[KlickdCipher]
44
+ ciphertext: Required[str] # base64
45
+
46
+
47
+ class KlickdMemoryEntry(TypedDict, total=False):
48
+ id: Required[str] # UUID v4
49
+ ts: Required[str] # RFC 3339 UTC Z
50
+ role: Required[str] # user | assistant | system
51
+ content: Required[str]
52
+ modality: Required[str] # text | image | audio | tool_call
53
+ tags: List[str]
54
+
55
+
56
+ class KlickdIdentity(TypedDict, total=False):
57
+ name: str
58
+ language: str
59
+ timezone: str
60
+ communication_style: str
61
+
62
+
63
+ class KlickdContext(TypedDict, total=False):
64
+ current_state: str
65
+ decisions_locked: List[str]
66
+ artifacts: List[Any]
67
+ summary: str
68
+
69
+
70
+ class KlickdKnowledge(TypedDict, total=False):
71
+ mastered: List[str]
72
+ gaps: List[str]
73
+ next_steps: List[str]
74
+
75
+
76
+ class KlickdPayload(TypedDict, total=False):
77
+ payload_schema_version: Required[str]
78
+ domain_schema_version: Required[str]
79
+ identity: KlickdIdentity
80
+ agent_instructions: str
81
+ user_preferences: dict
82
+ context: KlickdContext
83
+ knowledge: KlickdKnowledge
84
+ memory: List[KlickdMemoryEntry]
klickd/decode.py ADDED
@@ -0,0 +1,181 @@
1
+ # .klickd v3 — decode (load) implementation
2
+ # SPDX-License-Identifier: CC0-1.0
3
+
4
+ from __future__ import annotations
5
+
6
+ import base64
7
+ import hashlib
8
+ import json
9
+ from typing import Any
10
+
11
+ import jcs
12
+ from argon2.low_level import hash_secret_raw, Type
13
+ from cryptography.exceptions import InvalidTag
14
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
15
+
16
+ from .errors import KlickdError, KlickdErrorCode
17
+
18
+ SUPPORTED_MAJOR = 3
19
+ REQUIRED_ENVELOPE_FIELDS = frozenset(
20
+ ["klickd_version", "encrypted", "domain", "created_at", "kdf", "cipher", "ciphertext"]
21
+ )
22
+
23
+
24
+ def _b64_decode(s: str) -> bytes:
25
+ """RFC 4648 §4 standard padded base64 decode."""
26
+ return base64.b64decode(s)
27
+
28
+
29
+ def _derive_key_argon2id(passphrase: str, salt: bytes, m: int, t: int, p: int) -> bytes:
30
+ return hash_secret_raw(
31
+ secret=passphrase.encode("utf-8"),
32
+ salt=salt,
33
+ time_cost=t,
34
+ memory_cost=m,
35
+ parallelism=p,
36
+ hash_len=32,
37
+ type=Type.ID,
38
+ )
39
+
40
+
41
+ def _derive_key_pbkdf2(passphrase: str, salt: bytes, iterations: int) -> bytes:
42
+ return hashlib.pbkdf2_hmac("sha256", passphrase.encode("utf-8"), salt, iterations, dklen=32)
43
+
44
+
45
+ def load_klickd(
46
+ file_bytes: bytes,
47
+ passphrase: str | None = None,
48
+ legacy: bool = False,
49
+ ) -> dict[str, Any]:
50
+ """
51
+ Decrypt and parse a .klickd envelope.
52
+
53
+ Args:
54
+ file_bytes: Raw .klickd file content (UTF-8 JSON bytes).
55
+ passphrase: Decryption passphrase.
56
+ legacy: Set ``True`` to allow legacy PBKDF2-SHA256/600k v2.x files.
57
+ Default: ``False``.
58
+
59
+ Returns:
60
+ The decrypted payload as a dict (KlickdPayload-compatible).
61
+
62
+ Raises:
63
+ KlickdError: On any format, version, authentication, or schema error.
64
+ """
65
+ # Parse envelope JSON
66
+ try:
67
+ envelope: dict[str, Any] = json.loads(file_bytes.decode("utf-8"))
68
+ except (json.JSONDecodeError, UnicodeDecodeError) as exc:
69
+ raise KlickdError(KlickdErrorCode.FORMAT, f"Invalid JSON envelope: {exc}") from exc
70
+
71
+ if not isinstance(envelope, dict):
72
+ raise KlickdError(KlickdErrorCode.FORMAT, "Envelope must be a JSON object.")
73
+
74
+ # Validate required fields
75
+ missing = REQUIRED_ENVELOPE_FIELDS - envelope.keys()
76
+ if missing:
77
+ raise KlickdError(
78
+ KlickdErrorCode.FORMAT,
79
+ f"Missing required envelope fields: {', '.join(sorted(missing))}",
80
+ )
81
+
82
+ # Check major version
83
+ try:
84
+ major = int(str(envelope["klickd_version"]).split(".")[0])
85
+ except (ValueError, IndexError) as exc:
86
+ raise KlickdError(
87
+ KlickdErrorCode.VERSION,
88
+ f"Cannot parse klickd_version: {envelope['klickd_version']}",
89
+ ) from exc
90
+
91
+ if major != SUPPORTED_MAJOR:
92
+ raise KlickdError(
93
+ KlickdErrorCode.VERSION,
94
+ f"Unsupported klickd_version major: {envelope['klickd_version']}. "
95
+ f"This library supports v{SUPPORTED_MAJOR}.x.",
96
+ )
97
+
98
+ # Reconstruct AAD: JCS over the 6 canonical fields
99
+ envelope_for_aad: dict[str, Any] = {
100
+ "klickd_version": envelope["klickd_version"],
101
+ "encrypted": envelope["encrypted"],
102
+ "domain": envelope["domain"],
103
+ "created_at": envelope["created_at"],
104
+ "kdf": envelope["kdf"],
105
+ "cipher": envelope["cipher"],
106
+ }
107
+ aad: bytes = jcs.canonicalize(envelope_for_aad)
108
+
109
+ # Require passphrase
110
+ if not passphrase:
111
+ raise KlickdError(
112
+ KlickdErrorCode.AUTH,
113
+ "A passphrase is required to decrypt this file.",
114
+ )
115
+
116
+ # Derive key
117
+ kdf = envelope["kdf"]
118
+ kdf_name: str = kdf.get("name", "")
119
+
120
+ if kdf_name == "argon2id":
121
+ try:
122
+ salt = _b64_decode(kdf["salt"])
123
+ params = kdf["params"]
124
+ key = _derive_key_argon2id(passphrase, salt, params["m"], params["t"], params["p"])
125
+ except (KeyError, TypeError) as exc:
126
+ raise KlickdError(KlickdErrorCode.FORMAT, f"Invalid Argon2id KDF params: {exc}") from exc
127
+
128
+ elif kdf_name == "pbkdf2-sha256":
129
+ if not legacy:
130
+ raise KlickdError(
131
+ KlickdErrorCode.KDF,
132
+ "Legacy PBKDF2 KDF detected. Set legacy=True to enable reading legacy v2.x files.",
133
+ )
134
+ try:
135
+ salt = _b64_decode(kdf["salt"])
136
+ iterations = kdf["params"]["iterations"]
137
+ key = _derive_key_pbkdf2(passphrase, salt, iterations)
138
+ except (KeyError, TypeError) as exc:
139
+ raise KlickdError(KlickdErrorCode.FORMAT, f"Invalid PBKDF2 KDF params: {exc}") from exc
140
+
141
+ else:
142
+ raise KlickdError(KlickdErrorCode.KDF, f"Unknown KDF: '{kdf_name}'.")
143
+
144
+ # Decode ciphertext (ciphertext || 16-byte GCM tag, per AESGCM convention)
145
+ try:
146
+ ciphertext_with_tag = _b64_decode(envelope["ciphertext"])
147
+ except Exception as exc:
148
+ raise KlickdError(KlickdErrorCode.FORMAT, f"Invalid ciphertext base64: {exc}") from exc
149
+
150
+ if len(ciphertext_with_tag) < 16:
151
+ raise KlickdError(KlickdErrorCode.FORMAT, "Ciphertext too short.")
152
+
153
+ # Decrypt AES-256-GCM
154
+ try:
155
+ iv = _b64_decode(envelope["cipher"]["iv"])
156
+ except (KeyError, Exception) as exc:
157
+ raise KlickdError(KlickdErrorCode.FORMAT, f"Invalid cipher IV: {exc}") from exc
158
+
159
+ try:
160
+ aesgcm = AESGCM(key)
161
+ plaintext = aesgcm.decrypt(iv, ciphertext_with_tag, aad)
162
+ except InvalidTag as exc:
163
+ raise KlickdError(
164
+ KlickdErrorCode.AUTH,
165
+ "Decryption failed: wrong passphrase or corrupted file.",
166
+ ) from exc
167
+
168
+ # Parse payload JSON
169
+ try:
170
+ payload: dict[str, Any] = json.loads(plaintext.decode("utf-8"))
171
+ except (json.JSONDecodeError, UnicodeDecodeError) as exc:
172
+ raise KlickdError(KlickdErrorCode.FORMAT, f"Decrypted data is not valid JSON: {exc}") from exc
173
+
174
+ # Validate payload_schema_version
175
+ if not payload.get("payload_schema_version"):
176
+ raise KlickdError(
177
+ KlickdErrorCode.SCHEMA,
178
+ "Decrypted payload is missing payload_schema_version.",
179
+ )
180
+
181
+ return payload
klickd/encode.py ADDED
@@ -0,0 +1,116 @@
1
+ # .klickd v3 — encode (save) implementation
2
+ # SPDX-License-Identifier: CC0-1.0
3
+
4
+ from __future__ import annotations
5
+
6
+ import base64
7
+ import json
8
+ import os
9
+ from typing import Any
10
+
11
+ import jcs
12
+ from argon2.low_level import hash_secret_raw, Type
13
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
14
+
15
+ from .errors import KlickdError, KlickdErrorCode
16
+
17
+ KLICKD_VERSION = "3.0.0"
18
+ DEFAULT_KDF_PARAMS = {"m": 65536, "t": 3, "p": 1}
19
+
20
+
21
+ def _b64_encode(data: bytes) -> str:
22
+ """RFC 4648 §4 standard padded base64."""
23
+ return base64.b64encode(data).decode("ascii")
24
+
25
+
26
+ def _derive_key_argon2id(passphrase: str, salt: bytes, m: int, t: int, p: int) -> bytes:
27
+ """Derive a 32-byte key using Argon2id."""
28
+ return hash_secret_raw(
29
+ secret=passphrase.encode("utf-8"),
30
+ salt=salt,
31
+ time_cost=t,
32
+ memory_cost=m,
33
+ parallelism=p,
34
+ hash_len=32,
35
+ type=Type.ID,
36
+ )
37
+
38
+
39
+ def save_klickd(
40
+ payload: dict[str, Any],
41
+ passphrase: str,
42
+ domain: str = "education",
43
+ kdf_params: dict[str, int] | None = None,
44
+ ) -> bytes:
45
+ """
46
+ Encrypt a KlickdPayload dict and return a .klickd JSON envelope as UTF-8 bytes.
47
+
48
+ Args:
49
+ payload: Dict conforming to KlickdPayload schema.
50
+ Must include ``payload_schema_version``.
51
+ passphrase: Encryption passphrase (minimum 8 characters).
52
+ domain: .klickd domain tag. Default: ``"education"``.
53
+ kdf_params: Argon2id parameter overrides.
54
+ Defaults to ``{"m": 65536, "t": 3, "p": 1}``.
55
+
56
+ Returns:
57
+ UTF-8 bytes of the complete .klickd JSON envelope.
58
+
59
+ Raises:
60
+ KlickdError: On validation or cryptographic failure.
61
+ """
62
+ params = kdf_params or DEFAULT_KDF_PARAMS
63
+
64
+ # Validate passphrase
65
+ if not passphrase or len(passphrase) < 8:
66
+ raise KlickdError(KlickdErrorCode.WEAK_PASS, "Passphrase must be at least 8 characters.")
67
+
68
+ # Validate payload schema version
69
+ if not payload.get("payload_schema_version"):
70
+ raise KlickdError(KlickdErrorCode.SCHEMA, "payload_schema_version is required.")
71
+
72
+ # Generate fresh CSPRNG salt (16 bytes) and IV (12 bytes)
73
+ salt = os.urandom(16)
74
+ iv = os.urandom(12)
75
+
76
+ # Derive 32-byte key
77
+ key = _derive_key_argon2id(
78
+ passphrase, salt, params["m"], params["t"], params["p"]
79
+ )
80
+
81
+ # Build the 6-field envelope object for AAD (without ciphertext)
82
+ from datetime import datetime, timezone
83
+ created_at = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
84
+
85
+ envelope_for_aad: dict[str, Any] = {
86
+ "klickd_version": KLICKD_VERSION,
87
+ "encrypted": True,
88
+ "domain": domain,
89
+ "created_at": created_at,
90
+ "kdf": {
91
+ "name": "argon2id",
92
+ "params": {"m": params["m"], "t": params["t"], "p": params["p"]},
93
+ "salt": _b64_encode(salt),
94
+ },
95
+ "cipher": {
96
+ "name": "AES-256-GCM",
97
+ "iv": _b64_encode(iv),
98
+ },
99
+ }
100
+
101
+ # AAD = RFC 8785 JCS (JSON Canonicalization Scheme)
102
+ aad: bytes = jcs.canonicalize(envelope_for_aad)
103
+
104
+ # Encrypt payload JSON with AES-256-GCM
105
+ # cryptography's AESGCM appends the 16-byte tag to ciphertext
106
+ aesgcm = AESGCM(key)
107
+ plaintext = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
108
+ ciphertext_with_tag = aesgcm.encrypt(iv, plaintext, aad)
109
+
110
+ # Build final envelope
111
+ envelope: dict[str, Any] = {
112
+ **envelope_for_aad,
113
+ "ciphertext": _b64_encode(ciphertext_with_tag),
114
+ }
115
+
116
+ return json.dumps(envelope, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
klickd/errors.py ADDED
@@ -0,0 +1,32 @@
1
+ # .klickd error definitions
2
+ # SPDX-License-Identifier: CC0-1.0
3
+
4
+ from enum import Enum
5
+
6
+
7
+ class KlickdErrorCode(str, Enum):
8
+ AUTH = "KLICKD_E_AUTH" # Wrong passphrase / GCM tag mismatch
9
+ VERSION = "KLICKD_E_VERSION" # Unsupported klickd_version major
10
+ FORMAT = "KLICKD_E_FORMAT" # Malformed envelope JSON / missing fields
11
+ KDF = "KLICKD_E_KDF" # Unknown/unsupported KDF
12
+ WEAK_PASS = "KLICKD_E_WEAK_PASS" # Passphrase < 8 characters
13
+ SCHEMA = "KLICKD_E_SCHEMA" # Missing payload_schema_version
14
+
15
+
16
+ HTTP_STATUS: dict[KlickdErrorCode, int] = {
17
+ KlickdErrorCode.AUTH: 401,
18
+ KlickdErrorCode.VERSION: 400,
19
+ KlickdErrorCode.FORMAT: 400,
20
+ KlickdErrorCode.KDF: 400,
21
+ KlickdErrorCode.WEAK_PASS: 422,
22
+ KlickdErrorCode.SCHEMA: 400,
23
+ }
24
+
25
+
26
+ class KlickdError(Exception):
27
+ """Raised for all .klickd format and cryptographic errors."""
28
+
29
+ def __init__(self, code: KlickdErrorCode, message: str) -> None:
30
+ super().__init__(f"{code}: {message}")
31
+ self.code = code
32
+ self.http_status: int = HTTP_STATUS[code]
@@ -0,0 +1,138 @@
1
+ Metadata-Version: 2.4
2
+ Name: klickd
3
+ Version: 3.0.0
4
+ Summary: Official Python library for reading and writing .klickd portable AI context files
5
+ Project-URL: Homepage, https://klickd.app
6
+ Project-URL: Repository, https://github.com/Davincc77/klickdskill
7
+ Project-URL: Documentation, https://github.com/Davincc77/klickdskill/blob/main/SPEC.md
8
+ Author-email: "Vince C." <Luxlearn@pm.me>
9
+ License: CC0-1.0
10
+ Keywords: ai-context,encrypted,klickd,memory,portable
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
14
+ Classifier: Programming Language :: Python :: 3
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
+ Classifier: Topic :: Security :: Cryptography
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: argon2-cffi>=23.1
23
+ Requires-Dist: cryptography>=41.0
24
+ Requires-Dist: jcs>=0.2
25
+ Description-Content-Type: text/markdown
26
+
27
+ # klickd
28
+
29
+ Official Python library for reading and writing `.klickd` portable AI context files.
30
+
31
+ **One soul. Any model. Any body.**
32
+
33
+ [![PyPI version](https://img.shields.io/pypi/v/klickd)](https://pypi.org/project/klickd/)
34
+ [![Python](https://img.shields.io/pypi/pyversions/klickd)](https://pypi.org/project/klickd/)
35
+ [![License: CC0-1.0](https://img.shields.io/badge/License-CC0_1.0-lightgrey.svg)](https://creativecommons.org/publicdomain/zero/1.0/)
36
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.20262530.svg)](https://doi.org/10.5281/zenodo.20262530)
37
+
38
+ ---
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install klickd
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Quick start
49
+
50
+ ### Load (decrypt) a `.klickd` file
51
+
52
+ ```python
53
+ from klickd import load_klickd
54
+
55
+ with open("context.klickd", "rb") as f:
56
+ payload = load_klickd(f.read(), passphrase="my-passphrase")
57
+
58
+ print(payload["identity"]["name"])
59
+ print(payload["memory"])
60
+ ```
61
+
62
+ ### Save (encrypt) a `.klickd` file
63
+
64
+ ```python
65
+ from klickd import save_klickd
66
+
67
+ payload = {
68
+ "payload_schema_version": "3.0.0",
69
+ "domain_schema_version": "1.0.0",
70
+ "identity": {"name": "Alice", "language": "en", "timezone": "Europe/Luxembourg"},
71
+ "agent_instructions": "Be concise.",
72
+ "memory": [],
73
+ }
74
+
75
+ klickd_bytes = save_klickd(payload, passphrase="my-passphrase", domain="education")
76
+
77
+ with open("context.klickd", "wb") as f:
78
+ f.write(klickd_bytes)
79
+ ```
80
+
81
+ ### Legacy v2.x files (PBKDF2)
82
+
83
+ ```python
84
+ payload = load_klickd(file_bytes, passphrase="my-passphrase", legacy=True)
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Cryptographic specification (v3.0)
90
+
91
+ | Parameter | Value |
92
+ |---------------|-----------------------------------------|
93
+ | KDF (default) | Argon2id — m=65536, t=3, p=1 |
94
+ | KDF (legacy) | PBKDF2-SHA256 / 600 000 iterations |
95
+ | Cipher | AES-256-GCM |
96
+ | AAD | RFC 8785 JCS over 6 canonical fields |
97
+ | Base64 | RFC 4648 §4 standard padded |
98
+ | Salt | 16 bytes (CSPRNG) |
99
+ | IV | 12 bytes (CSPRNG) |
100
+
101
+ ---
102
+
103
+ ## Error codes
104
+
105
+ | Code | HTTP | Meaning |
106
+ |-----------------------|------|------------------------------------------|
107
+ | `KLICKD_E_AUTH` | 401 | Wrong passphrase / GCM tag mismatch |
108
+ | `KLICKD_E_VERSION` | 400 | Unsupported `klickd_version` major |
109
+ | `KLICKD_E_FORMAT` | 400 | Malformed JSON envelope / missing fields |
110
+ | `KLICKD_E_KDF` | 400 | Unknown or unavailable KDF |
111
+ | `KLICKD_E_WEAK_PASS` | 422 | Passphrase shorter than 8 characters |
112
+ | `KLICKD_E_SCHEMA` | 400 | Missing `payload_schema_version` |
113
+
114
+ ```python
115
+ from klickd import KlickdError, KlickdErrorCode
116
+
117
+ try:
118
+ payload = load_klickd(data, passphrase="wrong")
119
+ except KlickdError as e:
120
+ print(e.code) # KlickdErrorCode.AUTH
121
+ print(e.http_status) # 401
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Links
127
+
128
+ - Specification: [SPEC.md](https://github.com/Davincc77/klickdskill/blob/main/SPEC.md)
129
+ - Repository: [github.com/Davincc77/klickdskill](https://github.com/Davincc77/klickdskill)
130
+ - DOI: [10.5281/zenodo.20262530](https://doi.org/10.5281/zenodo.20262530)
131
+ - Homepage: [klickd.app](https://klickd.app)
132
+
133
+ ---
134
+
135
+ ## License
136
+
137
+ [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/) — Public Domain Dedication.
138
+ Author: Vince C. (Luxlearn, Luxembourg)
@@ -0,0 +1,8 @@
1
+ klickd/__init__.py,sha256=MDpYv_3ZzqKku92X8zTJZIp14FgH7dS8m1jbj9O7-js,818
2
+ klickd/_types.py,sha256=mV7OKseFIdLNKATRjCfUzbAfYxG554dTWKNHfUxoeTU,2075
3
+ klickd/decode.py,sha256=9yNNZVaRPUOdaNp0LEiw691Ou-tJzDO67y0ufSW19bs,6056
4
+ klickd/encode.py,sha256=mwtqReq0aQeTs5UG_k38MgvngZCoHjUGvw4nGLWTITo,3619
5
+ klickd/errors.py,sha256=BKHZRRA5aTbbE0MybNSkCVILp9ZifnkCkT4IOqzcCP8,1078
6
+ klickd-3.0.0.dist-info/METADATA,sha256=youPc90jkDfzApHPGHJx3yetUxRSzBNkmABTsN12TyY,4508
7
+ klickd-3.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
8
+ klickd-3.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any