xenfra 0.4.2__py3-none-any.whl → 0.4.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/commands/__init__.py +3 -3
- xenfra/commands/auth.py +144 -144
- xenfra/commands/auth_device.py +164 -164
- xenfra/commands/deployments.py +1133 -912
- xenfra/commands/intelligence.py +503 -412
- xenfra/commands/projects.py +204 -204
- xenfra/commands/security_cmd.py +233 -233
- xenfra/main.py +76 -75
- xenfra/utils/__init__.py +3 -3
- xenfra/utils/auth.py +374 -374
- xenfra/utils/codebase.py +169 -169
- xenfra/utils/config.py +459 -432
- xenfra/utils/errors.py +116 -116
- xenfra/utils/file_sync.py +286 -0
- xenfra/utils/security.py +336 -336
- xenfra/utils/validation.py +234 -234
- xenfra-0.4.4.dist-info/METADATA +113 -0
- xenfra-0.4.4.dist-info/RECORD +21 -0
- {xenfra-0.4.2.dist-info → xenfra-0.4.4.dist-info}/WHEEL +2 -2
- xenfra-0.4.2.dist-info/METADATA +0 -118
- xenfra-0.4.2.dist-info/RECORD +0 -20
- {xenfra-0.4.2.dist-info → xenfra-0.4.4.dist-info}/entry_points.txt +0 -0
xenfra/utils/codebase.py
CHANGED
|
@@ -1,169 +1,169 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Codebase scanning utilities for AI-powered project initialization.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def scan_codebase(max_files: int = 10, max_size: int = 50000) -> dict[str, str]:
|
|
10
|
-
"""
|
|
11
|
-
Scan current directory for important code files.
|
|
12
|
-
|
|
13
|
-
Args:
|
|
14
|
-
max_files: Maximum number of files to include (validated: 1-100)
|
|
15
|
-
max_size: Maximum file size in bytes (validated: 1KB-10MB)
|
|
16
|
-
|
|
17
|
-
Returns:
|
|
18
|
-
Dictionary of filename -> content for AI analysis
|
|
19
|
-
"""
|
|
20
|
-
# Validate limits
|
|
21
|
-
from .validation import validate_codebase_scan_limits
|
|
22
|
-
|
|
23
|
-
is_valid, error_msg = validate_codebase_scan_limits(max_files, max_size)
|
|
24
|
-
if not is_valid:
|
|
25
|
-
raise ValueError(f"Invalid scan limits: {error_msg}")
|
|
26
|
-
|
|
27
|
-
# Ensure limits are within bounds
|
|
28
|
-
max_files = max(1, min(100, max_files))
|
|
29
|
-
max_size = max(1024, min(10 * 1024 * 1024, max_size))
|
|
30
|
-
"""
|
|
31
|
-
Scan current directory for important code files.
|
|
32
|
-
|
|
33
|
-
Args:
|
|
34
|
-
max_files: Maximum number of files to include
|
|
35
|
-
max_size: Maximum file size in bytes (default 50KB)
|
|
36
|
-
|
|
37
|
-
Returns:
|
|
38
|
-
Dictionary of filename -> content for AI analysis
|
|
39
|
-
"""
|
|
40
|
-
code_snippets = {}
|
|
41
|
-
|
|
42
|
-
# Priority files to scan (in order)
|
|
43
|
-
important_files = [
|
|
44
|
-
# Python entry points
|
|
45
|
-
"main.py",
|
|
46
|
-
"app.py",
|
|
47
|
-
"wsgi.py",
|
|
48
|
-
"asgi.py",
|
|
49
|
-
"manage.py",
|
|
50
|
-
# Configuration files
|
|
51
|
-
"requirements.txt",
|
|
52
|
-
"pyproject.toml",
|
|
53
|
-
"Pipfile",
|
|
54
|
-
"setup.py",
|
|
55
|
-
# Django/Flask specific
|
|
56
|
-
"settings.py",
|
|
57
|
-
"config.py",
|
|
58
|
-
# Docker
|
|
59
|
-
"Dockerfile",
|
|
60
|
-
"docker-compose.yml",
|
|
61
|
-
# Xenfra config
|
|
62
|
-
"xenfra.yaml",
|
|
63
|
-
"xenfra.yml",
|
|
64
|
-
]
|
|
65
|
-
|
|
66
|
-
# Scan for important files in current directory
|
|
67
|
-
for filename in important_files:
|
|
68
|
-
if len(code_snippets) >= max_files:
|
|
69
|
-
break
|
|
70
|
-
|
|
71
|
-
if os.path.exists(filename) and os.path.isfile(filename):
|
|
72
|
-
try:
|
|
73
|
-
file_size = os.path.getsize(filename)
|
|
74
|
-
if file_size > max_size:
|
|
75
|
-
continue
|
|
76
|
-
|
|
77
|
-
with open(filename, "r", encoding="utf-8") as f:
|
|
78
|
-
content = f.read(max_size)
|
|
79
|
-
code_snippets[filename] = content
|
|
80
|
-
except (IOError, OSError, PermissionError, UnicodeDecodeError) as e:
|
|
81
|
-
# Skip files that can't be read (log but don't crash)
|
|
82
|
-
import logging
|
|
83
|
-
|
|
84
|
-
logger = logging.getLogger(__name__)
|
|
85
|
-
logger.debug(f"Skipping file {filename}: {type(e).__name__}")
|
|
86
|
-
continue
|
|
87
|
-
|
|
88
|
-
# If we haven't found enough files, look for Python files in common locations
|
|
89
|
-
if len(code_snippets) < 3:
|
|
90
|
-
search_patterns = [
|
|
91
|
-
"src/**/*.py",
|
|
92
|
-
"app/**/*.py",
|
|
93
|
-
"*.py",
|
|
94
|
-
]
|
|
95
|
-
|
|
96
|
-
for pattern in search_patterns:
|
|
97
|
-
if len(code_snippets) >= max_files:
|
|
98
|
-
break
|
|
99
|
-
|
|
100
|
-
for filepath in Path(".").glob(pattern):
|
|
101
|
-
if len(code_snippets) >= max_files:
|
|
102
|
-
break
|
|
103
|
-
|
|
104
|
-
if filepath.is_file() and filepath.name not in code_snippets:
|
|
105
|
-
try:
|
|
106
|
-
file_size = filepath.stat().st_size
|
|
107
|
-
if file_size > max_size:
|
|
108
|
-
continue
|
|
109
|
-
|
|
110
|
-
with open(filepath, "r", encoding="utf-8") as f:
|
|
111
|
-
content = f.read(max_size)
|
|
112
|
-
code_snippets[str(filepath)] = content
|
|
113
|
-
except (IOError, OSError, PermissionError, UnicodeDecodeError) as e:
|
|
114
|
-
# Skip files that can't be read
|
|
115
|
-
import logging
|
|
116
|
-
|
|
117
|
-
logger = logging.getLogger(__name__)
|
|
118
|
-
logger.debug(f"Skipping file {filepath}: {type(e).__name__}")
|
|
119
|
-
continue
|
|
120
|
-
|
|
121
|
-
return code_snippets
|
|
122
|
-
|
|
123
|
-
def detect_package_manager_conflicts(code_snippets: dict[str, str]) -> tuple[bool, list[dict]]:
|
|
124
|
-
"""
|
|
125
|
-
Deterministically detect package manager conflicts from scanned files.
|
|
126
|
-
|
|
127
|
-
This ensures Zen Nod (conflict resolution) always triggers when multiple
|
|
128
|
-
package managers are present, regardless of AI detection.
|
|
129
|
-
|
|
130
|
-
Args:
|
|
131
|
-
code_snippets: Dictionary of filename -> content from scan_codebase()
|
|
132
|
-
|
|
133
|
-
Returns:
|
|
134
|
-
(has_conflict, detected_managers) where detected_managers is a list of
|
|
135
|
-
{"manager": str, "file": str} dictionaries
|
|
136
|
-
"""
|
|
137
|
-
detected = []
|
|
138
|
-
|
|
139
|
-
# Check for Python package managers
|
|
140
|
-
if "pyproject.toml" in code_snippets:
|
|
141
|
-
content = code_snippets["pyproject.toml"]
|
|
142
|
-
if "[tool.poetry]" in content or "poetry.lock" in code_snippets:
|
|
143
|
-
detected.append({"manager": "poetry", "file": "pyproject.toml"})
|
|
144
|
-
elif "[tool.uv]" in content or "uv.lock" in code_snippets or "[project]" in content:
|
|
145
|
-
detected.append({"manager": "uv", "file": "pyproject.toml"})
|
|
146
|
-
|
|
147
|
-
if "Pipfile" in code_snippets:
|
|
148
|
-
detected.append({"manager": "pipenv", "file": "Pipfile"})
|
|
149
|
-
|
|
150
|
-
if "requirements.txt" in code_snippets:
|
|
151
|
-
detected.append({"manager": "pip", "file": "requirements.txt"})
|
|
152
|
-
|
|
153
|
-
# Check for Node.js package managers (all independent checks)
|
|
154
|
-
if "pnpm-lock.yaml" in code_snippets:
|
|
155
|
-
detected.append({"manager": "pnpm", "file": "pnpm-lock.yaml"})
|
|
156
|
-
|
|
157
|
-
if "yarn.lock" in code_snippets:
|
|
158
|
-
detected.append({"manager": "yarn", "file": "yarn.lock"})
|
|
159
|
-
|
|
160
|
-
if "package-lock.json" in code_snippets:
|
|
161
|
-
detected.append({"manager": "npm", "file": "package-lock.json"})
|
|
162
|
-
|
|
163
|
-
has_conflict = len(detected) > 1
|
|
164
|
-
return has_conflict, detected
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def has_xenfra_config() -> bool:
|
|
168
|
-
"""Check if xenfra.yaml already exists."""
|
|
169
|
-
return os.path.exists("xenfra.yaml") or os.path.exists("xenfra.yml")
|
|
1
|
+
"""
|
|
2
|
+
Codebase scanning utilities for AI-powered project initialization.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def scan_codebase(max_files: int = 10, max_size: int = 50000) -> dict[str, str]:
|
|
10
|
+
"""
|
|
11
|
+
Scan current directory for important code files.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
max_files: Maximum number of files to include (validated: 1-100)
|
|
15
|
+
max_size: Maximum file size in bytes (validated: 1KB-10MB)
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Dictionary of filename -> content for AI analysis
|
|
19
|
+
"""
|
|
20
|
+
# Validate limits
|
|
21
|
+
from .validation import validate_codebase_scan_limits
|
|
22
|
+
|
|
23
|
+
is_valid, error_msg = validate_codebase_scan_limits(max_files, max_size)
|
|
24
|
+
if not is_valid:
|
|
25
|
+
raise ValueError(f"Invalid scan limits: {error_msg}")
|
|
26
|
+
|
|
27
|
+
# Ensure limits are within bounds
|
|
28
|
+
max_files = max(1, min(100, max_files))
|
|
29
|
+
max_size = max(1024, min(10 * 1024 * 1024, max_size))
|
|
30
|
+
"""
|
|
31
|
+
Scan current directory for important code files.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
max_files: Maximum number of files to include
|
|
35
|
+
max_size: Maximum file size in bytes (default 50KB)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dictionary of filename -> content for AI analysis
|
|
39
|
+
"""
|
|
40
|
+
code_snippets = {}
|
|
41
|
+
|
|
42
|
+
# Priority files to scan (in order)
|
|
43
|
+
important_files = [
|
|
44
|
+
# Python entry points
|
|
45
|
+
"main.py",
|
|
46
|
+
"app.py",
|
|
47
|
+
"wsgi.py",
|
|
48
|
+
"asgi.py",
|
|
49
|
+
"manage.py",
|
|
50
|
+
# Configuration files
|
|
51
|
+
"requirements.txt",
|
|
52
|
+
"pyproject.toml",
|
|
53
|
+
"Pipfile",
|
|
54
|
+
"setup.py",
|
|
55
|
+
# Django/Flask specific
|
|
56
|
+
"settings.py",
|
|
57
|
+
"config.py",
|
|
58
|
+
# Docker
|
|
59
|
+
"Dockerfile",
|
|
60
|
+
"docker-compose.yml",
|
|
61
|
+
# Xenfra config
|
|
62
|
+
"xenfra.yaml",
|
|
63
|
+
"xenfra.yml",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
# Scan for important files in current directory
|
|
67
|
+
for filename in important_files:
|
|
68
|
+
if len(code_snippets) >= max_files:
|
|
69
|
+
break
|
|
70
|
+
|
|
71
|
+
if os.path.exists(filename) and os.path.isfile(filename):
|
|
72
|
+
try:
|
|
73
|
+
file_size = os.path.getsize(filename)
|
|
74
|
+
if file_size > max_size:
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
78
|
+
content = f.read(max_size)
|
|
79
|
+
code_snippets[filename] = content
|
|
80
|
+
except (IOError, OSError, PermissionError, UnicodeDecodeError) as e:
|
|
81
|
+
# Skip files that can't be read (log but don't crash)
|
|
82
|
+
import logging
|
|
83
|
+
|
|
84
|
+
logger = logging.getLogger(__name__)
|
|
85
|
+
logger.debug(f"Skipping file {filename}: {type(e).__name__}")
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
# If we haven't found enough files, look for Python files in common locations
|
|
89
|
+
if len(code_snippets) < 3:
|
|
90
|
+
search_patterns = [
|
|
91
|
+
"src/**/*.py",
|
|
92
|
+
"app/**/*.py",
|
|
93
|
+
"*.py",
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
for pattern in search_patterns:
|
|
97
|
+
if len(code_snippets) >= max_files:
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
for filepath in Path(".").glob(pattern):
|
|
101
|
+
if len(code_snippets) >= max_files:
|
|
102
|
+
break
|
|
103
|
+
|
|
104
|
+
if filepath.is_file() and filepath.name not in code_snippets:
|
|
105
|
+
try:
|
|
106
|
+
file_size = filepath.stat().st_size
|
|
107
|
+
if file_size > max_size:
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
111
|
+
content = f.read(max_size)
|
|
112
|
+
code_snippets[str(filepath)] = content
|
|
113
|
+
except (IOError, OSError, PermissionError, UnicodeDecodeError) as e:
|
|
114
|
+
# Skip files that can't be read
|
|
115
|
+
import logging
|
|
116
|
+
|
|
117
|
+
logger = logging.getLogger(__name__)
|
|
118
|
+
logger.debug(f"Skipping file {filepath}: {type(e).__name__}")
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
return code_snippets
|
|
122
|
+
|
|
123
|
+
def detect_package_manager_conflicts(code_snippets: dict[str, str]) -> tuple[bool, list[dict]]:
|
|
124
|
+
"""
|
|
125
|
+
Deterministically detect package manager conflicts from scanned files.
|
|
126
|
+
|
|
127
|
+
This ensures Zen Nod (conflict resolution) always triggers when multiple
|
|
128
|
+
package managers are present, regardless of AI detection.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
code_snippets: Dictionary of filename -> content from scan_codebase()
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
(has_conflict, detected_managers) where detected_managers is a list of
|
|
135
|
+
{"manager": str, "file": str} dictionaries
|
|
136
|
+
"""
|
|
137
|
+
detected = []
|
|
138
|
+
|
|
139
|
+
# Check for Python package managers
|
|
140
|
+
if "pyproject.toml" in code_snippets:
|
|
141
|
+
content = code_snippets["pyproject.toml"]
|
|
142
|
+
if "[tool.poetry]" in content or "poetry.lock" in code_snippets:
|
|
143
|
+
detected.append({"manager": "poetry", "file": "pyproject.toml"})
|
|
144
|
+
elif "[tool.uv]" in content or "uv.lock" in code_snippets or "[project]" in content:
|
|
145
|
+
detected.append({"manager": "uv", "file": "pyproject.toml"})
|
|
146
|
+
|
|
147
|
+
if "Pipfile" in code_snippets:
|
|
148
|
+
detected.append({"manager": "pipenv", "file": "Pipfile"})
|
|
149
|
+
|
|
150
|
+
if "requirements.txt" in code_snippets:
|
|
151
|
+
detected.append({"manager": "pip", "file": "requirements.txt"})
|
|
152
|
+
|
|
153
|
+
# Check for Node.js package managers (all independent checks)
|
|
154
|
+
if "pnpm-lock.yaml" in code_snippets:
|
|
155
|
+
detected.append({"manager": "pnpm", "file": "pnpm-lock.yaml"})
|
|
156
|
+
|
|
157
|
+
if "yarn.lock" in code_snippets:
|
|
158
|
+
detected.append({"manager": "yarn", "file": "yarn.lock"})
|
|
159
|
+
|
|
160
|
+
if "package-lock.json" in code_snippets:
|
|
161
|
+
detected.append({"manager": "npm", "file": "package-lock.json"})
|
|
162
|
+
|
|
163
|
+
has_conflict = len(detected) > 1
|
|
164
|
+
return has_conflict, detected
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def has_xenfra_config() -> bool:
|
|
168
|
+
"""Check if xenfra.yaml already exists."""
|
|
169
|
+
return os.path.exists("xenfra.yaml") or os.path.exists("xenfra.yml")
|