kekkai-cli 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.
- kekkai/__init__.py +7 -0
- kekkai/cli.py +1038 -0
- kekkai/config.py +403 -0
- kekkai/dojo.py +419 -0
- kekkai/dojo_import.py +213 -0
- kekkai/github/__init__.py +16 -0
- kekkai/github/commenter.py +198 -0
- kekkai/github/models.py +56 -0
- kekkai/github/sanitizer.py +112 -0
- kekkai/installer/__init__.py +39 -0
- kekkai/installer/errors.py +23 -0
- kekkai/installer/extract.py +161 -0
- kekkai/installer/manager.py +252 -0
- kekkai/installer/manifest.py +189 -0
- kekkai/installer/verify.py +86 -0
- kekkai/manifest.py +77 -0
- kekkai/output.py +218 -0
- kekkai/paths.py +46 -0
- kekkai/policy.py +326 -0
- kekkai/runner.py +70 -0
- kekkai/scanners/__init__.py +67 -0
- kekkai/scanners/backends/__init__.py +14 -0
- kekkai/scanners/backends/base.py +73 -0
- kekkai/scanners/backends/docker.py +178 -0
- kekkai/scanners/backends/native.py +240 -0
- kekkai/scanners/base.py +110 -0
- kekkai/scanners/container.py +144 -0
- kekkai/scanners/falco.py +237 -0
- kekkai/scanners/gitleaks.py +237 -0
- kekkai/scanners/semgrep.py +227 -0
- kekkai/scanners/trivy.py +246 -0
- kekkai/scanners/url_policy.py +163 -0
- kekkai/scanners/zap.py +340 -0
- kekkai/threatflow/__init__.py +94 -0
- kekkai/threatflow/artifacts.py +476 -0
- kekkai/threatflow/chunking.py +361 -0
- kekkai/threatflow/core.py +438 -0
- kekkai/threatflow/mermaid.py +374 -0
- kekkai/threatflow/model_adapter.py +491 -0
- kekkai/threatflow/prompts.py +277 -0
- kekkai/threatflow/redaction.py +228 -0
- kekkai/threatflow/sanitizer.py +643 -0
- kekkai/triage/__init__.py +33 -0
- kekkai/triage/app.py +168 -0
- kekkai/triage/audit.py +203 -0
- kekkai/triage/ignore.py +269 -0
- kekkai/triage/models.py +185 -0
- kekkai/triage/screens.py +341 -0
- kekkai/triage/widgets.py +169 -0
- kekkai_cli-1.0.0.dist-info/METADATA +135 -0
- kekkai_cli-1.0.0.dist-info/RECORD +90 -0
- kekkai_cli-1.0.0.dist-info/WHEEL +5 -0
- kekkai_cli-1.0.0.dist-info/entry_points.txt +3 -0
- kekkai_cli-1.0.0.dist-info/top_level.txt +3 -0
- kekkai_core/__init__.py +3 -0
- kekkai_core/ci/__init__.py +11 -0
- kekkai_core/ci/benchmarks.py +354 -0
- kekkai_core/ci/metadata.py +104 -0
- kekkai_core/ci/validators.py +92 -0
- kekkai_core/docker/__init__.py +17 -0
- kekkai_core/docker/metadata.py +153 -0
- kekkai_core/docker/sbom.py +173 -0
- kekkai_core/docker/security.py +158 -0
- kekkai_core/docker/signing.py +135 -0
- kekkai_core/redaction.py +84 -0
- kekkai_core/slsa/__init__.py +13 -0
- kekkai_core/slsa/verify.py +121 -0
- kekkai_core/windows/__init__.py +29 -0
- kekkai_core/windows/chocolatey.py +335 -0
- kekkai_core/windows/installer.py +256 -0
- kekkai_core/windows/scoop.py +165 -0
- kekkai_core/windows/validators.py +220 -0
- portal/__init__.py +19 -0
- portal/api.py +155 -0
- portal/auth.py +103 -0
- portal/enterprise/__init__.py +32 -0
- portal/enterprise/audit.py +435 -0
- portal/enterprise/licensing.py +342 -0
- portal/enterprise/rbac.py +276 -0
- portal/enterprise/saml.py +595 -0
- portal/ops/__init__.py +53 -0
- portal/ops/backup.py +553 -0
- portal/ops/log_shipper.py +469 -0
- portal/ops/monitoring.py +517 -0
- portal/ops/restore.py +469 -0
- portal/ops/secrets.py +408 -0
- portal/ops/upgrade.py +591 -0
- portal/tenants.py +340 -0
- portal/uploads.py +259 -0
- portal/web.py +384 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"""Chocolatey NuGet package generation and validation for Windows distribution."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import xml.etree.ElementTree as ET # nosec B405 - generates trusted XML, not parsing untrusted
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def generate_nuspec(
|
|
9
|
+
version: str,
|
|
10
|
+
sha256: str,
|
|
11
|
+
whl_url: str,
|
|
12
|
+
python_version: str = "3.12",
|
|
13
|
+
) -> dict[str, Any]:
|
|
14
|
+
"""
|
|
15
|
+
Generate Chocolatey NuGet package specification (nuspec).
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
version: Package version (e.g., "0.0.1")
|
|
19
|
+
sha256: SHA256 checksum of the wheel file
|
|
20
|
+
whl_url: URL to wheel file (typically GitHub release)
|
|
21
|
+
python_version: Minimum Python version required
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Nuspec as dictionary (will be converted to XML)
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
ValueError: If version format is invalid or URLs are not HTTPS
|
|
28
|
+
"""
|
|
29
|
+
# Validate version format (basic semver)
|
|
30
|
+
if not re.match(r"^\d+\.\d+\.\d+(-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?$", version):
|
|
31
|
+
raise ValueError(f"Invalid version format: {version}")
|
|
32
|
+
|
|
33
|
+
# Validate HTTPS URLs only
|
|
34
|
+
if not whl_url.startswith("https://"):
|
|
35
|
+
raise ValueError(f"URL must use HTTPS: {whl_url}")
|
|
36
|
+
|
|
37
|
+
# Validate SHA256 format (64 hex characters)
|
|
38
|
+
if not re.match(r"^[a-fA-F0-9]{64}$", sha256):
|
|
39
|
+
raise ValueError(f"Invalid SHA256 format: {sha256}")
|
|
40
|
+
|
|
41
|
+
# Build nuspec structure
|
|
42
|
+
nuspec: dict[str, Any] = {
|
|
43
|
+
"id": "kekkai",
|
|
44
|
+
"version": version,
|
|
45
|
+
"title": "Kekkai",
|
|
46
|
+
"authors": "Kademos Labs",
|
|
47
|
+
"owners": "Kademos Labs",
|
|
48
|
+
"licenseUrl": "https://github.com/kademoslabs/kekkai/blob/main/LICENSE",
|
|
49
|
+
"projectUrl": "https://github.com/kademoslabs/kekkai",
|
|
50
|
+
"iconUrl": "https://raw.githubusercontent.com/kademoslabs/kekkai/main/docs/assets/icon.png",
|
|
51
|
+
"requireLicenseAcceptance": False,
|
|
52
|
+
"description": (
|
|
53
|
+
"Kekkai - Local-first AppSec orchestration and compliance checker. "
|
|
54
|
+
"Integrates security scanning tools (Semgrep, Trivy, Gitleaks, OWASP ZAP) "
|
|
55
|
+
"with DefectDojo for centralized vulnerability management."
|
|
56
|
+
),
|
|
57
|
+
"summary": "Local-first AppSec orchestration and compliance checker",
|
|
58
|
+
"tags": "security appsec cli devsecops vulnerability-scanner compliance",
|
|
59
|
+
"copyright": "2024-2026 Kademos Labs",
|
|
60
|
+
"dependencies": [
|
|
61
|
+
{
|
|
62
|
+
"id": "python",
|
|
63
|
+
"version": f"[{python_version},)",
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"files": [
|
|
67
|
+
{"src": "tools\\**", "target": "tools"},
|
|
68
|
+
],
|
|
69
|
+
# Metadata for package generation
|
|
70
|
+
"_whl_url": whl_url,
|
|
71
|
+
"_sha256": sha256,
|
|
72
|
+
"_python_version": python_version,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return nuspec
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def validate_nuspec(nuspec: dict[str, Any]) -> bool:
|
|
79
|
+
"""
|
|
80
|
+
Validate Chocolatey nuspec structure and required fields.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
nuspec: Nuspec dictionary
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
True if nuspec is valid
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ValueError: If nuspec is invalid with detailed error message
|
|
90
|
+
"""
|
|
91
|
+
# Required fields
|
|
92
|
+
required_fields = [
|
|
93
|
+
"id",
|
|
94
|
+
"version",
|
|
95
|
+
"authors",
|
|
96
|
+
"description",
|
|
97
|
+
"licenseUrl",
|
|
98
|
+
"projectUrl",
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
for field in required_fields:
|
|
102
|
+
if field not in nuspec:
|
|
103
|
+
raise ValueError(f"Missing required field: {field}")
|
|
104
|
+
|
|
105
|
+
# Validate version format
|
|
106
|
+
version = nuspec["version"]
|
|
107
|
+
if not re.match(r"^\d+\.\d+\.\d+(-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?$", version):
|
|
108
|
+
raise ValueError(f"Invalid version format: {version}")
|
|
109
|
+
|
|
110
|
+
# Validate URLs
|
|
111
|
+
for url_field in ["licenseUrl", "projectUrl"]:
|
|
112
|
+
if url_field in nuspec:
|
|
113
|
+
url = nuspec[url_field]
|
|
114
|
+
if not url.startswith("https://") and not url.startswith("http://"):
|
|
115
|
+
raise ValueError(f"Invalid {url_field}: {url}")
|
|
116
|
+
|
|
117
|
+
# Validate dependencies structure
|
|
118
|
+
if "dependencies" in nuspec:
|
|
119
|
+
deps = nuspec["dependencies"]
|
|
120
|
+
if not isinstance(deps, list):
|
|
121
|
+
raise ValueError("dependencies must be a list")
|
|
122
|
+
|
|
123
|
+
for dep in deps:
|
|
124
|
+
if not isinstance(dep, dict):
|
|
125
|
+
raise ValueError("Each dependency must be a dict")
|
|
126
|
+
if "id" not in dep:
|
|
127
|
+
raise ValueError("Dependency missing 'id' field")
|
|
128
|
+
|
|
129
|
+
# Validate internal metadata if present
|
|
130
|
+
if "_sha256" in nuspec:
|
|
131
|
+
sha256 = nuspec["_sha256"]
|
|
132
|
+
if not re.match(r"^[a-fA-F0-9]{64}$", sha256):
|
|
133
|
+
raise ValueError(f"Invalid SHA256 format: {sha256}")
|
|
134
|
+
|
|
135
|
+
if "_whl_url" in nuspec:
|
|
136
|
+
whl_url = nuspec["_whl_url"]
|
|
137
|
+
if not whl_url.startswith("https://"):
|
|
138
|
+
raise ValueError(f"Wheel URL must use HTTPS: {whl_url}")
|
|
139
|
+
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def format_nuspec_xml(nuspec: dict[str, Any]) -> str:
|
|
144
|
+
"""
|
|
145
|
+
Format nuspec dictionary as XML string for Chocolatey package.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
nuspec: Nuspec dictionary
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
XML string with proper formatting and namespace
|
|
152
|
+
"""
|
|
153
|
+
# Define namespace
|
|
154
|
+
ns = "http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd"
|
|
155
|
+
ET.register_namespace("", ns)
|
|
156
|
+
|
|
157
|
+
# Create root element
|
|
158
|
+
package = ET.Element("package", xmlns=ns)
|
|
159
|
+
metadata = ET.SubElement(package, "metadata")
|
|
160
|
+
|
|
161
|
+
# Add simple fields
|
|
162
|
+
simple_fields = [
|
|
163
|
+
"id",
|
|
164
|
+
"version",
|
|
165
|
+
"title",
|
|
166
|
+
"authors",
|
|
167
|
+
"owners",
|
|
168
|
+
"licenseUrl",
|
|
169
|
+
"projectUrl",
|
|
170
|
+
"iconUrl",
|
|
171
|
+
"description",
|
|
172
|
+
"summary",
|
|
173
|
+
"tags",
|
|
174
|
+
"copyright",
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
for field in simple_fields:
|
|
178
|
+
if field in nuspec:
|
|
179
|
+
elem = ET.SubElement(metadata, field)
|
|
180
|
+
elem.text = str(nuspec[field])
|
|
181
|
+
|
|
182
|
+
# Add requireLicenseAcceptance
|
|
183
|
+
if "requireLicenseAcceptance" in nuspec:
|
|
184
|
+
elem = ET.SubElement(metadata, "requireLicenseAcceptance")
|
|
185
|
+
elem.text = "true" if nuspec["requireLicenseAcceptance"] else "false"
|
|
186
|
+
|
|
187
|
+
# Add dependencies
|
|
188
|
+
if "dependencies" in nuspec and nuspec["dependencies"]:
|
|
189
|
+
deps_elem = ET.SubElement(metadata, "dependencies")
|
|
190
|
+
for dep in nuspec["dependencies"]:
|
|
191
|
+
dep_attrs = {"id": dep["id"]}
|
|
192
|
+
if "version" in dep:
|
|
193
|
+
dep_attrs["version"] = dep["version"]
|
|
194
|
+
ET.SubElement(deps_elem, "dependency", dep_attrs)
|
|
195
|
+
|
|
196
|
+
# Add files section
|
|
197
|
+
if "files" in nuspec and nuspec["files"]:
|
|
198
|
+
files_elem = ET.SubElement(package, "files")
|
|
199
|
+
for file_entry in nuspec["files"]:
|
|
200
|
+
file_attrs = {}
|
|
201
|
+
if "src" in file_entry:
|
|
202
|
+
file_attrs["src"] = file_entry["src"]
|
|
203
|
+
if "target" in file_entry:
|
|
204
|
+
file_attrs["target"] = file_entry["target"]
|
|
205
|
+
ET.SubElement(files_elem, "file", file_attrs)
|
|
206
|
+
|
|
207
|
+
# Convert to string with XML declaration
|
|
208
|
+
xml_str = '<?xml version="1.0" encoding="utf-8"?>\n'
|
|
209
|
+
xml_str += _prettify_xml(package)
|
|
210
|
+
|
|
211
|
+
return xml_str
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _prettify_xml(elem: ET.Element, level: int = 0) -> str:
|
|
215
|
+
"""
|
|
216
|
+
Pretty-print XML element with 2-space indentation.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
elem: XML Element
|
|
220
|
+
level: Current indentation level
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Pretty-printed XML string
|
|
224
|
+
"""
|
|
225
|
+
indent = " " * level
|
|
226
|
+
result = f"{indent}<{elem.tag}"
|
|
227
|
+
|
|
228
|
+
# Add attributes
|
|
229
|
+
for key, value in elem.attrib.items():
|
|
230
|
+
result += f' {key}="{value}"'
|
|
231
|
+
|
|
232
|
+
# Handle self-closing tags
|
|
233
|
+
if not elem.text and len(elem) == 0:
|
|
234
|
+
result += " />\n"
|
|
235
|
+
return result
|
|
236
|
+
|
|
237
|
+
result += ">"
|
|
238
|
+
|
|
239
|
+
# Add text content
|
|
240
|
+
if elem.text and elem.text.strip():
|
|
241
|
+
result += elem.text
|
|
242
|
+
|
|
243
|
+
# Add children
|
|
244
|
+
if len(elem) > 0:
|
|
245
|
+
result += "\n"
|
|
246
|
+
for child in elem:
|
|
247
|
+
result += _prettify_xml(child, level + 1)
|
|
248
|
+
result += indent
|
|
249
|
+
|
|
250
|
+
result += f"</{elem.tag}>\n"
|
|
251
|
+
|
|
252
|
+
return result
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def generate_chocolatey_package_structure(
|
|
256
|
+
version: str,
|
|
257
|
+
sha256: str,
|
|
258
|
+
python_version: str = "3.12",
|
|
259
|
+
) -> dict[str, str]:
|
|
260
|
+
"""
|
|
261
|
+
Generate complete Chocolatey package structure.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
version: Package version
|
|
265
|
+
sha256: SHA256 checksum of wheel
|
|
266
|
+
python_version: Minimum Python version
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Dictionary mapping file paths to content:
|
|
270
|
+
- "kekkai.nuspec": XML content
|
|
271
|
+
- "tools/chocolateyinstall.ps1": Install script
|
|
272
|
+
- "tools/chocolateyuninstall.ps1": Uninstall script
|
|
273
|
+
"""
|
|
274
|
+
from kekkai_core.windows.installer import (
|
|
275
|
+
generate_chocolatey_install_script,
|
|
276
|
+
generate_chocolatey_uninstall_script,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
whl_url = f"https://github.com/kademoslabs/kekkai/releases/download/v{version}/kekkai-{version}-py3-none-any.whl"
|
|
280
|
+
|
|
281
|
+
# Generate nuspec
|
|
282
|
+
nuspec = generate_nuspec(version, sha256, whl_url, python_version)
|
|
283
|
+
nuspec_xml = format_nuspec_xml(nuspec)
|
|
284
|
+
|
|
285
|
+
# Generate install/uninstall scripts
|
|
286
|
+
install_script = generate_chocolatey_install_script(version, sha256, python_version)
|
|
287
|
+
uninstall_script = generate_chocolatey_uninstall_script()
|
|
288
|
+
|
|
289
|
+
# Return package structure
|
|
290
|
+
return {
|
|
291
|
+
"kekkai.nuspec": nuspec_xml,
|
|
292
|
+
"tools/chocolateyinstall.ps1": install_script,
|
|
293
|
+
"tools/chocolateyuninstall.ps1": uninstall_script,
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def generate_verification_file(version: str, sha256: str) -> str:
|
|
298
|
+
"""
|
|
299
|
+
Generate VERIFICATION.txt for Chocolatey moderation.
|
|
300
|
+
|
|
301
|
+
This file helps Chocolatey moderators verify package authenticity.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
version: Package version
|
|
305
|
+
sha256: SHA256 checksum
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
VERIFICATION.txt content
|
|
309
|
+
"""
|
|
310
|
+
whl_url = f"https://github.com/kademoslabs/kekkai/releases/download/v{version}/kekkai-{version}-py3-none-any.whl"
|
|
311
|
+
|
|
312
|
+
verification = f"""VERIFICATION
|
|
313
|
+
Verification is intended to assist the Chocolatey moderators and community
|
|
314
|
+
in verifying that this package's contents are trustworthy.
|
|
315
|
+
|
|
316
|
+
Package can be verified like this:
|
|
317
|
+
|
|
318
|
+
1. Download wheel from official GitHub release:
|
|
319
|
+
{whl_url}
|
|
320
|
+
|
|
321
|
+
2. Verify SHA256 checksum:
|
|
322
|
+
Expected: {sha256}
|
|
323
|
+
|
|
324
|
+
PowerShell command:
|
|
325
|
+
Get-FileHash -Path kekkai-{version}-py3-none-any.whl -Algorithm SHA256
|
|
326
|
+
|
|
327
|
+
3. Install using the downloaded wheel:
|
|
328
|
+
python -m pip install kekkai-{version}-py3-none-any.whl
|
|
329
|
+
|
|
330
|
+
The package scripts are included in this package under tools/ directory.
|
|
331
|
+
All scripts are also available in the official repository:
|
|
332
|
+
https://github.com/kademoslabs/kekkai
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
return verification
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""PowerShell installer script generation for Windows package managers."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def generate_installer_script(
|
|
5
|
+
whl_url: str,
|
|
6
|
+
python_version: str = "3.12",
|
|
7
|
+
) -> str:
|
|
8
|
+
"""
|
|
9
|
+
Generate PowerShell installer script for Kekkai.
|
|
10
|
+
|
|
11
|
+
Uses minimal logic to reduce attack surface:
|
|
12
|
+
- Validates Python version
|
|
13
|
+
- Uses pip to install wheel
|
|
14
|
+
- No remote code execution patterns (no Invoke-Expression)
|
|
15
|
+
- HTTPS-only URLs
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
whl_url: URL to wheel file (must be HTTPS)
|
|
19
|
+
python_version: Minimum Python version required
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
PowerShell script as string
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
ValueError: If URL is not HTTPS
|
|
26
|
+
"""
|
|
27
|
+
if not whl_url.startswith("https://"):
|
|
28
|
+
raise ValueError(f"URL must use HTTPS: {whl_url}")
|
|
29
|
+
|
|
30
|
+
# Use heredoc-style string with explicit escaping
|
|
31
|
+
script = f"""# Kekkai Installation Script
|
|
32
|
+
# Generated for Windows package managers (Scoop/Chocolatey)
|
|
33
|
+
|
|
34
|
+
# Validate Python availability
|
|
35
|
+
try {{
|
|
36
|
+
$pythonCmd = Get-Command python -ErrorAction Stop
|
|
37
|
+
Write-Host "Found Python: $($pythonCmd.Source)"
|
|
38
|
+
}} catch {{
|
|
39
|
+
Write-Error "Python not found in PATH. Please install Python {python_version}+ first."
|
|
40
|
+
exit 1
|
|
41
|
+
}}
|
|
42
|
+
|
|
43
|
+
# Validate Python version
|
|
44
|
+
try {{
|
|
45
|
+
$pythonVersionOutput = python --version 2>&1 | Out-String
|
|
46
|
+
if ($pythonVersionOutput -match "Python (\\d+\\.\\d+)") {{
|
|
47
|
+
$installedVersion = [version]$matches[1]
|
|
48
|
+
$requiredVersion = [version]"{python_version}"
|
|
49
|
+
|
|
50
|
+
if ($installedVersion -lt $requiredVersion) {{
|
|
51
|
+
Write-Error "Python {python_version}+ required, found $installedVersion"
|
|
52
|
+
exit 1
|
|
53
|
+
}}
|
|
54
|
+
|
|
55
|
+
Write-Host "Python version check passed: $installedVersion"
|
|
56
|
+
}} else {{
|
|
57
|
+
Write-Error "Could not parse Python version from: $pythonVersionOutput"
|
|
58
|
+
exit 1
|
|
59
|
+
}}
|
|
60
|
+
}} catch {{
|
|
61
|
+
Write-Error "Failed to check Python version: $_"
|
|
62
|
+
exit 1
|
|
63
|
+
}}
|
|
64
|
+
|
|
65
|
+
# Validate pip availability
|
|
66
|
+
try {{
|
|
67
|
+
python -m pip --version | Out-Null
|
|
68
|
+
if ($LASTEXITCODE -ne 0) {{
|
|
69
|
+
throw "pip check failed"
|
|
70
|
+
}}
|
|
71
|
+
Write-Host "pip is available"
|
|
72
|
+
}} catch {{
|
|
73
|
+
Write-Error "pip is not available. Please ensure pip is installed."
|
|
74
|
+
exit 1
|
|
75
|
+
}}
|
|
76
|
+
|
|
77
|
+
# Install Kekkai wheel with force-reinstall and no-deps
|
|
78
|
+
Write-Host "Installing Kekkai from: {whl_url}"
|
|
79
|
+
try {{
|
|
80
|
+
python -m pip install --force-reinstall --no-deps "{whl_url}"
|
|
81
|
+
|
|
82
|
+
if ($LASTEXITCODE -ne 0) {{
|
|
83
|
+
throw "pip install failed with exit code $LASTEXITCODE"
|
|
84
|
+
}}
|
|
85
|
+
|
|
86
|
+
Write-Host "✅ Kekkai installed successfully!"
|
|
87
|
+
Write-Host ""
|
|
88
|
+
Write-Host "Run 'kekkai --help' to get started."
|
|
89
|
+
}} catch {{
|
|
90
|
+
Write-Error "Installation failed: $_"
|
|
91
|
+
exit 1
|
|
92
|
+
}}
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
return script
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def generate_uninstaller_script() -> str:
|
|
99
|
+
"""
|
|
100
|
+
Generate PowerShell uninstaller script for Kekkai.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
PowerShell uninstall script as string
|
|
104
|
+
"""
|
|
105
|
+
script = """# Kekkai Uninstallation Script
|
|
106
|
+
# Generated for Windows package managers (Scoop/Chocolatey)
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
Write-Host "Uninstalling Kekkai..."
|
|
110
|
+
|
|
111
|
+
# Check if pip is available
|
|
112
|
+
python -m pip --version | Out-Null
|
|
113
|
+
if ($LASTEXITCODE -ne 0) {
|
|
114
|
+
Write-Warning "pip is not available, skipping uninstall"
|
|
115
|
+
exit 0
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Uninstall Kekkai
|
|
119
|
+
python -m pip uninstall -y kekkai
|
|
120
|
+
|
|
121
|
+
if ($LASTEXITCODE -eq 0) {
|
|
122
|
+
Write-Host "✅ Kekkai uninstalled successfully"
|
|
123
|
+
} else {
|
|
124
|
+
Write-Warning "Kekkai may not have been installed or was already removed"
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
Write-Warning "Uninstall encountered an issue: $_"
|
|
128
|
+
# Don't fail on uninstall errors
|
|
129
|
+
exit 0
|
|
130
|
+
}
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
return script
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def generate_chocolatey_install_script(
|
|
137
|
+
version: str,
|
|
138
|
+
sha256: str,
|
|
139
|
+
python_version: str = "3.12",
|
|
140
|
+
) -> str:
|
|
141
|
+
"""
|
|
142
|
+
Generate chocolateyinstall.ps1 script for Chocolatey package.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
version: Package version
|
|
146
|
+
sha256: Expected SHA256 checksum
|
|
147
|
+
python_version: Minimum Python version required
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Chocolatey install script as string
|
|
151
|
+
"""
|
|
152
|
+
whl_url = f"https://github.com/kademoslabs/kekkai/releases/download/v{version}/kekkai-{version}-py3-none-any.whl"
|
|
153
|
+
|
|
154
|
+
if not whl_url.startswith("https://"):
|
|
155
|
+
raise ValueError(f"URL must use HTTPS: {whl_url}")
|
|
156
|
+
|
|
157
|
+
script = f"""# Kekkai Chocolatey Installation Script
|
|
158
|
+
$ErrorActionPreference = 'Stop'
|
|
159
|
+
|
|
160
|
+
$packageName = 'kekkai'
|
|
161
|
+
$version = '{version}'
|
|
162
|
+
$url = '{whl_url}'
|
|
163
|
+
$checksum = '{sha256}'
|
|
164
|
+
$checksumType = 'sha256'
|
|
165
|
+
|
|
166
|
+
Write-Host "Installing $packageName version $version..."
|
|
167
|
+
|
|
168
|
+
# Validate Python availability
|
|
169
|
+
try {{
|
|
170
|
+
$pythonCmd = Get-Command python -ErrorAction Stop
|
|
171
|
+
Write-Host "Found Python: $($pythonCmd.Source)"
|
|
172
|
+
}} catch {{
|
|
173
|
+
throw "Python not found in PATH. Please install Python {python_version}+ first."
|
|
174
|
+
}}
|
|
175
|
+
|
|
176
|
+
# Validate Python version
|
|
177
|
+
$pythonVersionOutput = python --version 2>&1 | Out-String
|
|
178
|
+
if ($pythonVersionOutput -match "Python (\\d+\\.\\d+)") {{
|
|
179
|
+
$installedVersion = [version]$matches[1]
|
|
180
|
+
$requiredVersion = [version]"{python_version}"
|
|
181
|
+
|
|
182
|
+
if ($installedVersion -lt $requiredVersion) {{
|
|
183
|
+
throw "Python {python_version}+ required, found $installedVersion"
|
|
184
|
+
}}
|
|
185
|
+
|
|
186
|
+
Write-Host "Python version check passed: $installedVersion"
|
|
187
|
+
}} else {{
|
|
188
|
+
throw "Could not parse Python version"
|
|
189
|
+
}}
|
|
190
|
+
|
|
191
|
+
# Download wheel file to temp location
|
|
192
|
+
$tempDir = Join-Path $env:TEMP "kekkai-$version"
|
|
193
|
+
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
|
|
194
|
+
$whlFile = Join-Path $tempDir "kekkai-$version-py3-none-any.whl"
|
|
195
|
+
|
|
196
|
+
Write-Host "Downloading wheel from: $url"
|
|
197
|
+
try {{
|
|
198
|
+
Invoke-WebRequest -Uri $url -OutFile $whlFile -UseBasicParsing
|
|
199
|
+
}} catch {{
|
|
200
|
+
throw "Failed to download wheel: $_"
|
|
201
|
+
}}
|
|
202
|
+
|
|
203
|
+
# Verify checksum
|
|
204
|
+
Write-Host "Verifying checksum..."
|
|
205
|
+
$actualChecksum = (Get-FileHash -Path $whlFile -Algorithm SHA256).Hash
|
|
206
|
+
if ($actualChecksum -ne $checksum) {{
|
|
207
|
+
throw "Checksum mismatch! Expected: $checksum, Got: $actualChecksum"
|
|
208
|
+
}}
|
|
209
|
+
Write-Host "Checksum verified: $actualChecksum"
|
|
210
|
+
|
|
211
|
+
# Install via pip
|
|
212
|
+
Write-Host "Installing via pip..."
|
|
213
|
+
python -m pip install --force-reinstall --no-deps $whlFile
|
|
214
|
+
|
|
215
|
+
if ($LASTEXITCODE -ne 0) {{
|
|
216
|
+
throw "pip install failed"
|
|
217
|
+
}}
|
|
218
|
+
|
|
219
|
+
# Cleanup
|
|
220
|
+
Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue
|
|
221
|
+
|
|
222
|
+
Write-Host "✅ $packageName installed successfully!"
|
|
223
|
+
Write-Host "Run 'kekkai --help' to get started."
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
return script
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def generate_chocolatey_uninstall_script() -> str:
|
|
230
|
+
"""
|
|
231
|
+
Generate chocolateyuninstall.ps1 script for Chocolatey package.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Chocolatey uninstall script as string
|
|
235
|
+
"""
|
|
236
|
+
script = """# Kekkai Chocolatey Uninstallation Script
|
|
237
|
+
$ErrorActionPreference = 'Continue'
|
|
238
|
+
|
|
239
|
+
$packageName = 'kekkai'
|
|
240
|
+
|
|
241
|
+
Write-Host "Uninstalling $packageName..."
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
python -m pip uninstall -y kekkai
|
|
245
|
+
|
|
246
|
+
if ($LASTEXITCODE -eq 0) {
|
|
247
|
+
Write-Host "✅ $packageName uninstalled successfully"
|
|
248
|
+
} else {
|
|
249
|
+
Write-Warning "$packageName may not have been installed or was already removed"
|
|
250
|
+
}
|
|
251
|
+
} catch {
|
|
252
|
+
Write-Warning "Uninstall encountered an issue: $_"
|
|
253
|
+
}
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
return script
|