dlab-cli 0.1.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.
- dlab/__init__.py +6 -0
- dlab/cli.py +1075 -0
- dlab/config.py +190 -0
- dlab/create_dpack.py +1096 -0
- dlab/create_dpack_wizard.py +1471 -0
- dlab/create_parallel_agent_wizard.py +582 -0
- dlab/data/__init__.py +0 -0
- dlab/data/models.json +1793 -0
- dlab/docker.py +591 -0
- dlab/local.py +269 -0
- dlab/model_fallback.py +360 -0
- dlab/parallel_tool.py +18 -0
- dlab/session.py +389 -0
- dlab/timeline.py +684 -0
- dlab/tui/__init__.py +9 -0
- dlab/tui/app.py +664 -0
- dlab/tui/log_watcher.py +208 -0
- dlab/tui/models.py +438 -0
- dlab/tui/widgets/__init__.py +18 -0
- dlab/tui/widgets/agent_list.py +170 -0
- dlab/tui/widgets/artifacts_pane.py +618 -0
- dlab/tui/widgets/log_view.py +505 -0
- dlab/tui/widgets/search_popup.py +151 -0
- dlab/tui/widgets/status_bar.py +106 -0
- dlab_cli-0.1.0.dist-info/METADATA +237 -0
- dlab_cli-0.1.0.dist-info/RECORD +30 -0
- dlab_cli-0.1.0.dist-info/WHEEL +5 -0
- dlab_cli-0.1.0.dist-info/entry_points.txt +2 -0
- dlab_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
- dlab_cli-0.1.0.dist-info/top_level.txt +1 -0
dlab/config.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration loading and validation for decision-pack config directories.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
REQUIRED_DIRS: list[str] = ["docker", "opencode"]
|
|
12
|
+
REQUIRED_FILES: list[str] = ["config.yaml"]
|
|
13
|
+
CONFIG_KEYS: list[str] = ["name", "description", "docker_image_name", "default_model"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def list_config_issues(config_dir: str) -> list[str]:
|
|
17
|
+
"""
|
|
18
|
+
Check a decision-pack directory and return a list of issues found.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
config_dir : str
|
|
23
|
+
Path to the decision-pack config directory.
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
list[str]
|
|
28
|
+
List of issue descriptions. Empty if valid.
|
|
29
|
+
"""
|
|
30
|
+
issues: list[str] = []
|
|
31
|
+
config_path: Path = Path(config_dir)
|
|
32
|
+
|
|
33
|
+
if not config_path.exists():
|
|
34
|
+
return [f"Directory does not exist: {config_dir}"]
|
|
35
|
+
if not config_path.is_dir():
|
|
36
|
+
return [f"Path is not a directory: {config_dir}"]
|
|
37
|
+
|
|
38
|
+
for required_dir in REQUIRED_DIRS:
|
|
39
|
+
dir_path: Path = config_path / required_dir
|
|
40
|
+
if not dir_path.exists():
|
|
41
|
+
issues.append(f"Missing directory: {required_dir}/")
|
|
42
|
+
elif not dir_path.is_dir():
|
|
43
|
+
issues.append(f"Expected directory but found file: {required_dir}")
|
|
44
|
+
|
|
45
|
+
for required_file in REQUIRED_FILES:
|
|
46
|
+
file_path: Path = config_path / required_file
|
|
47
|
+
if not file_path.exists():
|
|
48
|
+
issues.append(f"Missing file: {required_file}")
|
|
49
|
+
elif not file_path.is_file():
|
|
50
|
+
issues.append(f"Expected file but found directory: {required_file}")
|
|
51
|
+
|
|
52
|
+
return issues
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def validate_config_structure(config_dir: str) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Validate that a decision-pack config directory has the required structure.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
config_dir : str
|
|
62
|
+
Path to the decision-pack config directory.
|
|
63
|
+
|
|
64
|
+
Raises
|
|
65
|
+
------
|
|
66
|
+
ValueError
|
|
67
|
+
If the directory structure is invalid.
|
|
68
|
+
"""
|
|
69
|
+
config_path: Path = Path(config_dir)
|
|
70
|
+
|
|
71
|
+
if not config_path.exists():
|
|
72
|
+
raise ValueError(f"Config directory does not exist: {config_dir}")
|
|
73
|
+
|
|
74
|
+
if not config_path.is_dir():
|
|
75
|
+
raise ValueError(f"Config path is not a directory: {config_dir}")
|
|
76
|
+
|
|
77
|
+
for required_dir in REQUIRED_DIRS:
|
|
78
|
+
dir_path: Path = config_path / required_dir
|
|
79
|
+
if not dir_path.exists():
|
|
80
|
+
raise ValueError(f"Missing required directory: {required_dir}")
|
|
81
|
+
if not dir_path.is_dir():
|
|
82
|
+
raise ValueError(f"Expected directory but found file: {required_dir}")
|
|
83
|
+
|
|
84
|
+
for required_file in REQUIRED_FILES:
|
|
85
|
+
file_path: Path = config_path / required_file
|
|
86
|
+
if not file_path.exists():
|
|
87
|
+
raise ValueError(f"Missing required file: {required_file}")
|
|
88
|
+
if not file_path.is_file():
|
|
89
|
+
raise ValueError(f"Expected file but found directory: {required_file}")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def load_config_yaml(config_dir: str) -> dict[str, Any]:
|
|
93
|
+
"""
|
|
94
|
+
Load and validate config.yaml from a decision-pack config directory.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
config_dir : str
|
|
99
|
+
Path to the decision-pack config directory.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
dict[str, Any]
|
|
104
|
+
The parsed config.yaml contents.
|
|
105
|
+
|
|
106
|
+
Raises
|
|
107
|
+
------
|
|
108
|
+
ValueError
|
|
109
|
+
If config.yaml is invalid or missing required keys.
|
|
110
|
+
"""
|
|
111
|
+
config_path: Path = Path(config_dir) / "config.yaml"
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
with open(config_path, "r") as f:
|
|
115
|
+
config: dict[str, Any] = yaml.safe_load(f)
|
|
116
|
+
except yaml.YAMLError as e:
|
|
117
|
+
raise ValueError(f"Invalid YAML in config.yaml: {e}")
|
|
118
|
+
|
|
119
|
+
if not isinstance(config, dict):
|
|
120
|
+
raise ValueError("config.yaml must contain a YAML mapping")
|
|
121
|
+
|
|
122
|
+
missing_keys: list[str] = [key for key in CONFIG_KEYS if key not in config]
|
|
123
|
+
if missing_keys:
|
|
124
|
+
raise ValueError(f"config.yaml missing required keys: {missing_keys}")
|
|
125
|
+
|
|
126
|
+
return config
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def load_dpack_config(config_dir: str) -> dict[str, Any]:
|
|
130
|
+
"""
|
|
131
|
+
Load and validate a complete decision-pack configuration.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
config_dir : str
|
|
136
|
+
Path to the decision-pack config directory.
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
dict[str, Any]
|
|
141
|
+
Complete decision-pack configuration including:
|
|
142
|
+
- config_dir: Absolute path to config directory
|
|
143
|
+
- name: decision-pack name
|
|
144
|
+
- description: decision-pack description
|
|
145
|
+
- docker_image_name: Name for the Docker image
|
|
146
|
+
- default_model: Default LLM model to use
|
|
147
|
+
- opencode_version: Version of opencode to install (optional, defaults to "latest")
|
|
148
|
+
|
|
149
|
+
Raises
|
|
150
|
+
------
|
|
151
|
+
ValueError
|
|
152
|
+
If the configuration is invalid.
|
|
153
|
+
"""
|
|
154
|
+
config_path: Path = Path(config_dir).resolve()
|
|
155
|
+
config_dir_str: str = str(config_path)
|
|
156
|
+
|
|
157
|
+
validate_config_structure(config_dir_str)
|
|
158
|
+
config: dict[str, Any] = load_config_yaml(config_dir_str)
|
|
159
|
+
|
|
160
|
+
config["config_dir"] = config_dir_str
|
|
161
|
+
|
|
162
|
+
# Autodetect package_manager from docker/ contents if not specified
|
|
163
|
+
if "package_manager" not in config:
|
|
164
|
+
docker_dir: Path = config_path / "docker"
|
|
165
|
+
if (docker_dir / "environment.yml").exists():
|
|
166
|
+
config["package_manager"] = "conda"
|
|
167
|
+
elif (docker_dir / "pixi.toml").exists():
|
|
168
|
+
config["package_manager"] = "pixi"
|
|
169
|
+
else:
|
|
170
|
+
config["package_manager"] = "pip"
|
|
171
|
+
|
|
172
|
+
# Default opencode_version to "latest" if not specified
|
|
173
|
+
if "opencode_version" not in config:
|
|
174
|
+
config["opencode_version"] = "latest"
|
|
175
|
+
|
|
176
|
+
# Normalize hooks: string -> list, missing -> empty list
|
|
177
|
+
hooks: dict[str, Any] = config.get("hooks", {})
|
|
178
|
+
if not isinstance(hooks, dict):
|
|
179
|
+
hooks = {}
|
|
180
|
+
for key in ("pre-run", "post-run"):
|
|
181
|
+
value: Any = hooks.get(key, [])
|
|
182
|
+
if isinstance(value, str):
|
|
183
|
+
hooks[key] = [value]
|
|
184
|
+
elif isinstance(value, list):
|
|
185
|
+
hooks[key] = value
|
|
186
|
+
else:
|
|
187
|
+
hooks[key] = []
|
|
188
|
+
config["hooks"] = hooks
|
|
189
|
+
|
|
190
|
+
return config
|