auxly-cli 1.0.21__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.
auxly_cli/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """auxly-cli — local-first unified memory for AI agents (binary wrapper)."""
2
+
3
+ from ._runner import __version__, main
4
+
5
+ __all__ = ["main", "__version__"]
auxly_cli/_runner.py ADDED
@@ -0,0 +1,118 @@
1
+ """auxly console-script entry point.
2
+
3
+ The wheel ships only this thin Python package; the actual auxly binary is
4
+ downloaded + verified on FIRST RUN into a per-user cache, then exec'd. Subsequent
5
+ runs use the cache. This keeps the wheel pure-Python (one wheel, all platforms)
6
+ while still verifying the minisign signature like the native installers do.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import platform
13
+ import stat
14
+ import sys
15
+ import urllib.request
16
+ from pathlib import Path
17
+
18
+ from ._verify import manifest_has_hash, sha256_hex, verify_minisign
19
+
20
+ REPO = "Tzamun-Arabia-IT-Co/auxly-memory-cli"
21
+ __version__ = "1.0.21"
22
+
23
+
24
+ def _target() -> tuple[str, str]:
25
+ sys_map = {"darwin": "darwin", "linux": "linux", "windows": "windows"}
26
+ machine = platform.machine().lower()
27
+ arch_map = {"x86_64": "amd64", "amd64": "amd64", "arm64": "arm64", "aarch64": "arm64"}
28
+ osname = sys_map.get(platform.system().lower())
29
+ arch = arch_map.get(machine)
30
+ if not osname or not arch:
31
+ raise SystemExit(f"auxly: unsupported platform {platform.system()}/{machine}")
32
+ ext = ".exe" if osname == "windows" else ""
33
+ return f"auxly-{osname}-{arch}{ext}", ext
34
+
35
+
36
+ def _cache_dir() -> Path:
37
+ base = os.environ.get("XDG_CACHE_HOME") or os.path.join(Path.home(), ".cache")
38
+ root = Path(base) / "auxly"
39
+ d = root / __version__
40
+ d.mkdir(parents=True, exist_ok=True)
41
+ # Restrict to the owner so another local user can't pre-place / swap the cached
42
+ # binary that we exec (the cached copy is run without re-fetching the manifest).
43
+ for p in (root, d):
44
+ try:
45
+ os.chmod(p, 0o700)
46
+ except OSError:
47
+ pass
48
+ return d
49
+
50
+
51
+ def _fetch(url: str) -> bytes:
52
+ req = urllib.request.Request(url, headers={"User-Agent": "auxly-pip-installer"})
53
+ with urllib.request.urlopen(req) as resp: # nosec - https only, verified below
54
+ return resp.read()
55
+
56
+
57
+ def _download_and_verify(dest: Path, bin_name: str) -> None:
58
+ base = f"https://github.com/{REPO}/releases/download/v{__version__}"
59
+ manifest_name = f"auxly-{__version__}-checksums.txt"
60
+ sys.stderr.write(f"auxly: downloading {bin_name} (v{__version__})…\n")
61
+ binary = _fetch(f"{base}/{bin_name}")
62
+
63
+ # Every release this wrapper targets (it is version-locked to a release tag)
64
+ # ships a signed manifest, so verification is REQUIRED by default — a missing or
65
+ # junk manifest aborts rather than running an unverified binary.
66
+ # AUXLY_ALLOW_UNSIGNED=1 relaxes this for emergencies.
67
+ allow_unsigned = os.environ.get("AUXLY_ALLOW_UNSIGNED") == "1"
68
+ try:
69
+ manifest = _fetch(f"{base}/{manifest_name}").decode("utf-8")
70
+ sig = _fetch(f"{base}/{manifest_name}.minisig").decode("utf-8")
71
+ except Exception as e: # noqa: BLE001
72
+ if not allow_unsigned:
73
+ raise SystemExit(
74
+ f"auxly: signed manifest unavailable ({e}) and verification is required "
75
+ "— set AUXLY_ALLOW_UNSIGNED=1 only if you accept an unverified install"
76
+ )
77
+ sys.stderr.write(f"auxly: AUXLY_ALLOW_UNSIGNED=1 — installing unverified ({e})\n")
78
+ manifest = sig = None
79
+
80
+ if manifest and sig:
81
+ import re
82
+
83
+ if not re.search(r"^[0-9a-f]{64}\s+\S", manifest, re.MULTILINE):
84
+ if not allow_unsigned:
85
+ raise SystemExit("auxly: fetched manifest is not a checksums file — refusing")
86
+ sys.stderr.write("auxly: AUXLY_ALLOW_UNSIGNED=1 — manifest not a checksums file; unverified\n")
87
+ else:
88
+ verify_minisign(manifest.encode("utf-8"), sig) # raises on failure
89
+ if not manifest_has_hash(manifest, sha256_hex(binary)):
90
+ raise SystemExit("auxly: binary SHA-256 not in signed manifest — refusing")
91
+ sys.stderr.write("auxly: signature + checksum verified ✔\n")
92
+
93
+ tmp = dest.with_suffix(dest.suffix + ".tmp")
94
+ tmp.write_bytes(binary)
95
+ tmp.chmod(tmp.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
96
+ os.replace(tmp, dest)
97
+
98
+
99
+ def _binary_path() -> Path:
100
+ bin_name, ext = _target()
101
+ dest = _cache_dir() / f"auxly{ext}"
102
+ if not dest.exists() or dest.stat().st_size == 0:
103
+ _download_and_verify(dest, bin_name)
104
+ return dest
105
+
106
+
107
+ def main() -> None:
108
+ binary = _binary_path()
109
+ args = [str(binary), *sys.argv[1:]]
110
+ if os.name == "nt":
111
+ import subprocess
112
+
113
+ sys.exit(subprocess.run(args).returncode)
114
+ os.execv(str(binary), args) # replace this process on POSIX
115
+
116
+
117
+ if __name__ == "__main__":
118
+ main()
auxly_cli/_verify.py ADDED
@@ -0,0 +1,93 @@
1
+ """Minisign + SHA-256 verification for the auxly pip wrapper.
2
+
3
+ Mirrors npm/lib/verify.js and internal/update/verify.go: the release CI signs the
4
+ checksum manifest with minisign (prehashed "ED" mode = ed25519 over BLAKE2b-512).
5
+ We verify that signature against the PINNED public key, plus the SHA-256 integrity
6
+ of the downloaded binary against the signed manifest.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import base64
12
+ import hashlib
13
+
14
+ from cryptography.exceptions import InvalidSignature
15
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
16
+
17
+ # Pinned minisign public key (base64). MUST match internal/update/verify.go.
18
+ PUBKEY_B64 = "RWQfIGHWpXR4MtPvcbWwN1J7mx9FGsCaHMmdIpGMZAKDvmILC2Of5Q/K"
19
+
20
+
21
+ def sha256_hex(data: bytes) -> str:
22
+ return hashlib.sha256(data).hexdigest()
23
+
24
+
25
+ def manifest_has_hash(manifest_text: str, hash_hex: str) -> bool:
26
+ """True if hash_hex is the first whitespace-delimited field of any line
27
+ (full field match, never a substring)."""
28
+ want = hash_hex.lower()
29
+ for line in manifest_text.splitlines():
30
+ parts = line.strip().split()
31
+ if parts and parts[0].lower() == want:
32
+ return True
33
+ return False
34
+
35
+
36
+ def _ed25519_ok(pub_raw: bytes, message: bytes, sig: bytes) -> bool:
37
+ try:
38
+ Ed25519PublicKey.from_public_bytes(pub_raw).verify(sig, message)
39
+ return True
40
+ except InvalidSignature:
41
+ return False
42
+
43
+
44
+ def verify_minisign(file_bytes: bytes, minisig_text: str) -> str:
45
+ """Verify a minisign signature over file_bytes using the pinned key.
46
+
47
+ Verifies BOTH the file signature and the global (trusted-comment) signature,
48
+ exactly as the minisign CLI does. Raises ValueError on any failure; returns
49
+ the trusted comment on success.
50
+ """
51
+ pub = base64.b64decode(PUBKEY_B64)
52
+ if len(pub) != 42:
53
+ raise ValueError("pinned public key malformed")
54
+ pub_keyid = pub[2:10]
55
+ pub_raw = pub[10:42]
56
+
57
+ lines = minisig_text.split("\n")
58
+ if len(lines) < 4:
59
+ raise ValueError("minisig truncated")
60
+
61
+ sig_blob = base64.b64decode(lines[1].strip())
62
+ if len(sig_blob) != 74:
63
+ raise ValueError("minisig signature block malformed")
64
+ algo = sig_blob[0:2]
65
+ sig_keyid = sig_blob[2:10]
66
+ sig = sig_blob[10:74]
67
+ if sig_keyid != pub_keyid:
68
+ raise ValueError("minisign key id mismatch")
69
+
70
+ if algo == b"ED":
71
+ message = hashlib.blake2b(file_bytes, digest_size=64).digest() # prehashed
72
+ elif algo == b"Ed":
73
+ message = file_bytes # legacy
74
+ else:
75
+ raise ValueError(f"unsupported minisign algorithm {algo!r}")
76
+
77
+ if not _ed25519_ok(pub_raw, message, sig):
78
+ raise ValueError("minisign file signature is INVALID")
79
+
80
+ # Global signature binds the trusted comment to the signature.
81
+ trusted_line = lines[2]
82
+ global_sig = base64.b64decode(lines[3].strip())
83
+ if len(global_sig) != 64:
84
+ raise ValueError("minisig global signature malformed")
85
+ prefix = "trusted comment:"
86
+ idx = trusted_line.find(prefix)
87
+ trusted_comment = trusted_line[idx + len(prefix):] if idx >= 0 else ""
88
+ if trusted_comment[:1].isspace(): # strip the single separator space
89
+ trusted_comment = trusted_comment[1:]
90
+ global_msg = sig + trusted_comment.encode("utf-8")
91
+ if not _ed25519_ok(pub_raw, global_msg, global_sig):
92
+ raise ValueError("minisign trusted-comment signature is INVALID")
93
+ return trusted_comment
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.4
2
+ Name: auxly-cli
3
+ Version: 1.0.21
4
+ Summary: Local-first unified memory system for AI agents — downloads the signature-verified auxly binary.
5
+ Author-email: "Tzamun Arabia IT Co." <hi@auxly.io>
6
+ License: MIT
7
+ Project-URL: Homepage, https://auxly.io
8
+ Project-URL: Repository, https://github.com/Tzamun-Arabia-IT-Co/auxly-memory-cli
9
+ Project-URL: Issues, https://github.com/Tzamun-Arabia-IT-Co/auxly-memory-cli/issues
10
+ Keywords: auxly,ai,memory,mcp,cli,agents
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: cryptography>=3.4
14
+
15
+ # auxly-cli
16
+
17
+ Local-first unified memory system for AI agents. This package installs a thin
18
+ launcher that downloads the prebuilt [`auxly`](https://auxly.io) binary for your
19
+ platform on first run and verifies it against the project's **minisign-signed**
20
+ checksum manifest.
21
+
22
+ ```bash
23
+ pip install auxly-cli
24
+ auxly --help
25
+ ```
26
+
27
+ ## How it works
28
+
29
+ - `pip install auxly-cli` installs a small pure-Python package (one wheel, all
30
+ platforms).
31
+ - The first time you run `auxly`, it downloads `auxly-<os>-<arch>` from the GitHub
32
+ release matching this package's version, fetches the signed
33
+ `auxly-<version>-checksums.txt` + `.minisig`, verifies the **minisign signature**
34
+ against the pinned public key, checks the binary's **SHA-256** against the signed
35
+ manifest, then caches and execs it. Later runs use the cache.
36
+
37
+ Set `AUXLY_REQUIRE_SIGNATURE=1` to hard-fail if a signed manifest is unavailable.
38
+ The cache lives under `$XDG_CACHE_HOME/auxly` (or `~/.cache/auxly`).
39
+
40
+ Supported: macOS / Linux / Windows on x64 / arm64. Python ≥ 3.8.
41
+
42
+ License: MIT · <https://github.com/Tzamun-Arabia-IT-Co/auxly-memory-cli>
@@ -0,0 +1,8 @@
1
+ auxly_cli/__init__.py,sha256=nep1av7udRmB-x_ecNcsygV3pGQctLNoxYlLciwExaI,154
2
+ auxly_cli/_runner.py,sha256=8LGnY3Xo_ey-fP0Zj7Ol_s4uKQC8oZZTenuyOFz-faQ,4529
3
+ auxly_cli/_verify.py,sha256=PSIv2rxwNnkAVA1pB15_Ahp7CH_sMrq_JIzKYMGrbcs,3416
4
+ auxly_cli-1.0.21.dist-info/METADATA,sha256=8MsJOUvmQddYoIb2Vgje63W2UxDl27g1uOsy_pAcNfE,1671
5
+ auxly_cli-1.0.21.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
6
+ auxly_cli-1.0.21.dist-info/entry_points.txt,sha256=Ms4dxdljXHM9JP-ffAYjr3C1wI7HJheOfpkJBEonzEc,49
7
+ auxly_cli-1.0.21.dist-info/top_level.txt,sha256=A_cEYpc363qz8PjoDFanzCKnPEr6-Ik9OmqOJEVw6rI,10
8
+ auxly_cli-1.0.21.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ auxly = auxly_cli._runner:main
@@ -0,0 +1 @@
1
+ auxly_cli