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.
Files changed (90) hide show
  1. kekkai/__init__.py +7 -0
  2. kekkai/cli.py +1038 -0
  3. kekkai/config.py +403 -0
  4. kekkai/dojo.py +419 -0
  5. kekkai/dojo_import.py +213 -0
  6. kekkai/github/__init__.py +16 -0
  7. kekkai/github/commenter.py +198 -0
  8. kekkai/github/models.py +56 -0
  9. kekkai/github/sanitizer.py +112 -0
  10. kekkai/installer/__init__.py +39 -0
  11. kekkai/installer/errors.py +23 -0
  12. kekkai/installer/extract.py +161 -0
  13. kekkai/installer/manager.py +252 -0
  14. kekkai/installer/manifest.py +189 -0
  15. kekkai/installer/verify.py +86 -0
  16. kekkai/manifest.py +77 -0
  17. kekkai/output.py +218 -0
  18. kekkai/paths.py +46 -0
  19. kekkai/policy.py +326 -0
  20. kekkai/runner.py +70 -0
  21. kekkai/scanners/__init__.py +67 -0
  22. kekkai/scanners/backends/__init__.py +14 -0
  23. kekkai/scanners/backends/base.py +73 -0
  24. kekkai/scanners/backends/docker.py +178 -0
  25. kekkai/scanners/backends/native.py +240 -0
  26. kekkai/scanners/base.py +110 -0
  27. kekkai/scanners/container.py +144 -0
  28. kekkai/scanners/falco.py +237 -0
  29. kekkai/scanners/gitleaks.py +237 -0
  30. kekkai/scanners/semgrep.py +227 -0
  31. kekkai/scanners/trivy.py +246 -0
  32. kekkai/scanners/url_policy.py +163 -0
  33. kekkai/scanners/zap.py +340 -0
  34. kekkai/threatflow/__init__.py +94 -0
  35. kekkai/threatflow/artifacts.py +476 -0
  36. kekkai/threatflow/chunking.py +361 -0
  37. kekkai/threatflow/core.py +438 -0
  38. kekkai/threatflow/mermaid.py +374 -0
  39. kekkai/threatflow/model_adapter.py +491 -0
  40. kekkai/threatflow/prompts.py +277 -0
  41. kekkai/threatflow/redaction.py +228 -0
  42. kekkai/threatflow/sanitizer.py +643 -0
  43. kekkai/triage/__init__.py +33 -0
  44. kekkai/triage/app.py +168 -0
  45. kekkai/triage/audit.py +203 -0
  46. kekkai/triage/ignore.py +269 -0
  47. kekkai/triage/models.py +185 -0
  48. kekkai/triage/screens.py +341 -0
  49. kekkai/triage/widgets.py +169 -0
  50. kekkai_cli-1.0.0.dist-info/METADATA +135 -0
  51. kekkai_cli-1.0.0.dist-info/RECORD +90 -0
  52. kekkai_cli-1.0.0.dist-info/WHEEL +5 -0
  53. kekkai_cli-1.0.0.dist-info/entry_points.txt +3 -0
  54. kekkai_cli-1.0.0.dist-info/top_level.txt +3 -0
  55. kekkai_core/__init__.py +3 -0
  56. kekkai_core/ci/__init__.py +11 -0
  57. kekkai_core/ci/benchmarks.py +354 -0
  58. kekkai_core/ci/metadata.py +104 -0
  59. kekkai_core/ci/validators.py +92 -0
  60. kekkai_core/docker/__init__.py +17 -0
  61. kekkai_core/docker/metadata.py +153 -0
  62. kekkai_core/docker/sbom.py +173 -0
  63. kekkai_core/docker/security.py +158 -0
  64. kekkai_core/docker/signing.py +135 -0
  65. kekkai_core/redaction.py +84 -0
  66. kekkai_core/slsa/__init__.py +13 -0
  67. kekkai_core/slsa/verify.py +121 -0
  68. kekkai_core/windows/__init__.py +29 -0
  69. kekkai_core/windows/chocolatey.py +335 -0
  70. kekkai_core/windows/installer.py +256 -0
  71. kekkai_core/windows/scoop.py +165 -0
  72. kekkai_core/windows/validators.py +220 -0
  73. portal/__init__.py +19 -0
  74. portal/api.py +155 -0
  75. portal/auth.py +103 -0
  76. portal/enterprise/__init__.py +32 -0
  77. portal/enterprise/audit.py +435 -0
  78. portal/enterprise/licensing.py +342 -0
  79. portal/enterprise/rbac.py +276 -0
  80. portal/enterprise/saml.py +595 -0
  81. portal/ops/__init__.py +53 -0
  82. portal/ops/backup.py +553 -0
  83. portal/ops/log_shipper.py +469 -0
  84. portal/ops/monitoring.py +517 -0
  85. portal/ops/restore.py +469 -0
  86. portal/ops/secrets.py +408 -0
  87. portal/ops/upgrade.py +591 -0
  88. portal/tenants.py +340 -0
  89. portal/uploads.py +259 -0
  90. 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