codexfer 0.1.0__tar.gz → 0.2.1__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.
- {codexfer-0.1.0 → codexfer-0.2.1}/PKG-INFO +1 -1
- {codexfer-0.1.0 → codexfer-0.2.1}/codexfer/__init__.py +1 -1
- {codexfer-0.1.0 → codexfer-0.2.1}/codexfer/api_client.py +6 -12
- {codexfer-0.1.0 → codexfer-0.2.1}/codexfer/cli.py +2 -2
- {codexfer-0.1.0 → codexfer-0.2.1}/codexfer/compression.py +7 -6
- {codexfer-0.1.0 → codexfer-0.2.1}/codexfer/config.py +9 -3
- {codexfer-0.1.0 → codexfer-0.2.1}/codexfer/encryption.py +2 -3
- {codexfer-0.1.0 → codexfer-0.2.1}/codexfer/package_format.py +2 -5
- {codexfer-0.1.0 → codexfer-0.2.1}/codexfer/utils.py +2 -2
- {codexfer-0.1.0 → codexfer-0.2.1}/codexfer.egg-info/PKG-INFO +1 -1
- {codexfer-0.1.0 → codexfer-0.2.1}/pyproject.toml +2 -2
- {codexfer-0.1.0 → codexfer-0.2.1}/README.md +0 -0
- {codexfer-0.1.0 → codexfer-0.2.1}/codexfer.egg-info/SOURCES.txt +0 -0
- {codexfer-0.1.0 → codexfer-0.2.1}/codexfer.egg-info/dependency_links.txt +0 -0
- {codexfer-0.1.0 → codexfer-0.2.1}/codexfer.egg-info/entry_points.txt +0 -0
- {codexfer-0.1.0 → codexfer-0.2.1}/codexfer.egg-info/requires.txt +0 -0
- {codexfer-0.1.0 → codexfer-0.2.1}/codexfer.egg-info/top_level.txt +0 -0
- {codexfer-0.1.0 → codexfer-0.2.1}/setup.cfg +0 -0
- {codexfer-0.1.0 → codexfer-0.2.1}/tests/test_core.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""CodeXfer: end-to-end encrypted code sharing."""
|
|
2
|
-
__version__ = "0.1
|
|
2
|
+
__version__ = "0.2.1"
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
"""HTTP client for the CodeXfer backend (Cloudflare Worker).
|
|
1
|
+
"""HTTP client for the CodeXfer backend (Cloudflare Worker).
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
end-to-end on one machine without any cloud account -- useful for testing
|
|
6
|
-
before you deploy the Worker.
|
|
3
|
+
Defaults to the maintainer's deployed Worker (see config.py). Falls back to
|
|
4
|
+
a local folder only if that URL is explicitly overridden to empty.
|
|
7
5
|
"""
|
|
8
6
|
from __future__ import annotations
|
|
9
7
|
|
|
@@ -47,9 +45,7 @@ class ApiClient:
|
|
|
47
45
|
dest = self.local_dir / f"{package_id}.cxf"
|
|
48
46
|
shutil.copyfile(cxf_path, dest)
|
|
49
47
|
logger.warning(
|
|
50
|
-
"CODEXFER_API_URL not set -- saved package locally to %s
|
|
51
|
-
"(no real upload happened; set CODEXFER_API_URL to use the "
|
|
52
|
-
"Cloudflare backend).",
|
|
48
|
+
"CODEXFER_API_URL not set -- saved package locally to %s.",
|
|
53
49
|
dest,
|
|
54
50
|
)
|
|
55
51
|
|
|
@@ -65,9 +61,7 @@ class ApiClient:
|
|
|
65
61
|
src = self.local_dir / f"{package_id}.cxf"
|
|
66
62
|
if not src.exists():
|
|
67
63
|
raise FileNotFoundError(
|
|
68
|
-
f"No local package '{package_id}' found in {self.local_dir}.
|
|
69
|
-
"If the sender used a real deployment, set CODEXFER_API_URL "
|
|
70
|
-
"to the same Worker URL before running 'codexfer build'."
|
|
64
|
+
f"No local package '{package_id}' found in {self.local_dir}."
|
|
71
65
|
)
|
|
72
66
|
shutil.copyfile(src, destination)
|
|
73
|
-
return destination
|
|
67
|
+
return destination
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""CodeXfer CLI -- exactly three commands: pack, share, build."""
|
|
1
|
+
"""CodeXfer CLI -- exactly three commands: pack, share, build."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import tempfile
|
|
@@ -131,4 +131,4 @@ def build(
|
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
if __name__ == "__main__":
|
|
134
|
-
app()
|
|
134
|
+
app()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Project compression: zip a project tree while excluding noise directories,
|
|
1
|
+
"""Project compression: zip a project tree while excluding noise directories,
|
|
2
2
|
preserving hidden files, binary files, and folder structure exactly.
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
@@ -17,6 +17,8 @@ DEFAULT_EXCLUDES = [
|
|
|
17
17
|
".mypy_cache", ".mypy_cache/*", ".pytest_cache", ".pytest_cache/*",
|
|
18
18
|
"dist", "dist/*", "build", "build/*", "*.egg-info", "*.egg-info/*",
|
|
19
19
|
".codexfer_pack.zip", "*.cxf",
|
|
20
|
+
"target", "target/*", ".gradle", ".gradle/*", "out", "out/*",
|
|
21
|
+
".idea", ".idea/*", ".vscode", ".vscode/*",
|
|
20
22
|
]
|
|
21
23
|
|
|
22
24
|
|
|
@@ -41,7 +43,9 @@ def compress_project(
|
|
|
41
43
|
patterns = DEFAULT_EXCLUDES + (excludes or [])
|
|
42
44
|
|
|
43
45
|
file_count = 0
|
|
44
|
-
with zipfile.ZipFile(
|
|
46
|
+
with zipfile.ZipFile(
|
|
47
|
+
output_path, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9
|
|
48
|
+
) as zf:
|
|
45
49
|
for root, dirnames, filenames in os.walk(project_dir):
|
|
46
50
|
rel_root = os.path.relpath(root, project_dir)
|
|
47
51
|
dirnames[:] = [
|
|
@@ -51,9 +55,6 @@ def compress_project(
|
|
|
51
55
|
for name in filenames:
|
|
52
56
|
rel_path = os.path.normpath(os.path.join(rel_root, name))
|
|
53
57
|
abs_path = Path(os.path.join(root, name)).resolve()
|
|
54
|
-
# Never zip the archive into itself (it may already exist on
|
|
55
|
-
# disk while we're actively writing to it) and skip any
|
|
56
|
-
# leftover .cxf packages from a previous share.
|
|
57
58
|
if abs_path == output_path or abs_path.suffix == ".cxf":
|
|
58
59
|
continue
|
|
59
60
|
if _is_excluded(rel_path, patterns):
|
|
@@ -70,4 +71,4 @@ def extract_project(archive_path: str | Path, destination_dir: str | Path) -> Pa
|
|
|
70
71
|
destination_dir.mkdir(parents=True, exist_ok=True)
|
|
71
72
|
with zipfile.ZipFile(archive_path, mode="r") as zf:
|
|
72
73
|
zf.extractall(destination_dir)
|
|
73
|
-
return destination_dir
|
|
74
|
+
return destination_dir
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Central configuration for CodeXfer.
|
|
1
|
+
"""Central configuration for CodeXfer.
|
|
2
2
|
|
|
3
3
|
All values can be overridden via environment variables so the CLI works
|
|
4
4
|
identically for every user without editing source.
|
|
@@ -11,7 +11,13 @@ from dataclasses import dataclass
|
|
|
11
11
|
|
|
12
12
|
@dataclass(frozen=True)
|
|
13
13
|
class Settings:
|
|
14
|
-
|
|
14
|
+
# Default points at the maintainer's deployed Cloudflare Worker so
|
|
15
|
+
# `pip install codexfer` works out of the box for every user with just
|
|
16
|
+
# a Package ID + Access Code -- no setup required. Still overridable
|
|
17
|
+
# via CODEXFER_API_URL for anyone running their own backend.
|
|
18
|
+
api_base_url: str = os.environ.get(
|
|
19
|
+
"CODEXFER_API_URL", "https://codexfer.anvss499.workers.dev"
|
|
20
|
+
)
|
|
15
21
|
local_fallback_dir: str = os.environ.get(
|
|
16
22
|
"CODEXFER_LOCAL_DIR", os.path.expanduser("~/.codexfer/packages")
|
|
17
23
|
)
|
|
@@ -21,4 +27,4 @@ class Settings:
|
|
|
21
27
|
kdf_iterations: int = 390_000 # PBKDF2-HMAC-SHA256 iterations
|
|
22
28
|
|
|
23
29
|
|
|
24
|
-
settings = Settings()
|
|
30
|
+
settings = Settings()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""AES-256-GCM encryption with PBKDF2-HMAC-SHA256 key derivation.
|
|
1
|
+
"""AES-256-GCM encryption with PBKDF2-HMAC-SHA256 key derivation.
|
|
2
2
|
|
|
3
3
|
Design:
|
|
4
4
|
- Access code is the only secret. It is generated locally, never sent
|
|
@@ -51,5 +51,4 @@ def encrypt_bytes(plaintext: bytes, access_code: str) -> EncryptedBlob:
|
|
|
51
51
|
|
|
52
52
|
def decrypt_bytes(blob: EncryptedBlob, access_code: str) -> bytes:
|
|
53
53
|
key = _derive_key(access_code, blob.salt)
|
|
54
|
-
|
|
55
|
-
return AESGCM(key).decrypt(blob.nonce, blob.ciphertext, associated_data=None)
|
|
54
|
+
return AESGCM(key).decrypt(blob.nonce, blob.ciphertext, associated_data=None)
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
"""The .cxf container format: a small JSON header + the encrypted archive.
|
|
1
|
+
"""The .cxf container format: a small JSON header + the encrypted archive.
|
|
2
2
|
|
|
3
3
|
Layout on disk:
|
|
4
4
|
[4 bytes] header length (big-endian uint32)
|
|
5
5
|
[N bytes] UTF-8 JSON header
|
|
6
6
|
[rest] raw ciphertext bytes (AES-GCM, tag appended)
|
|
7
|
-
|
|
8
|
-
The header never contains secrets. It is safe for the server to read it,
|
|
9
|
-
though CodeXfer's server never needs to.
|
|
10
7
|
"""
|
|
11
8
|
from __future__ import annotations
|
|
12
9
|
|
|
@@ -54,4 +51,4 @@ def read_cxf(path: str | Path) -> tuple[CxfHeader, bytes]:
|
|
|
54
51
|
ciphertext = f.read()
|
|
55
52
|
header_dict = json.loads(header_bytes.decode("utf-8"))
|
|
56
53
|
header = CxfHeader(**header_dict)
|
|
57
|
-
return header, ciphertext
|
|
54
|
+
return header, ciphertext
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Small shared helpers: ids, access codes, logging setup."""
|
|
1
|
+
"""Small shared helpers: ids, access codes, logging setup."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import logging
|
|
@@ -26,4 +26,4 @@ def generate_access_code(groups: int = 3, group_len: int = 4) -> str:
|
|
|
26
26
|
"".join(secrets.choice(alphabet) for _ in range(group_len))
|
|
27
27
|
for _ in range(groups)
|
|
28
28
|
]
|
|
29
|
-
return "-".join(parts)
|
|
29
|
+
return "-".join(parts)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codexfer"
|
|
7
|
-
version = "0.1
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "End-to-end encrypted code sharing for developers."
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
dependencies = [
|
|
@@ -21,4 +21,4 @@ dev = ["pytest>=8.0"]
|
|
|
21
21
|
codexfer = "codexfer.cli:app"
|
|
22
22
|
|
|
23
23
|
[tool.setuptools.packages.find]
|
|
24
|
-
include = ["codexfer*"]
|
|
24
|
+
include = ["codexfer*"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|