justob 1.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.
justob/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """justob — CLI for the Obfuscate API."""
2
+ from .cli import main, run # noqa: F401
3
+
4
+ __version__ = "1.0.0"
justob/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ # Enables `python -m justob ./file.zip` in addition to the `justob` entry point.
2
+ from .cli import main
3
+ import sys
4
+
5
+ if __name__ == "__main__":
6
+ sys.exit(main())
justob/cli.py ADDED
@@ -0,0 +1,114 @@
1
+ """
2
+ justob.cli — argparse front-end for the Obfuscate API.
3
+
4
+ Identical logic to the standalone /justob.py script, but installed as a
5
+ proper package so users get a `justob` command on $PATH after
6
+ `pip install justob`.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import mimetypes
12
+ import os
13
+ import sys
14
+ import uuid
15
+ from urllib import request as _req
16
+ from urllib.error import HTTPError, URLError
17
+
18
+ __version__ = "1.0.0"
19
+ DEFAULT_ENDPOINT = os.environ.get(
20
+ "JUSTOB_ENDPOINT",
21
+ "https://project--36cd11b2-d68a-4f5c-b183-df77d18fac1d.lovable.app",
22
+ )
23
+
24
+
25
+ def _encode_multipart(fields: dict, file_path: str):
26
+ """Build a multipart/form-data body using only the stdlib."""
27
+ boundary = uuid.uuid4().hex
28
+ crlf = b"\r\n"
29
+ body = bytearray()
30
+ for name, value in fields.items():
31
+ body += f"--{boundary}\r\n".encode()
32
+ body += f'Content-Disposition: form-data; name="{name}"\r\n\r\n'.encode()
33
+ body += str(value).encode() + crlf
34
+ fname = os.path.basename(file_path)
35
+ ctype = mimetypes.guess_type(fname)[0] or "application/zip"
36
+ with open(file_path, "rb") as fh:
37
+ data = fh.read()
38
+ body += f"--{boundary}\r\n".encode()
39
+ body += (
40
+ f'Content-Disposition: form-data; name="file"; filename="{fname}"\r\n'
41
+ f"Content-Type: {ctype}\r\n\r\n"
42
+ ).encode()
43
+ body += data + crlf
44
+ body += f"--{boundary}--\r\n".encode()
45
+ return bytes(body), f"multipart/form-data; boundary={boundary}"
46
+
47
+
48
+ def run(argv=None) -> int:
49
+ p = argparse.ArgumentParser(
50
+ prog="justob",
51
+ description="Obfuscate a ZIP archive via the Obfuscate API.",
52
+ )
53
+ p.add_argument("zip", nargs="?", help="Path to the .zip file to obfuscate.")
54
+ p.add_argument("--profile", default="maximum",
55
+ choices=["light", "standard", "maximum"])
56
+ p.add_argument("--strip-console", default="true")
57
+ p.add_argument("--encrypt-strings", default="true")
58
+ p.add_argument("--include-skipped", default="false")
59
+ p.add_argument("--inject-header", default="false")
60
+ p.add_argument("--header", default="Sealed by Obfuscate")
61
+ p.add_argument("--out", default=None)
62
+ p.add_argument("--endpoint", default=DEFAULT_ENDPOINT)
63
+ p.add_argument("--version", action="version", version=f"justob {__version__}")
64
+ args = p.parse_args(argv)
65
+
66
+ if not args.zip:
67
+ p.print_help()
68
+ return 1
69
+ if not os.path.isfile(args.zip):
70
+ print(f"justob: file not found: {args.zip}", file=sys.stderr); return 2
71
+ if not args.zip.lower().endswith(".zip"):
72
+ print("justob: input must be a .zip archive", file=sys.stderr); return 2
73
+
74
+ out_path = args.out or args.zip.rsplit(".zip", 1)[0] + ".obfuscated.zip"
75
+ url = args.endpoint.rstrip("/") + "/api/public/obfuscate"
76
+ fields = {
77
+ "profile": args.profile,
78
+ "stripConsole": args.strip_console,
79
+ "encryptStrings": args.encrypt_strings,
80
+ "includeSkipped": args.include_skipped,
81
+ "injectHeader": args.inject_header,
82
+ "headerText": args.header,
83
+ }
84
+ body, content_type = _encode_multipart(fields, args.zip)
85
+ print(f"→ POST {url} ({len(body)/(1024*1024):.2f} MB, profile={args.profile})")
86
+ req = _req.Request(url, data=body, method="POST",
87
+ headers={"Content-Type": content_type,
88
+ "User-Agent": f"justob/{__version__}"})
89
+ try:
90
+ with _req.urlopen(req, timeout=300) as resp:
91
+ payload = resp.read()
92
+ total = resp.headers.get("x-files-total", "?")
93
+ obf = resp.headers.get("x-files-obfuscated", "?")
94
+ dur = resp.headers.get("x-duration-ms", "?")
95
+ except HTTPError as e:
96
+ print(f"justob: server returned {e.code}: {e.read().decode(errors='replace')}",
97
+ file=sys.stderr); return 3
98
+ except URLError as e:
99
+ print(f"justob: network error: {e}", file=sys.stderr); return 3
100
+
101
+ with open(out_path, "wb") as fh:
102
+ fh.write(payload)
103
+ print(f"✓ wrote {out_path} ({len(payload)/1024:.1f} KB)")
104
+ print(f" files: {obf}/{total} obfuscated · {dur}ms")
105
+ return 0
106
+
107
+
108
+ def main() -> int:
109
+ """Entry point referenced by [project.scripts] in pyproject.toml."""
110
+ return run()
111
+
112
+
113
+ if __name__ == "__main__":
114
+ sys.exit(main())
@@ -0,0 +1,56 @@
1
+ Metadata-Version: 2.4
2
+ Name: justob
3
+ Version: 1.0.0
4
+ Summary: CLI for the Obfuscate API — seal a ZIP of JS/TS source from your terminal.
5
+ Author: Obfuscate
6
+ License: MIT
7
+ Project-URL: Homepage, https://project--36cd11b2-d68a-4f5c-b183-df77d18fac1d.lovable.app
8
+ Project-URL: API, https://project--36cd11b2-d68a-4f5c-b183-df77d18fac1d.lovable.app/api/public/obfuscate
9
+ Keywords: obfuscate,javascript,minify,cli,zip
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Build Tools
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+
19
+ # justob
20
+
21
+ CLI for the Obfuscate API — seal a ZIP of JS/TS source from your terminal.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install justob # any system with Python 3.8+
27
+ pkg install python && pip install justob # Termux (Android)
28
+ sudo apt install pipx && pipx install justob # Debian / Ubuntu
29
+ ```
30
+
31
+ ## Use
32
+
33
+ ```bash
34
+ justob ./my-project.zip # default: profile=maximum
35
+ justob ./app.zip --profile standard --out sealed.zip
36
+ justob --help
37
+ ```
38
+
39
+ ## Flags
40
+
41
+ | Flag | Default | Notes |
42
+ |------|---------|-------|
43
+ | `--profile` | `maximum` | `light` / `standard` / `maximum` |
44
+ | `--strip-console` | `true` | Remove `console.*` calls |
45
+ | `--encrypt-strings` | `true` | Base64-encode string literals |
46
+ | `--include-skipped` | `false` | Repack `node_modules` / `.git` untouched |
47
+ | `--inject-header` | `false` | Prepend a license header |
48
+ | `--header` | `Sealed by Obfuscate` | Header text |
49
+ | `--out` | `<name>.obfuscated.zip` | Output path |
50
+ | `--endpoint` | the hosted site | Override with `$JUSTOB_ENDPOINT` |
51
+
52
+ ## Zero dependencies
53
+
54
+ `justob` uses only the Python stdlib (`urllib`, `argparse`). Works on
55
+ Termux, Alpine, minimal Docker images, CI runners — anywhere Python 3.8+
56
+ runs.
@@ -0,0 +1,8 @@
1
+ justob/__init__.py,sha256=cMVprk7D0VeZyZfgXwoYY84BnlVGRlIwMkUdPo3IoGc,108
2
+ justob/__main__.py,sha256=x7kVdomkteoKLRuaK4XVXlq3FgD-3nfM_Rp96y0g9Zo,163
3
+ justob/cli.py,sha256=6muNzAfym0-1P82pKgN5bBSOwi_z6vZ1_ZIMSciBRe8,4253
4
+ justob-1.0.0.dist-info/METADATA,sha256=tRIPWxl2_Pg1qGzroAREDrBgeU5xEBNhv7qgnKBlcog,1999
5
+ justob-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
6
+ justob-1.0.0.dist-info/entry_points.txt,sha256=QZlSlARLQZDKKiz5XGQNAWfPD2_8qp10gZr4qeFfEss,43
7
+ justob-1.0.0.dist-info/top_level.txt,sha256=50VDY2XHjn2o9GC2q25QAejll1ajJDXwfnJb3OxzQV4,7
8
+ justob-1.0.0.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
+ justob = justob.cli:main
@@ -0,0 +1 @@
1
+ justob