xenfra-sdk 0.2.2__py3-none-any.whl → 0.2.4__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.
- xenfra_sdk/__init__.py +61 -21
- xenfra_sdk/cli/main.py +226 -226
- xenfra_sdk/client.py +90 -90
- xenfra_sdk/config.py +26 -26
- xenfra_sdk/db/models.py +24 -24
- xenfra_sdk/db/session.py +30 -30
- xenfra_sdk/dependencies.py +39 -39
- xenfra_sdk/detection.py +396 -0
- xenfra_sdk/dockerizer.py +195 -194
- xenfra_sdk/engine.py +741 -619
- xenfra_sdk/exceptions.py +19 -19
- xenfra_sdk/manifest.py +212 -0
- xenfra_sdk/mcp_client.py +154 -154
- xenfra_sdk/models.py +184 -184
- xenfra_sdk/orchestrator.py +666 -0
- xenfra_sdk/patterns.json +13 -13
- xenfra_sdk/privacy.py +153 -153
- xenfra_sdk/recipes.py +26 -26
- xenfra_sdk/resources/base.py +3 -3
- xenfra_sdk/resources/deployments.py +278 -248
- xenfra_sdk/resources/files.py +101 -101
- xenfra_sdk/resources/intelligence.py +102 -95
- xenfra_sdk/security.py +41 -41
- xenfra_sdk/security_scanner.py +431 -0
- xenfra_sdk/templates/Caddyfile.j2 +14 -0
- xenfra_sdk/templates/Dockerfile.j2 +41 -38
- xenfra_sdk/templates/cloud-init.sh.j2 +90 -90
- xenfra_sdk/templates/docker-compose-multi.yml.j2 +29 -0
- xenfra_sdk/templates/docker-compose.yml.j2 +30 -30
- xenfra_sdk-0.2.4.dist-info/METADATA +116 -0
- xenfra_sdk-0.2.4.dist-info/RECORD +38 -0
- xenfra_sdk-0.2.2.dist-info/METADATA +0 -118
- xenfra_sdk-0.2.2.dist-info/RECORD +0 -32
- {xenfra_sdk-0.2.2.dist-info → xenfra_sdk-0.2.4.dist-info}/WHEEL +0 -0
xenfra_sdk/resources/files.py
CHANGED
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Files resource manager for delta uploads.
|
|
3
|
-
|
|
4
|
-
Provides methods to check file cache status and upload files to the server.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from typing import Dict, List
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class FilesManager:
|
|
11
|
-
"""Manager for file upload operations."""
|
|
12
|
-
|
|
13
|
-
def __init__(self, client):
|
|
14
|
-
"""
|
|
15
|
-
Initialize the FilesManager.
|
|
16
|
-
|
|
17
|
-
Args:
|
|
18
|
-
client: The XenfraClient instance.
|
|
19
|
-
"""
|
|
20
|
-
self._client = client
|
|
21
|
-
|
|
22
|
-
def check(self, files: List[Dict]) -> Dict:
|
|
23
|
-
"""
|
|
24
|
-
Check which files are missing from server cache.
|
|
25
|
-
|
|
26
|
-
Args:
|
|
27
|
-
files: List of file info dicts with keys: path, sha, size
|
|
28
|
-
|
|
29
|
-
Returns:
|
|
30
|
-
Dict with keys:
|
|
31
|
-
- missing: List of SHA hashes that need to be uploaded
|
|
32
|
-
- cached: Number of files already cached on server
|
|
33
|
-
"""
|
|
34
|
-
payload = {
|
|
35
|
-
"files": [
|
|
36
|
-
{"path": f["path"], "sha": f["sha"], "size": f["size"]}
|
|
37
|
-
for f in files
|
|
38
|
-
]
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
response = self._client._request("POST", "/files/check", json=payload)
|
|
42
|
-
return response.json()
|
|
43
|
-
|
|
44
|
-
def upload(self, content: bytes, sha: str, path: str) -> Dict:
|
|
45
|
-
"""
|
|
46
|
-
Upload a single file to the server.
|
|
47
|
-
|
|
48
|
-
Args:
|
|
49
|
-
content: Raw file content as bytes
|
|
50
|
-
sha: SHA256 hash of the content
|
|
51
|
-
path: Relative file path
|
|
52
|
-
|
|
53
|
-
Returns:
|
|
54
|
-
Dict with keys: sha, size, stored
|
|
55
|
-
"""
|
|
56
|
-
import httpx
|
|
57
|
-
|
|
58
|
-
headers = {
|
|
59
|
-
"Authorization": f"Bearer {self._client._token}",
|
|
60
|
-
"Content-Type": "application/octet-stream",
|
|
61
|
-
"X-Xenfra-Sha": sha,
|
|
62
|
-
"X-Xenfra-Path": path,
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
response = httpx.post(
|
|
66
|
-
f"{self._client.api_url}/files/upload",
|
|
67
|
-
content=content,
|
|
68
|
-
headers=headers,
|
|
69
|
-
timeout=120.0, # 2 minutes for large files
|
|
70
|
-
)
|
|
71
|
-
response.raise_for_status()
|
|
72
|
-
return response.json()
|
|
73
|
-
|
|
74
|
-
def upload_files(self, files: List[Dict], missing_shas: List[str], progress_callback=None) -> int:
|
|
75
|
-
"""
|
|
76
|
-
Upload multiple files that are missing from the server.
|
|
77
|
-
|
|
78
|
-
Args:
|
|
79
|
-
files: List of file info dicts with keys: path, sha, size, abs_path
|
|
80
|
-
missing_shas: List of SHA hashes that need to be uploaded
|
|
81
|
-
progress_callback: Optional callback(uploaded_count, total_count)
|
|
82
|
-
|
|
83
|
-
Returns:
|
|
84
|
-
Number of files uploaded
|
|
85
|
-
"""
|
|
86
|
-
missing_set = set(missing_shas)
|
|
87
|
-
files_to_upload = [f for f in files if f["sha"] in missing_set]
|
|
88
|
-
total = len(files_to_upload)
|
|
89
|
-
uploaded = 0
|
|
90
|
-
|
|
91
|
-
for file_info in files_to_upload:
|
|
92
|
-
with open(file_info["abs_path"], "rb") as f:
|
|
93
|
-
content = f.read()
|
|
94
|
-
|
|
95
|
-
self.upload(content, file_info["sha"], file_info["path"])
|
|
96
|
-
uploaded += 1
|
|
97
|
-
|
|
98
|
-
if progress_callback:
|
|
99
|
-
progress_callback(uploaded, total)
|
|
100
|
-
|
|
101
|
-
return uploaded
|
|
1
|
+
"""
|
|
2
|
+
Files resource manager for delta uploads.
|
|
3
|
+
|
|
4
|
+
Provides methods to check file cache status and upload files to the server.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, List
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FilesManager:
|
|
11
|
+
"""Manager for file upload operations."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, client):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the FilesManager.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
client: The XenfraClient instance.
|
|
19
|
+
"""
|
|
20
|
+
self._client = client
|
|
21
|
+
|
|
22
|
+
def check(self, files: List[Dict]) -> Dict:
|
|
23
|
+
"""
|
|
24
|
+
Check which files are missing from server cache.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
files: List of file info dicts with keys: path, sha, size
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Dict with keys:
|
|
31
|
+
- missing: List of SHA hashes that need to be uploaded
|
|
32
|
+
- cached: Number of files already cached on server
|
|
33
|
+
"""
|
|
34
|
+
payload = {
|
|
35
|
+
"files": [
|
|
36
|
+
{"path": f["path"], "sha": f["sha"], "size": f["size"]}
|
|
37
|
+
for f in files
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
response = self._client._request("POST", "/files/check", json=payload)
|
|
42
|
+
return response.json()
|
|
43
|
+
|
|
44
|
+
def upload(self, content: bytes, sha: str, path: str) -> Dict:
|
|
45
|
+
"""
|
|
46
|
+
Upload a single file to the server.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
content: Raw file content as bytes
|
|
50
|
+
sha: SHA256 hash of the content
|
|
51
|
+
path: Relative file path
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Dict with keys: sha, size, stored
|
|
55
|
+
"""
|
|
56
|
+
import httpx
|
|
57
|
+
|
|
58
|
+
headers = {
|
|
59
|
+
"Authorization": f"Bearer {self._client._token}",
|
|
60
|
+
"Content-Type": "application/octet-stream",
|
|
61
|
+
"X-Xenfra-Sha": sha,
|
|
62
|
+
"X-Xenfra-Path": path,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
response = httpx.post(
|
|
66
|
+
f"{self._client.api_url}/files/upload",
|
|
67
|
+
content=content,
|
|
68
|
+
headers=headers,
|
|
69
|
+
timeout=120.0, # 2 minutes for large files
|
|
70
|
+
)
|
|
71
|
+
response.raise_for_status()
|
|
72
|
+
return response.json()
|
|
73
|
+
|
|
74
|
+
def upload_files(self, files: List[Dict], missing_shas: List[str], progress_callback=None) -> int:
|
|
75
|
+
"""
|
|
76
|
+
Upload multiple files that are missing from the server.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
files: List of file info dicts with keys: path, sha, size, abs_path
|
|
80
|
+
missing_shas: List of SHA hashes that need to be uploaded
|
|
81
|
+
progress_callback: Optional callback(uploaded_count, total_count)
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Number of files uploaded
|
|
85
|
+
"""
|
|
86
|
+
missing_set = set(missing_shas)
|
|
87
|
+
files_to_upload = [f for f in files if f["sha"] in missing_set]
|
|
88
|
+
total = len(files_to_upload)
|
|
89
|
+
uploaded = 0
|
|
90
|
+
|
|
91
|
+
for file_info in files_to_upload:
|
|
92
|
+
with open(file_info["abs_path"], "rb") as f:
|
|
93
|
+
content = f.read()
|
|
94
|
+
|
|
95
|
+
self.upload(content, file_info["sha"], file_info["path"])
|
|
96
|
+
uploaded += 1
|
|
97
|
+
|
|
98
|
+
if progress_callback:
|
|
99
|
+
progress_callback(uploaded, total)
|
|
100
|
+
|
|
101
|
+
return uploaded
|
|
@@ -1,95 +1,102 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Intelligence resource manager for Xenfra SDK.
|
|
3
|
-
Provides AI-powered deployment diagnosis and codebase analysis.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import logging
|
|
7
|
-
|
|
8
|
-
from ..exceptions import XenfraAPIError, XenfraError
|
|
9
|
-
from ..models import CodebaseAnalysisResponse, DiagnosisResponse
|
|
10
|
-
from ..utils import safe_json_parse
|
|
11
|
-
from .base import BaseManager
|
|
12
|
-
|
|
13
|
-
logger = logging.getLogger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class IntelligenceManager(BaseManager):
|
|
17
|
-
"""
|
|
18
|
-
Manager for AI-powered intelligence operations.
|
|
19
|
-
|
|
20
|
-
Provides:
|
|
21
|
-
- Deployment failure diagnosis (Zen Nod)
|
|
22
|
-
- Codebase analysis for zero-config init (Zen Init)
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
def diagnose(
|
|
26
|
-
self,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
1
|
+
"""
|
|
2
|
+
Intelligence resource manager for Xenfra SDK.
|
|
3
|
+
Provides AI-powered deployment diagnosis and codebase analysis.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from ..exceptions import XenfraAPIError, XenfraError
|
|
9
|
+
from ..models import CodebaseAnalysisResponse, DiagnosisResponse
|
|
10
|
+
from ..utils import safe_json_parse
|
|
11
|
+
from .base import BaseManager
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class IntelligenceManager(BaseManager):
|
|
17
|
+
"""
|
|
18
|
+
Manager for AI-powered intelligence operations.
|
|
19
|
+
|
|
20
|
+
Provides:
|
|
21
|
+
- Deployment failure diagnosis (Zen Nod)
|
|
22
|
+
- Codebase analysis for zero-config init (Zen Init)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def diagnose(
|
|
26
|
+
self,
|
|
27
|
+
logs: str,
|
|
28
|
+
package_manager: str | None = None,
|
|
29
|
+
dependency_file: str | None = None,
|
|
30
|
+
services: list | None = None
|
|
31
|
+
) -> DiagnosisResponse:
|
|
32
|
+
"""
|
|
33
|
+
Diagnose deployment failure from logs using AI.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
logs: The deployment logs to analyze
|
|
37
|
+
package_manager: Optional package manager context (uv, pip, poetry, npm, etc.)
|
|
38
|
+
If provided, AI will target this manager's dependency file
|
|
39
|
+
dependency_file: Optional dependency file context (pyproject.toml, requirements.txt, etc.)
|
|
40
|
+
If provided, AI will suggest patches for this file
|
|
41
|
+
services: Optional list of service definitions for project structure context (Zen Mode)
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
DiagnosisResponse with diagnosis, suggestion, and optional patch
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
XenfraAPIError: If the API request fails
|
|
48
|
+
XenfraError: If parsing the response fails
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
# Build request payload
|
|
52
|
+
payload = {"logs": logs}
|
|
53
|
+
if package_manager:
|
|
54
|
+
payload["package_manager"] = package_manager
|
|
55
|
+
if dependency_file:
|
|
56
|
+
payload["dependency_file"] = dependency_file
|
|
57
|
+
if services:
|
|
58
|
+
payload["services"] = services
|
|
59
|
+
|
|
60
|
+
response = self._client._request("POST", "/intelligence/diagnose", json=payload)
|
|
61
|
+
|
|
62
|
+
logger.debug(f"IntelligenceManager.diagnose response: status={response.status_code}")
|
|
63
|
+
|
|
64
|
+
# Safe JSON parsing
|
|
65
|
+
data = safe_json_parse(response)
|
|
66
|
+
return DiagnosisResponse(**data)
|
|
67
|
+
except XenfraAPIError:
|
|
68
|
+
raise
|
|
69
|
+
except Exception as e:
|
|
70
|
+
raise XenfraError(f"Failed to diagnose logs: {e}")
|
|
71
|
+
|
|
72
|
+
def analyze_codebase(self, code_snippets: dict[str, str]) -> CodebaseAnalysisResponse:
|
|
73
|
+
"""
|
|
74
|
+
Analyze codebase to detect framework, dependencies, and deployment config.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
code_snippets: Dictionary of filename -> content
|
|
78
|
+
e.g., {"main.py": "...", "requirements.txt": "..."}
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
CodebaseAnalysisResponse with detected configuration
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
XenfraAPIError: If the API request fails
|
|
85
|
+
XenfraError: If parsing the response fails
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
response = self._client._request(
|
|
89
|
+
"POST", "/intelligence/analyze-codebase", json={"code_snippets": code_snippets}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
logger.debug(
|
|
93
|
+
f"IntelligenceManager.analyze_codebase response: status={response.status_code}"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Safe JSON parsing
|
|
97
|
+
data = safe_json_parse(response)
|
|
98
|
+
return CodebaseAnalysisResponse(**data)
|
|
99
|
+
except XenfraAPIError:
|
|
100
|
+
raise
|
|
101
|
+
except Exception as e:
|
|
102
|
+
raise XenfraError(f"Failed to analyze codebase: {e}")
|
xenfra_sdk/security.py
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
# src/xenfra_sdk/security.py
|
|
2
|
-
"""
|
|
3
|
-
Security utilities for the Xenfra SDK.
|
|
4
|
-
Provides token encryption/decryption for storing OAuth credentials.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import os
|
|
8
|
-
from typing import Optional
|
|
9
|
-
|
|
10
|
-
from cryptography.fernet import Fernet
|
|
11
|
-
|
|
12
|
-
# --- Configuration from Environment ---
|
|
13
|
-
# These should be set in the service's environment
|
|
14
|
-
ENCRYPTION_KEY = os.getenv("ENCRYPTION_KEY", "")
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _get_fernet() -> Optional[Fernet]:
|
|
18
|
-
"""Get Fernet instance for encryption/decryption."""
|
|
19
|
-
if not ENCRYPTION_KEY:
|
|
20
|
-
return None
|
|
21
|
-
try:
|
|
22
|
-
return Fernet(ENCRYPTION_KEY.encode())
|
|
23
|
-
except Exception:
|
|
24
|
-
return None
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# --- Token Encryption ---
|
|
28
|
-
def encrypt_token(token: str) -> str:
|
|
29
|
-
"""Encrypts a token using Fernet symmetric encryption."""
|
|
30
|
-
fernet = _get_fernet()
|
|
31
|
-
if fernet is None:
|
|
32
|
-
raise ValueError("ENCRYPTION_KEY environment variable is not set or invalid")
|
|
33
|
-
return fernet.encrypt(token.encode()).decode()
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def decrypt_token(encrypted_token: str) -> str:
|
|
37
|
-
"""Decrypts a token."""
|
|
38
|
-
fernet = _get_fernet()
|
|
39
|
-
if fernet is None:
|
|
40
|
-
raise ValueError("ENCRYPTION_KEY environment variable is not set or invalid")
|
|
41
|
-
return fernet.decrypt(encrypted_token.encode()).decode()
|
|
1
|
+
# src/xenfra_sdk/security.py
|
|
2
|
+
"""
|
|
3
|
+
Security utilities for the Xenfra SDK.
|
|
4
|
+
Provides token encryption/decryption for storing OAuth credentials.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from cryptography.fernet import Fernet
|
|
11
|
+
|
|
12
|
+
# --- Configuration from Environment ---
|
|
13
|
+
# These should be set in the service's environment
|
|
14
|
+
ENCRYPTION_KEY = os.getenv("ENCRYPTION_KEY", "")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _get_fernet() -> Optional[Fernet]:
|
|
18
|
+
"""Get Fernet instance for encryption/decryption."""
|
|
19
|
+
if not ENCRYPTION_KEY:
|
|
20
|
+
return None
|
|
21
|
+
try:
|
|
22
|
+
return Fernet(ENCRYPTION_KEY.encode())
|
|
23
|
+
except Exception:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# --- Token Encryption ---
|
|
28
|
+
def encrypt_token(token: str) -> str:
|
|
29
|
+
"""Encrypts a token using Fernet symmetric encryption."""
|
|
30
|
+
fernet = _get_fernet()
|
|
31
|
+
if fernet is None:
|
|
32
|
+
raise ValueError("ENCRYPTION_KEY environment variable is not set or invalid")
|
|
33
|
+
return fernet.encrypt(token.encode()).decode()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def decrypt_token(encrypted_token: str) -> str:
|
|
37
|
+
"""Decrypts a token."""
|
|
38
|
+
fernet = _get_fernet()
|
|
39
|
+
if fernet is None:
|
|
40
|
+
raise ValueError("ENCRYPTION_KEY environment variable is not set or invalid")
|
|
41
|
+
return fernet.decrypt(encrypted_token.encode()).decode()
|