sitrtech 1.0.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,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: sitrtech
3
+ Version: 1.0.0
4
+ Summary: SitrTech Python code encryption CLI — protect Odoo, Django, Flask, FastAPI and Tornado projects
5
+ Author-email: SitrTech <support@sitrtech.com>
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://sitrtech.com
8
+ Project-URL: Docs, https://sitrtech.com/docs
9
+ Project-URL: Repository, https://github.com/sohaib929/sitr_loader
10
+ Project-URL: Bug Tracker, https://github.com/sohaib929/sitr_loader/issues
11
+ Keywords: odoo,django,flask,fastapi,tornado,encryption,obfuscation,code-protection
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Security :: Cryptography
24
+ Classifier: Topic :: Software Development :: Build Tools
25
+ Requires-Python: >=3.8
26
+ Description-Content-Type: text/markdown
27
+ Requires-Dist: requests>=2.31
28
+ Requires-Dist: rich>=13
29
+ Requires-Dist: click>=8
30
+
31
+ # SitrTech — Python Code Encryption CLI
32
+
33
+ Protect **Odoo, Django, Flask, FastAPI and Tornado** projects with AES-256 encryption, licensing controls, and anti-reverse-engineering in one command.
34
+
35
+ ## Quick start
36
+
37
+ ```bash
38
+ pip install sitrtech
39
+ ```
40
+
41
+ Get your API secret key from [sitrtech.com/api-keys](https://sitrtech.com/api-keys), then:
42
+
43
+ ```bash
44
+ # Encrypt an Odoo addon
45
+ sitr encrypt my_module.zip --framework odoo --secret sk-...
46
+
47
+ # Encrypt a Django project
48
+ sitr encrypt my_project.zip --framework django --secret sk-...
49
+
50
+ # Check your token balance
51
+ sitr balance --secret sk-...
52
+ ```
53
+
54
+ ## Installation
55
+
56
+ ```bash
57
+ pip install sitrtech # latest stable
58
+ pip install sitrtech==1.0.0 # pin a specific version
59
+ ```
60
+
61
+ Python 3.8 → 3.14 · Windows, macOS, Linux
62
+
63
+ ## Usage
64
+
65
+ ### `sitr encrypt`
66
+
67
+ ```
68
+ sitr encrypt <FILE.zip> [OPTIONS]
69
+
70
+ Arguments:
71
+ FILE.zip Zipped project folder
72
+
73
+ Options:
74
+ -f, --framework TEXT odoo | django | flask | fastapi | tornado [default: odoo]
75
+ -v, --version TEXT Framework version (e.g. 16.0 for Odoo)
76
+ -e, --expiry DATE Expiry: 2026-12-31 or 2026-12-31T23:59:00Z
77
+ -n, --network CIDR Allowed IP/CIDR (repeatable)
78
+ -o, --output PATH Output file (default: <input>_encrypted.zip)
79
+ -s, --secret TEXT API secret key [env: SITR_SECRET]
80
+ -b, --base URL API base URL [env: SITR_BASE]
81
+ ```
82
+
83
+ ### Examples
84
+
85
+ ```bash
86
+ # Odoo (default)
87
+ sitr encrypt my_addon.zip --framework odoo
88
+
89
+ # Django with expiry and IP restriction
90
+ sitr encrypt backend.zip --framework django \
91
+ --expiry 2026-12-31 \
92
+ --network 10.0.0.0/24
93
+
94
+ # FastAPI project
95
+ sitr encrypt api.zip --framework fastapi
96
+
97
+ # Flask with multiple IP ranges
98
+ sitr encrypt webapp.zip --framework flask \
99
+ --network 192.168.1.10 \
100
+ --network 10.0.0.0/8
101
+ ```
102
+
103
+ ### `sitr balance`
104
+
105
+ ```bash
106
+ sitr balance --secret sk-...
107
+ # ┌─────────────────────────────┐
108
+ # │ SitrTech Account │
109
+ # │ Tokens │ 5,000 │
110
+ # │ Plan │ Pro │
111
+ # │ Rate │ 0.3 tokens/line │
112
+ # └─────────────────────────────┘
113
+ ```
114
+
115
+ ### `sitr info`
116
+
117
+ ```bash
118
+ sitr info
119
+ # Shows all supported frameworks and required project structure
120
+ ```
121
+
122
+ ## Environment variables
123
+
124
+ | Variable | Description |
125
+ |-----------------|--------------------------------------|
126
+ | `SITR_SECRET` | API secret key (avoids typing it) |
127
+ | `SITR_BASE` | Override API URL (default: sitrtech.com) |
128
+
129
+ ```bash
130
+ export SITR_SECRET=sk-your-key-here
131
+ sitr encrypt my_project.zip --framework django
132
+ ```
133
+
134
+ ## Project structure requirements
135
+
136
+ | Framework | Required entry-point file |
137
+ |-----------|--------------------------|
138
+ | Odoo | `__manifest__.py` |
139
+ | Django | `manage.py` |
140
+ | Flask | `app.py` or `wsgi.py` |
141
+ | FastAPI | `main.py` or `asgi.py` |
142
+ | Tornado | `main.py` or `server.py` |
143
+
144
+ **Always zip the root folder** — not its contents:
145
+
146
+ ```
147
+ # Correct ✓
148
+ zip -r my_addon.zip my_addon/
149
+
150
+ # Wrong ✗
151
+ cd my_addon && zip -r ../my_addon.zip .
152
+ ```
153
+
154
+ ## CI/CD integration
155
+
156
+ ```yaml
157
+ # GitHub Actions
158
+ - name: Encrypt Odoo addon
159
+ env:
160
+ SITR_SECRET: ${{ secrets.SITR_SECRET }}
161
+ run: |
162
+ pip install sitrtech
163
+ sitr encrypt dist/my_addon.zip --framework odoo --output dist/my_addon_encrypted.zip
164
+ ```
165
+
166
+ ## Links
167
+
168
+ - **Documentation**: <https://sitrtech.com/docs>
169
+ - **API Keys**: <https://sitrtech.com/api-keys>
170
+ - **Support**: support@sitrtech.com
@@ -0,0 +1,140 @@
1
+ # SitrTech — Python Code Encryption CLI
2
+
3
+ Protect **Odoo, Django, Flask, FastAPI and Tornado** projects with AES-256 encryption, licensing controls, and anti-reverse-engineering in one command.
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ pip install sitrtech
9
+ ```
10
+
11
+ Get your API secret key from [sitrtech.com/api-keys](https://sitrtech.com/api-keys), then:
12
+
13
+ ```bash
14
+ # Encrypt an Odoo addon
15
+ sitr encrypt my_module.zip --framework odoo --secret sk-...
16
+
17
+ # Encrypt a Django project
18
+ sitr encrypt my_project.zip --framework django --secret sk-...
19
+
20
+ # Check your token balance
21
+ sitr balance --secret sk-...
22
+ ```
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install sitrtech # latest stable
28
+ pip install sitrtech==1.0.0 # pin a specific version
29
+ ```
30
+
31
+ Python 3.8 → 3.14 · Windows, macOS, Linux
32
+
33
+ ## Usage
34
+
35
+ ### `sitr encrypt`
36
+
37
+ ```
38
+ sitr encrypt <FILE.zip> [OPTIONS]
39
+
40
+ Arguments:
41
+ FILE.zip Zipped project folder
42
+
43
+ Options:
44
+ -f, --framework TEXT odoo | django | flask | fastapi | tornado [default: odoo]
45
+ -v, --version TEXT Framework version (e.g. 16.0 for Odoo)
46
+ -e, --expiry DATE Expiry: 2026-12-31 or 2026-12-31T23:59:00Z
47
+ -n, --network CIDR Allowed IP/CIDR (repeatable)
48
+ -o, --output PATH Output file (default: <input>_encrypted.zip)
49
+ -s, --secret TEXT API secret key [env: SITR_SECRET]
50
+ -b, --base URL API base URL [env: SITR_BASE]
51
+ ```
52
+
53
+ ### Examples
54
+
55
+ ```bash
56
+ # Odoo (default)
57
+ sitr encrypt my_addon.zip --framework odoo
58
+
59
+ # Django with expiry and IP restriction
60
+ sitr encrypt backend.zip --framework django \
61
+ --expiry 2026-12-31 \
62
+ --network 10.0.0.0/24
63
+
64
+ # FastAPI project
65
+ sitr encrypt api.zip --framework fastapi
66
+
67
+ # Flask with multiple IP ranges
68
+ sitr encrypt webapp.zip --framework flask \
69
+ --network 192.168.1.10 \
70
+ --network 10.0.0.0/8
71
+ ```
72
+
73
+ ### `sitr balance`
74
+
75
+ ```bash
76
+ sitr balance --secret sk-...
77
+ # ┌─────────────────────────────┐
78
+ # │ SitrTech Account │
79
+ # │ Tokens │ 5,000 │
80
+ # │ Plan │ Pro │
81
+ # │ Rate │ 0.3 tokens/line │
82
+ # └─────────────────────────────┘
83
+ ```
84
+
85
+ ### `sitr info`
86
+
87
+ ```bash
88
+ sitr info
89
+ # Shows all supported frameworks and required project structure
90
+ ```
91
+
92
+ ## Environment variables
93
+
94
+ | Variable | Description |
95
+ |-----------------|--------------------------------------|
96
+ | `SITR_SECRET` | API secret key (avoids typing it) |
97
+ | `SITR_BASE` | Override API URL (default: sitrtech.com) |
98
+
99
+ ```bash
100
+ export SITR_SECRET=sk-your-key-here
101
+ sitr encrypt my_project.zip --framework django
102
+ ```
103
+
104
+ ## Project structure requirements
105
+
106
+ | Framework | Required entry-point file |
107
+ |-----------|--------------------------|
108
+ | Odoo | `__manifest__.py` |
109
+ | Django | `manage.py` |
110
+ | Flask | `app.py` or `wsgi.py` |
111
+ | FastAPI | `main.py` or `asgi.py` |
112
+ | Tornado | `main.py` or `server.py` |
113
+
114
+ **Always zip the root folder** — not its contents:
115
+
116
+ ```
117
+ # Correct ✓
118
+ zip -r my_addon.zip my_addon/
119
+
120
+ # Wrong ✗
121
+ cd my_addon && zip -r ../my_addon.zip .
122
+ ```
123
+
124
+ ## CI/CD integration
125
+
126
+ ```yaml
127
+ # GitHub Actions
128
+ - name: Encrypt Odoo addon
129
+ env:
130
+ SITR_SECRET: ${{ secrets.SITR_SECRET }}
131
+ run: |
132
+ pip install sitrtech
133
+ sitr encrypt dist/my_addon.zip --framework odoo --output dist/my_addon_encrypted.zip
134
+ ```
135
+
136
+ ## Links
137
+
138
+ - **Documentation**: <https://sitrtech.com/docs>
139
+ - **API Keys**: <https://sitrtech.com/api-keys>
140
+ - **Support**: support@sitrtech.com
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sitrtech"
7
+ version = "1.0.0"
8
+ description = "SitrTech Python code encryption CLI — protect Odoo, Django, Flask, FastAPI and Tornado projects"
9
+ readme = "README.md"
10
+ license = { text = "Proprietary" }
11
+ requires-python = ">=3.8"
12
+ authors = [{ name = "SitrTech", email = "support@sitrtech.com" }]
13
+ keywords = [
14
+ "odoo", "django", "flask", "fastapi", "tornado",
15
+ "encryption", "obfuscation", "code-protection",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 5 - Production/Stable",
19
+ "Environment :: Console",
20
+ "Intended Audience :: Developers",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.8",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Programming Language :: Python :: 3.14",
29
+ "Topic :: Security :: Cryptography",
30
+ "Topic :: Software Development :: Build Tools",
31
+ ]
32
+ dependencies = [
33
+ "requests>=2.31",
34
+ "rich>=13",
35
+ "click>=8",
36
+ ]
37
+
38
+ [project.scripts]
39
+ sitr = "sitrtech.cli:main"
40
+ sitrtech = "sitrtech.cli:main"
41
+
42
+ [project.urls]
43
+ Homepage = "https://sitrtech.com"
44
+ Docs = "https://sitrtech.com/docs"
45
+ Repository = "https://github.com/sohaib929/sitr_loader"
46
+ "Bug Tracker" = "https://github.com/sohaib929/sitr_loader/issues"
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """SitrTech — Python code encryption SDK."""
2
+ __version__ = "1.0.0"
3
+ __all__ = ["encrypt", "balance"]
@@ -0,0 +1,492 @@
1
+ """
2
+ sitrtech — Python code encryption CLI
3
+ Encrypt Odoo, Django, Flask, FastAPI and Tornado projects in one command.
4
+
5
+ Usage examples
6
+ --------------
7
+ sitr encrypt my_module.zip --framework odoo --secret sk-...
8
+ sitr encrypt my_app.zip --framework django --secret sk-...
9
+ sitr balance --secret sk-...
10
+ sitr encrypt my_module.zip --framework odoo --expiry 2026-12-31 --network 10.0.0.0/24 --secret sk-...
11
+
12
+ Credentials can also be supplied via env vars:
13
+ SITR_SECRET — API secret key
14
+ SITR_BASE — override the API base URL (default: https://sitrtech.com)
15
+ """
16
+
17
+ from __future__ import annotations
18
+ import hashlib
19
+ import io
20
+ import json
21
+ import os
22
+ import sys
23
+ import zipfile
24
+ from pathlib import Path
25
+ from typing import Optional
26
+
27
+ import click
28
+ import requests
29
+ from rich.console import Console
30
+ from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, TimeElapsedColumn
31
+ from rich.table import Table
32
+ from rich import print as rprint
33
+
34
+ console = Console()
35
+
36
+ API_BASE_DEFAULT = "https://sitrtech.com"
37
+
38
+ FRAMEWORK_HINTS = {
39
+ "odoo": "Requires __manifest__.py at the project root",
40
+ "django": "Requires manage.py at the project root",
41
+ "flask": "Requires app.py / wsgi.py / application.py at the root",
42
+ "fastapi": "Requires main.py / asgi.py at the project root",
43
+ "tornado": "Requires main.py / server.py at the project root",
44
+ }
45
+
46
+ # Files that must always be included in a partial upload so the server can
47
+ # locate the project root and framework entry point.
48
+ _STRUCTURAL_FILENAMES = {
49
+ "__manifest__.py", "__openerp__.py", "__init__.py",
50
+ "manage.py", "app.py", "application.py", "wsgi.py",
51
+ "main.py", "asgi.py", "server.py",
52
+ }
53
+
54
+ MANIFEST_SUFFIX = ".sitr_manifest.json"
55
+
56
+
57
+ # ─────────────────────────────────────────────
58
+ # Incremental-encryption helpers
59
+ # ─────────────────────────────────────────────
60
+
61
+ def _sha256_bytes(data: bytes) -> str:
62
+ return hashlib.sha256(data).hexdigest()
63
+
64
+
65
+ def _get_source_checksums(zip_path: Path) -> dict[str, str]:
66
+ """Return {zip_entry_name: sha256} for every .py file inside the ZIP."""
67
+ checksums: dict[str, str] = {}
68
+ with zipfile.ZipFile(zip_path, "r") as zf:
69
+ for name in zf.namelist():
70
+ if name.endswith(".py") and not name.endswith("/"):
71
+ checksums[name] = _sha256_bytes(zf.read(name))
72
+ return checksums
73
+
74
+
75
+ def _build_partial_zip(source_zip: Path, changed_files: set[str]) -> bytes:
76
+ """
77
+ Build an in-memory ZIP containing only:
78
+ • every file listed in *changed_files*
79
+ • structural files (framework entry-points, __init__.py …) so the server
80
+ can locate the project root even in a partial upload
81
+ • all non-.py assets (views, static, data …) that were originally present
82
+ — they are small and the server needs them to reconstruct paths
83
+ """
84
+ buf = io.BytesIO()
85
+ with zipfile.ZipFile(source_zip, "r") as src:
86
+ with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as dst:
87
+ for name in src.namelist():
88
+ if name.endswith("/"):
89
+ continue
90
+ basename = name.split("/")[-1]
91
+ is_py = name.endswith(".py")
92
+ is_changed = name in changed_files
93
+ is_structural = basename in _STRUCTURAL_FILENAMES
94
+ is_asset = not is_py # non-Python files (XML, CSV, JS …)
95
+ if is_changed or is_structural or is_asset:
96
+ dst.writestr(name, src.read(name))
97
+ buf.seek(0)
98
+ return buf.read()
99
+
100
+
101
+ def _merge_encrypted_zips(
102
+ previous_zip: Path,
103
+ new_encrypted_bytes: bytes,
104
+ output: Path,
105
+ ) -> None:
106
+ """
107
+ Merge *new_encrypted_bytes* (partial encrypted ZIP from server) into
108
+ *previous_zip* and write the result to *output*.
109
+
110
+ Strategy:
111
+ • Copy every entry from *previous_zip*.
112
+ • If an entry's name also appears in the new ZIP, replace it with the
113
+ fresh encrypted version.
114
+ • Append any brand-new entries that only exist in the new ZIP.
115
+ """
116
+ new_entries: dict[str, bytes] = {}
117
+ with zipfile.ZipFile(io.BytesIO(new_encrypted_bytes), "r") as nz:
118
+ for item in nz.infolist():
119
+ if not item.filename.endswith("/"):
120
+ new_entries[item.filename] = nz.read(item.filename)
121
+
122
+ merged = io.BytesIO()
123
+ with zipfile.ZipFile(previous_zip, "r") as pz:
124
+ with zipfile.ZipFile(merged, "w", compression=zipfile.ZIP_DEFLATED) as out:
125
+ for item in pz.infolist():
126
+ if item.filename.endswith("/"):
127
+ continue
128
+ if item.filename in new_entries:
129
+ # Replace with freshly encrypted version
130
+ out.writestr(item.filename, new_entries.pop(item.filename))
131
+ else:
132
+ out.writestr(item.filename, pz.read(item.filename))
133
+ # Append files that are new in this increment
134
+ for name, data in new_entries.items():
135
+ out.writestr(name, data)
136
+
137
+ output.write_bytes(merged.getvalue())
138
+
139
+
140
+ def _manifest_path_for(output: Path) -> Path:
141
+ return output.parent / (output.stem + MANIFEST_SUFFIX)
142
+
143
+
144
+ def _save_manifest(output: Path, framework: str, checksums: dict[str, str]) -> Path:
145
+ mp = _manifest_path_for(output)
146
+ mp.write_text(
147
+ json.dumps({"version": "1.0", "framework": framework, "files": checksums}, indent=2),
148
+ encoding="utf-8",
149
+ )
150
+ return mp
151
+
152
+
153
+ def _load_manifest(output: Path) -> dict | None:
154
+ mp = _manifest_path_for(output)
155
+ if mp.exists():
156
+ try:
157
+ return json.loads(mp.read_text(encoding="utf-8"))
158
+ except Exception:
159
+ return None
160
+ return None
161
+
162
+
163
+ def _resolve_base(ctx_obj: dict) -> str:
164
+ return (
165
+ ctx_obj.get("base")
166
+ or os.getenv("SITR_BASE", "")
167
+ or API_BASE_DEFAULT
168
+ ).rstrip("/")
169
+
170
+
171
+ def _resolve_secret(ctx_obj: dict, secret: Optional[str]) -> str:
172
+ s = secret or ctx_obj.get("secret") or os.getenv("SITR_SECRET", "")
173
+ if not s:
174
+ console.print("[red]Error:[/] No API secret provided. "
175
+ "Use --secret or set the SITR_SECRET environment variable.")
176
+ sys.exit(2)
177
+ return s
178
+
179
+
180
+ # ─────────────────────────────────────────────
181
+ # Root group
182
+ # ─────────────────────────────────────────────
183
+
184
+ @click.group(invoke_without_command=False)
185
+ @click.option("--secret", "-s", envvar="SITR_SECRET", help="Your SitrTech API secret key")
186
+ @click.option("--base", "-b", envvar="SITR_BASE", default=None, help="API base URL")
187
+ @click.pass_context
188
+ def main(ctx: click.Context, secret: Optional[str], base: Optional[str]):
189
+ """
190
+ \b
191
+ SitrTech code encryption CLI
192
+ Docs: https://sitrtech.com/docs
193
+ """
194
+ ctx.ensure_object(dict)
195
+ ctx.obj["secret"] = secret
196
+ ctx.obj["base"] = base
197
+
198
+
199
+ # ─────────────────────────────────────────────
200
+ # encrypt command
201
+ # ─────────────────────────────────────────────
202
+
203
+ @main.command()
204
+ @click.argument("file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
205
+ @click.option("--framework", "-f", default="odoo",
206
+ type=click.Choice(["odoo", "django", "flask", "fastapi", "tornado"],
207
+ case_sensitive=False),
208
+ show_default=True,
209
+ help="Python framework of the project")
210
+ @click.option("--version", "-v", default="", help="Framework version (e.g. 16.0 for Odoo)")
211
+ @click.option("--expiry", "-e", default=None,
212
+ metavar="DATE",
213
+ help="Expiry date/time (e.g. 2026-12-31T23:59:00Z)")
214
+ @click.option("--network", "-n", multiple=True,
215
+ metavar="CIDR",
216
+ help="Allowed IP/CIDR (may repeat: --network 10.0.0.1 --network 10.0.1.0/24)")
217
+ @click.option("--output", "-o", default=None,
218
+ type=click.Path(path_type=Path),
219
+ help="Output path for the encrypted ZIP (default: <input>_encrypted.zip)")
220
+ @click.option("--incremental", "-i", default=None,
221
+ type=click.Path(path_type=Path),
222
+ metavar="PREV_ZIP",
223
+ help="Previous encrypted ZIP to merge changed files into (auto-detected when a "
224
+ "manifest exists next to the output file)")
225
+ @click.option("--full", is_flag=True, default=False,
226
+ help="Force a full re-encryption even if a manifest exists")
227
+ @click.option("--secret", "-s", envvar="SITR_SECRET", default=None,
228
+ help="API secret key (overrides group-level --secret)")
229
+ @click.pass_context
230
+ def encrypt(
231
+ ctx: click.Context,
232
+ file: Path,
233
+ framework: str,
234
+ version: str,
235
+ expiry: Optional[str],
236
+ network: tuple[str, ...],
237
+ output: Optional[Path],
238
+ incremental: Optional[Path],
239
+ full: bool,
240
+ secret: Optional[str],
241
+ ):
242
+ """
243
+ Encrypt a Python project ZIP file.
244
+
245
+ \b
246
+ On the first run a full encryption is performed and a manifest file
247
+ (.sitr_manifest.json) is saved alongside the output. On subsequent runs
248
+ the CLI automatically detects the manifest, computes which .py files
249
+ changed, sends only those files to the server, and merges the result back
250
+ into the previous encrypted ZIP — saving time and tokens.
251
+
252
+ \b
253
+ Examples:
254
+ sitr encrypt my_module.zip --framework odoo
255
+ sitr encrypt my_app.zip --framework django --expiry 2026-12-31
256
+ sitr encrypt api.zip --framework fastapi --network 192.168.1.0/24
257
+ sitr encrypt my_module.zip --framework odoo --full # force full re-encrypt
258
+ """
259
+ resolved_secret = _resolve_secret(ctx.obj, secret)
260
+ base = _resolve_base(ctx.obj)
261
+ fw = framework.lower()
262
+
263
+ if not file.name.endswith(".zip"):
264
+ console.print(f"[red]Error:[/] File must be a .zip archive (got {file.name})")
265
+ sys.exit(1)
266
+
267
+ hint = FRAMEWORK_HINTS.get(fw, "")
268
+ console.print(f"\n[bold]SitrTech Encrypt[/] · framework: [cyan]{fw}[/]")
269
+ if hint:
270
+ console.print(f"[dim]Hint: {hint}[/]")
271
+
272
+ output_path: Path = output or file.with_name(file.stem + "_encrypted.zip")
273
+
274
+ # ─── Detect incremental mode ───────────────────────
275
+ manifest = _load_manifest(output_path)
276
+ prev_zip: Optional[Path] = None
277
+
278
+ if not full:
279
+ if incremental is not None:
280
+ prev_zip = incremental
281
+ if not prev_zip.exists():
282
+ console.print(f"[red]Error:[/] --incremental path not found: {prev_zip}")
283
+ sys.exit(1)
284
+ elif manifest is not None and output_path.exists():
285
+ # Auto-detect: previous output + manifest both exist
286
+ prev_zip = output_path
287
+ console.print("[dim]Manifest detected — running incremental diff…[/]")
288
+
289
+ # ─── Preflight: check balance ──────────────────────
290
+ try:
291
+ bal_r = requests.get(
292
+ f"{base}/api/balance",
293
+ headers={"Authorization": f"Bearer {resolved_secret}"},
294
+ timeout=15,
295
+ )
296
+ if bal_r.status_code == 401:
297
+ console.print("[red]Error:[/] Invalid API secret.")
298
+ sys.exit(2)
299
+ if bal_r.ok:
300
+ bal = bal_r.json()
301
+ tokens = bal.get("tokens", 0)
302
+ console.print(f"Balance: [green]{tokens:,}[/] tokens")
303
+ if tokens <= 0:
304
+ console.print("[red]Error:[/] Insufficient token balance.")
305
+ sys.exit(3)
306
+ except requests.exceptions.ConnectionError:
307
+ console.print(f"[red]Error:[/] Cannot connect to {base}. Check your connection.")
308
+ sys.exit(4)
309
+
310
+ # ─── Build the payload ZIP ─────────────────────────
311
+ current_checksums = _get_source_checksums(file)
312
+
313
+ if prev_zip is not None:
314
+ # Incremental: find changed / new files
315
+ prev_checksums: dict[str, str] = (manifest or {}).get("files", {})
316
+ changed = {
317
+ name
318
+ for name, digest in current_checksums.items()
319
+ if prev_checksums.get(name) != digest
320
+ }
321
+ if not changed:
322
+ console.print("[green]✓ No changes detected.[/] Encrypted file is already up to date.")
323
+ return
324
+
325
+ console.print(
326
+ f"\n[bold cyan]Incremental mode[/] — "
327
+ f"[yellow]{len(changed)}[/] file(s) changed / added:"
328
+ )
329
+ for name in sorted(changed):
330
+ prev = prev_checksums.get(name)
331
+ tag = "[green]+new[/]" if prev is None else "[yellow]~mod[/]"
332
+ console.print(f" {tag} {name}")
333
+
334
+ payload_bytes = _build_partial_zip(file, changed)
335
+ upload_name = file.stem + "_partial.zip"
336
+ upload_bytes = payload_bytes
337
+ incremental_mode = True
338
+ else:
339
+ # Full encryption
340
+ with open(file, "rb") as fh:
341
+ upload_bytes = fh.read()
342
+ upload_name = file.name
343
+ incremental_mode = False
344
+
345
+ # ─── Upload & encrypt ──────────────────────────────
346
+ with Progress(
347
+ SpinnerColumn(),
348
+ TextColumn("[progress.description]{task.description}"),
349
+ BarColumn(bar_width=32),
350
+ TimeElapsedColumn(),
351
+ console=console,
352
+ transient=True,
353
+ ) as progress:
354
+ task = progress.add_task(
355
+ "Encrypting changes…" if incremental_mode else "Encrypting…",
356
+ total=None,
357
+ )
358
+
359
+ form: dict = {}
360
+ if version:
361
+ form["version"] = version
362
+ if expiry:
363
+ form["expires_at"] = expiry
364
+ if network:
365
+ form["network_scopes"] = ",".join(network)
366
+ form["all_platforms"] = "true"
367
+ form["all_pythons"] = "true"
368
+ form["framework"] = fw
369
+
370
+ try:
371
+ resp = requests.post(
372
+ f"{base}/api/encrypt",
373
+ headers={
374
+ "Authorization": f"Bearer {resolved_secret}",
375
+ "x-file-name": upload_name,
376
+ "x-framework": fw,
377
+ "x-version": version or "",
378
+ },
379
+ data=form,
380
+ files={"file": (upload_name, io.BytesIO(upload_bytes), "application/zip")},
381
+ stream=True,
382
+ timeout=300,
383
+ )
384
+ except requests.exceptions.ReadTimeout:
385
+ console.print("[red]Error:[/] Request timed out. The file may be too large.")
386
+ sys.exit(5)
387
+
388
+ if resp.status_code == 400:
389
+ detail = resp.json().get("detail", resp.text)
390
+ console.print(f"[red]Error 400:[/] {detail}")
391
+ sys.exit(1)
392
+ elif resp.status_code == 402:
393
+ console.print("[red]Error:[/] Payment required — insufficient tokens.")
394
+ sys.exit(3)
395
+ elif not resp.ok:
396
+ console.print(f"[red]Error {resp.status_code}:[/] {resp.text[:300]}")
397
+ sys.exit(1)
398
+
399
+ progress.update(task, description="Downloading result…")
400
+ result_bytes = b"".join(resp.iter_content(chunk_size=1 << 16))
401
+
402
+ # ─── Write output ──────────────────────────────────
403
+ if incremental_mode and prev_zip is not None:
404
+ _merge_encrypted_zips(prev_zip, result_bytes, output_path)
405
+ console.print(
406
+ f"\n[green]✓ Incremental update complete![/] "
407
+ f"Merged [yellow]{len(changed)}[/] file(s) into [bold]{output_path}[/]"
408
+ )
409
+ else:
410
+ output_path.write_bytes(result_bytes)
411
+ size_kb = output_path.stat().st_size / 1024
412
+ console.print(f"\n[green]✓ Success![/] Saved to [bold]{output_path}[/] ({size_kb:.1f} KB)")
413
+
414
+ # ─── Save / update manifest ────────────────────────
415
+ mp = _save_manifest(output_path, fw, current_checksums)
416
+ console.print(f"[dim]Manifest saved: {mp.name}[/]")
417
+
418
+ # Loader info from header
419
+ info_hdr = resp.headers.get("X-Loader-Info", "")
420
+ if info_hdr:
421
+ try:
422
+ info = json.loads(info_hdr)
423
+ console.print(f"[dim]Prebuilts: {', '.join(info.get('prebuilts_used', [])[:3])}…[/]")
424
+ except Exception:
425
+ pass
426
+
427
+
428
+ # ─────────────────────────────────────────────
429
+ # balance command
430
+ # ─────────────────────────────────────────────
431
+
432
+ @main.command()
433
+ @click.option("--secret", "-s", envvar="SITR_SECRET", default=None,
434
+ help="API secret key (overrides group-level --secret)")
435
+ @click.pass_context
436
+ def balance(ctx: click.Context, secret: Optional[str]):
437
+ """Show your current token balance and plan."""
438
+ resolved_secret = _resolve_secret(ctx.obj, secret)
439
+ base = _resolve_base(ctx.obj)
440
+
441
+ try:
442
+ r = requests.get(
443
+ f"{base}/api/balance",
444
+ headers={"Authorization": f"Bearer {resolved_secret}"},
445
+ timeout=15,
446
+ )
447
+ except requests.exceptions.ConnectionError:
448
+ console.print(f"[red]Error:[/] Cannot connect to {base}.")
449
+ sys.exit(4)
450
+
451
+ if r.status_code == 401:
452
+ console.print("[red]Error:[/] Invalid API secret.")
453
+ sys.exit(2)
454
+
455
+ if not r.ok:
456
+ console.print(f"[red]Error {r.status_code}:[/] {r.text[:200]}")
457
+ sys.exit(1)
458
+
459
+ data = r.json()
460
+ table = Table(title="SitrTech Account", show_header=False)
461
+ table.add_column("Key", style="bold cyan")
462
+ table.add_column("Value")
463
+ table.add_row("Tokens", f"{data.get('tokens', 0):,}")
464
+ table.add_row("Plan", str(data.get("plan_tier", "—")).capitalize())
465
+ table.add_row("Rate", f"{data.get('rate', 0.6)} tokens / line")
466
+ console.print(table)
467
+
468
+
469
+ # ─────────────────────────────────────────────
470
+ # info command (show framework hints)
471
+ # ─────────────────────────────────────────────
472
+
473
+ @main.command()
474
+ def info():
475
+ """Show supported frameworks and their required project structure."""
476
+ table = Table(title="Supported Frameworks", highlight=True)
477
+ table.add_column("Framework", style="bold cyan")
478
+ table.add_column("Status", style="green")
479
+ table.add_column("Required file at project root")
480
+
481
+ for fw, hint in FRAMEWORK_HINTS.items():
482
+ table.add_row(fw.capitalize(), "✓ Available", hint)
483
+
484
+ console.print(table)
485
+ console.print(
486
+ "\n[dim]Zip your project folder and run:[/]\n"
487
+ " [bold]sitr encrypt your_project.zip --framework <name>[/]\n"
488
+ )
489
+
490
+
491
+ if __name__ == "__main__":
492
+ main()
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: sitrtech
3
+ Version: 1.0.0
4
+ Summary: SitrTech Python code encryption CLI — protect Odoo, Django, Flask, FastAPI and Tornado projects
5
+ Author-email: SitrTech <support@sitrtech.com>
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://sitrtech.com
8
+ Project-URL: Docs, https://sitrtech.com/docs
9
+ Project-URL: Repository, https://github.com/sohaib929/sitr_loader
10
+ Project-URL: Bug Tracker, https://github.com/sohaib929/sitr_loader/issues
11
+ Keywords: odoo,django,flask,fastapi,tornado,encryption,obfuscation,code-protection
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Security :: Cryptography
24
+ Classifier: Topic :: Software Development :: Build Tools
25
+ Requires-Python: >=3.8
26
+ Description-Content-Type: text/markdown
27
+ Requires-Dist: requests>=2.31
28
+ Requires-Dist: rich>=13
29
+ Requires-Dist: click>=8
30
+
31
+ # SitrTech — Python Code Encryption CLI
32
+
33
+ Protect **Odoo, Django, Flask, FastAPI and Tornado** projects with AES-256 encryption, licensing controls, and anti-reverse-engineering in one command.
34
+
35
+ ## Quick start
36
+
37
+ ```bash
38
+ pip install sitrtech
39
+ ```
40
+
41
+ Get your API secret key from [sitrtech.com/api-keys](https://sitrtech.com/api-keys), then:
42
+
43
+ ```bash
44
+ # Encrypt an Odoo addon
45
+ sitr encrypt my_module.zip --framework odoo --secret sk-...
46
+
47
+ # Encrypt a Django project
48
+ sitr encrypt my_project.zip --framework django --secret sk-...
49
+
50
+ # Check your token balance
51
+ sitr balance --secret sk-...
52
+ ```
53
+
54
+ ## Installation
55
+
56
+ ```bash
57
+ pip install sitrtech # latest stable
58
+ pip install sitrtech==1.0.0 # pin a specific version
59
+ ```
60
+
61
+ Python 3.8 → 3.14 · Windows, macOS, Linux
62
+
63
+ ## Usage
64
+
65
+ ### `sitr encrypt`
66
+
67
+ ```
68
+ sitr encrypt <FILE.zip> [OPTIONS]
69
+
70
+ Arguments:
71
+ FILE.zip Zipped project folder
72
+
73
+ Options:
74
+ -f, --framework TEXT odoo | django | flask | fastapi | tornado [default: odoo]
75
+ -v, --version TEXT Framework version (e.g. 16.0 for Odoo)
76
+ -e, --expiry DATE Expiry: 2026-12-31 or 2026-12-31T23:59:00Z
77
+ -n, --network CIDR Allowed IP/CIDR (repeatable)
78
+ -o, --output PATH Output file (default: <input>_encrypted.zip)
79
+ -s, --secret TEXT API secret key [env: SITR_SECRET]
80
+ -b, --base URL API base URL [env: SITR_BASE]
81
+ ```
82
+
83
+ ### Examples
84
+
85
+ ```bash
86
+ # Odoo (default)
87
+ sitr encrypt my_addon.zip --framework odoo
88
+
89
+ # Django with expiry and IP restriction
90
+ sitr encrypt backend.zip --framework django \
91
+ --expiry 2026-12-31 \
92
+ --network 10.0.0.0/24
93
+
94
+ # FastAPI project
95
+ sitr encrypt api.zip --framework fastapi
96
+
97
+ # Flask with multiple IP ranges
98
+ sitr encrypt webapp.zip --framework flask \
99
+ --network 192.168.1.10 \
100
+ --network 10.0.0.0/8
101
+ ```
102
+
103
+ ### `sitr balance`
104
+
105
+ ```bash
106
+ sitr balance --secret sk-...
107
+ # ┌─────────────────────────────┐
108
+ # │ SitrTech Account │
109
+ # │ Tokens │ 5,000 │
110
+ # │ Plan │ Pro │
111
+ # │ Rate │ 0.3 tokens/line │
112
+ # └─────────────────────────────┘
113
+ ```
114
+
115
+ ### `sitr info`
116
+
117
+ ```bash
118
+ sitr info
119
+ # Shows all supported frameworks and required project structure
120
+ ```
121
+
122
+ ## Environment variables
123
+
124
+ | Variable | Description |
125
+ |-----------------|--------------------------------------|
126
+ | `SITR_SECRET` | API secret key (avoids typing it) |
127
+ | `SITR_BASE` | Override API URL (default: sitrtech.com) |
128
+
129
+ ```bash
130
+ export SITR_SECRET=sk-your-key-here
131
+ sitr encrypt my_project.zip --framework django
132
+ ```
133
+
134
+ ## Project structure requirements
135
+
136
+ | Framework | Required entry-point file |
137
+ |-----------|--------------------------|
138
+ | Odoo | `__manifest__.py` |
139
+ | Django | `manage.py` |
140
+ | Flask | `app.py` or `wsgi.py` |
141
+ | FastAPI | `main.py` or `asgi.py` |
142
+ | Tornado | `main.py` or `server.py` |
143
+
144
+ **Always zip the root folder** — not its contents:
145
+
146
+ ```
147
+ # Correct ✓
148
+ zip -r my_addon.zip my_addon/
149
+
150
+ # Wrong ✗
151
+ cd my_addon && zip -r ../my_addon.zip .
152
+ ```
153
+
154
+ ## CI/CD integration
155
+
156
+ ```yaml
157
+ # GitHub Actions
158
+ - name: Encrypt Odoo addon
159
+ env:
160
+ SITR_SECRET: ${{ secrets.SITR_SECRET }}
161
+ run: |
162
+ pip install sitrtech
163
+ sitr encrypt dist/my_addon.zip --framework odoo --output dist/my_addon_encrypted.zip
164
+ ```
165
+
166
+ ## Links
167
+
168
+ - **Documentation**: <https://sitrtech.com/docs>
169
+ - **API Keys**: <https://sitrtech.com/api-keys>
170
+ - **Support**: support@sitrtech.com
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/sitrtech/__init__.py
4
+ src/sitrtech/cli.py
5
+ src/sitrtech.egg-info/PKG-INFO
6
+ src/sitrtech.egg-info/SOURCES.txt
7
+ src/sitrtech.egg-info/dependency_links.txt
8
+ src/sitrtech.egg-info/entry_points.txt
9
+ src/sitrtech.egg-info/requires.txt
10
+ src/sitrtech.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ sitr = sitrtech.cli:main
3
+ sitrtech = sitrtech.cli:main
@@ -0,0 +1,3 @@
1
+ requests>=2.31
2
+ rich>=13
3
+ click>=8
@@ -0,0 +1 @@
1
+ sitrtech