hiverge-cli 0.1.0__tar.gz

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.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,101 @@
1
+ Metadata-Version: 2.4
2
+ Name: hiverge-cli
3
+ Version: 0.1.0
4
+ Summary: Universal Command Line Interface for Hive agent.
5
+ Author: Hiverge Team
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.7
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: PyYAML>=5.1
13
+ Requires-Dist: pydantic>=1.8.2
14
+ Requires-Dist: gitpython>=3.1.24
15
+ Requires-Dist: python-dotenv>=0.19.2
16
+ Requires-Dist: rich>=12.5.1
17
+ Requires-Dist: kubernetes>=32.0.0
18
+ Provides-Extra: test
19
+ Provides-Extra: dev
20
+ Requires-Dist: ruff>=0.12; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # Hive-CLI
24
+
25
+ Hive-CLI is a command-line interface for managing and deploying Hive agent and experiments on Kubernetes and other platforms.
26
+
27
+ ```bash
28
+ ███ █████ █████ ███
29
+ ░░░███ ░░███ ░░███ ░░░
30
+ ░░░███ ░███ ░███ ████ █████ █████ ██████
31
+ ░░░███ ░███████████ ░░███ ░░███ ░░███ ███░░███
32
+ ███░ ░███░░░░░███ ░███ ░███ ░███ ░███████
33
+ ███░ ░███ ░███ ░███ ░░███ ███ ░███░░░
34
+ ███░ █████ █████ █████ ░░█████ ░░██████
35
+ ░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░
36
+ ```
37
+
38
+ ## Installation
39
+
40
+ ### Install via pip (Not-Available Yet)
41
+
42
+ ```bash
43
+ pip install hive-cli
44
+ ```
45
+
46
+ ### Install from source
47
+
48
+ ```bash
49
+ source start.sh
50
+ ```
51
+
52
+ ## How to run
53
+
54
+ **Note**: Hive-CLI reads the configuration from a yaml file, by default it will look for the `~/.hive/sandbox-config.yaml`. You can also specify a different configuration file using the `-f` option. Refer to the [config.yaml](./config.yaml) for examples.
55
+
56
+ Below we assume that you have a `~/.hive/sandbox-config.yaml` file.
57
+
58
+ ### Edit the experiment
59
+
60
+ `Edit` command will open the configuration file in your default editor (e.g., vim, nano, etc.) for you to modify the experiment configuration. You can also specify a different editor using the `EDITOR` environment variable, by default it will use `vim`.
61
+
62
+ ```bash
63
+ hive edit config
64
+ ```
65
+
66
+ ### Create an experiment
67
+
68
+ ```bash
69
+ hive create exp my-experiment
70
+ ```
71
+
72
+ ### List experiments
73
+
74
+ ```bash
75
+ hive show exps
76
+ ```
77
+
78
+ ### Visit Dashboard
79
+
80
+ ```bash
81
+ hive dashboard
82
+ ```
83
+
84
+ ### Delete an experiment
85
+
86
+
87
+ ```bash
88
+ hive delete exp my-experiment
89
+ ```
90
+
91
+ ### More
92
+
93
+ See `hive -h` for more details.
94
+
95
+ ## Development
96
+
97
+ **Note**: Hive-CLI will read the `.env` file to load logging configurations. Refer to the `.env.example` file for examples.
98
+
99
+ ### Debugging
100
+
101
+ Change the log level in `.env` file to `DEBUG` to see more detailed logs.
@@ -0,0 +1,79 @@
1
+ # Hive-CLI
2
+
3
+ Hive-CLI is a command-line interface for managing and deploying Hive agent and experiments on Kubernetes and other platforms.
4
+
5
+ ```bash
6
+ ███ █████ █████ ███
7
+ ░░░███ ░░███ ░░███ ░░░
8
+ ░░░███ ░███ ░███ ████ █████ █████ ██████
9
+ ░░░███ ░███████████ ░░███ ░░███ ░░███ ███░░███
10
+ ███░ ░███░░░░░███ ░███ ░███ ░███ ░███████
11
+ ███░ ░███ ░███ ░███ ░░███ ███ ░███░░░
12
+ ███░ █████ █████ █████ ░░█████ ░░██████
13
+ ░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░
14
+ ```
15
+
16
+ ## Installation
17
+
18
+ ### Install via pip (Not-Available Yet)
19
+
20
+ ```bash
21
+ pip install hive-cli
22
+ ```
23
+
24
+ ### Install from source
25
+
26
+ ```bash
27
+ source start.sh
28
+ ```
29
+
30
+ ## How to run
31
+
32
+ **Note**: Hive-CLI reads the configuration from a yaml file, by default it will look for the `~/.hive/sandbox-config.yaml`. You can also specify a different configuration file using the `-f` option. Refer to the [config.yaml](./config.yaml) for examples.
33
+
34
+ Below we assume that you have a `~/.hive/sandbox-config.yaml` file.
35
+
36
+ ### Edit the experiment
37
+
38
+ `Edit` command will open the configuration file in your default editor (e.g., vim, nano, etc.) for you to modify the experiment configuration. You can also specify a different editor using the `EDITOR` environment variable, by default it will use `vim`.
39
+
40
+ ```bash
41
+ hive edit config
42
+ ```
43
+
44
+ ### Create an experiment
45
+
46
+ ```bash
47
+ hive create exp my-experiment
48
+ ```
49
+
50
+ ### List experiments
51
+
52
+ ```bash
53
+ hive show exps
54
+ ```
55
+
56
+ ### Visit Dashboard
57
+
58
+ ```bash
59
+ hive dashboard
60
+ ```
61
+
62
+ ### Delete an experiment
63
+
64
+
65
+ ```bash
66
+ hive delete exp my-experiment
67
+ ```
68
+
69
+ ### More
70
+
71
+ See `hive -h` for more details.
72
+
73
+ ## Development
74
+
75
+ **Note**: Hive-CLI will read the `.env` file to load logging configurations. Refer to the `.env.example` file for examples.
76
+
77
+ ### Debugging
78
+
79
+ Change the log level in `.env` file to `DEBUG` to see more detailed logs.
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hiverge-cli"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name="Hiverge Team" },
10
+ ]
11
+ description = "Universal Command Line Interface for Hive agent."
12
+ readme = "README.md"
13
+ requires-python = ">=3.7"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+
20
+ dependencies = [
21
+ "PyYAML>=5.1",
22
+ "pydantic>=1.8.2",
23
+ "gitpython>=3.1.24",
24
+ "python-dotenv>=0.19.2",
25
+ "rich>=12.5.1",
26
+ "kubernetes>=32.0.0",
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ test = [
31
+ ]
32
+ dev = [
33
+ "ruff>=0.12",
34
+ ]
35
+
36
+ [project.scripts]
37
+ hive = "hive_cli.main:main"
38
+
39
+ [tool.ruff]
40
+ line-length = 100
41
+ exclude = ["libs"]
42
+
43
+ [tool.ruff.lint]
44
+ select = ["E", "F", "I"]
45
+ ignore = ["E501"]
46
+
47
+ [tool.ruff.format]
48
+ quote-style = "double"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,129 @@
1
+ import os
2
+ from enum import Enum
3
+ from typing import Optional
4
+
5
+ import yaml
6
+ from pydantic import BaseModel, Field, field_validator
7
+
8
+
9
+ class PlatformType(str, Enum):
10
+ K8S = "k8s"
11
+ ON_PREM = "on-prem"
12
+
13
+
14
+ class ResourceConfig(BaseModel):
15
+ requests: Optional[dict] = None
16
+ limits: Optional[dict] = None
17
+ accelerators: Optional[str] = None # e.g., "a100-80gb:8"
18
+ shmsize: Optional[str] = None
19
+
20
+
21
+ class EnvConfig(BaseModel):
22
+ name: str
23
+ value: str
24
+
25
+
26
+ class SandboxConfig(BaseModel):
27
+ image: Optional[str] = None
28
+ replicas: int = 1
29
+ timeout: int = 60
30
+ resources: Optional[ResourceConfig] = None
31
+ envs: Optional[list[EnvConfig]] = None
32
+ pre_processor: Optional[str] = Field(
33
+ default=None,
34
+ description="The pre-processing script to run before the experiment. Use the `/data` directory to load/store datasets.",
35
+ )
36
+
37
+
38
+ class RepoConfig(BaseModel):
39
+ url: str
40
+ branch: str = "main"
41
+ evaluation_script: str = "evaluator.py"
42
+ evolve_files_and_ranges: str
43
+
44
+
45
+ class WanDBConfig(BaseModel):
46
+ enabled: bool = False
47
+
48
+
49
+ class GCPConfig(BaseModel):
50
+ enabled: bool = False
51
+ project_id: str = Field(
52
+ default="runsandbox-449400",
53
+ description="The GCP project ID to use for the experiment.",
54
+ )
55
+ image_registry: str | None = Field(
56
+ default=None,
57
+ description="The GCP image registry to use for the experiment images. If not set, will use the default GCP registry.",
58
+ )
59
+
60
+
61
+ class AWSConfig(BaseModel):
62
+ enabled: bool = False
63
+ image_registry: str | None = Field(
64
+ default=None,
65
+ description="The AWS image registry to use for the experiment images. If not set, will use the default AWS ECR registry.",
66
+ )
67
+
68
+
69
+ class CloudProviderConfig(BaseModel):
70
+ spot: bool = False
71
+ gcp: Optional[GCPConfig] = None
72
+ aws: Optional[AWSConfig] = None
73
+
74
+
75
+ class HiveConfig(BaseModel):
76
+ project_name: str = Field(
77
+ description="The name of the project. Must be all lowercase.",
78
+ )
79
+
80
+ token_path: str = Field(
81
+ default=os.path.expandvars("$HOME/.kube/config"),
82
+ description="Path to the auth token file, default to ~/.kube/config",
83
+ )
84
+
85
+ coordinator_config_name: str = Field(
86
+ default="default-coordinator-config",
87
+ description="The name of the coordinator config to use for the experiment. Default to 'default-coordinator-config'.",
88
+ )
89
+
90
+ platform: PlatformType = PlatformType.K8S
91
+
92
+ repo: RepoConfig
93
+ sandbox: SandboxConfig
94
+ wandb: Optional[WanDBConfig] = None
95
+
96
+ # cloud vendor configuration
97
+ cloud_provider: CloudProviderConfig
98
+
99
+ @field_validator("project_name")
100
+ def must_be_lowercase(cls, v):
101
+ if not v.islower():
102
+ raise ValueError("project_name must be all lowercase")
103
+ return v
104
+
105
+ def model_post_init(self, __context):
106
+ if (
107
+ self.cloud_provider.gcp
108
+ and self.cloud_provider.gcp.enabled
109
+ and not self.cloud_provider.gcp.image_registry
110
+ ):
111
+ self.cloud_provider.gcp.image_registry = (
112
+ f"gcr.io/{self.cloud_provider.gcp.project_id}/{self.project_name}"
113
+ )
114
+
115
+ if (
116
+ self.cloud_provider.aws
117
+ and self.cloud_provider.aws.enabled
118
+ and not self.cloud_provider.aws.image_registry
119
+ ):
120
+ self.cloud_provider.aws.image_registry = (
121
+ f"621302123805.dkr.ecr.eu-north-1.amazonaws.com/hiverge/{self.project_name}"
122
+ )
123
+
124
+
125
+ def load_config(file_path: str) -> HiveConfig:
126
+ """Load configuration from a YAML file."""
127
+ with open(file_path, "r") as file:
128
+ config_data = yaml.safe_load(file)
129
+ return HiveConfig(**config_data)
@@ -0,0 +1,216 @@
1
+ import argparse
2
+ import os
3
+ import subprocess
4
+
5
+ from rich.console import Console
6
+ from rich.text import Text
7
+
8
+ from hive_cli.config import load_config
9
+ from hive_cli.platform.k8s import K8sPlatform
10
+
11
+ PLATFORMS = {
12
+ "k8s": K8sPlatform,
13
+ # "on-prem": OnPremPlatform,
14
+ }
15
+
16
+
17
+ def init(args):
18
+ print("(Unimplemented) Initializing hive...")
19
+
20
+
21
+ def create_experiment(args):
22
+ BLUE = "\033[94m"
23
+ RESET = "\033[0m"
24
+
25
+ ascii_art = r"""
26
+ ███ █████ █████ ███
27
+ ░░░███ ░░███ ░░███ ░░░
28
+ ░░░███ ░███ ░███ ████ █████ █████ ██████
29
+ ░░░███ ░███████████ ░░███ ░░███ ░░███ ███░░███
30
+ ███░ ░███░░░░░███ ░███ ░███ ░███ ░███████
31
+ ███░ ░███ ░███ ░███ ░░███ ███ ░███░░░
32
+ ███░ █████ █████ █████ ░░█████ ░░██████
33
+ ░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░
34
+ """
35
+
36
+ print(f"{BLUE}{ascii_art}{RESET}")
37
+
38
+ config = load_config(args.config)
39
+ # Init the platform based on the config.
40
+ platform = PLATFORMS[config.platform.value](args.name, config.token_path)
41
+
42
+ platform.create(config=config)
43
+
44
+
45
+ def update_experiment(args):
46
+ config = load_config(args.config)
47
+ # Init the platform based on the config.
48
+ platform = PLATFORMS[config.platform.value](args.name, config.token_path)
49
+
50
+ platform.update(args.name, config=config)
51
+
52
+ console = Console()
53
+ msg = Text(f"Experiment {args.name} updated successfully.", style="bold green")
54
+ console.print(msg)
55
+
56
+
57
+ def delete_experiment(args):
58
+ config = load_config(args.config)
59
+
60
+ platform = PLATFORMS[args.platform](args.platform, config.token_path)
61
+ platform.delete(args.name)
62
+
63
+
64
+ def show_experiment(args):
65
+ config = load_config(args.config)
66
+
67
+ platform = PLATFORMS[args.platform](args.platform, config.token_path)
68
+ platform.show_experiments(args)
69
+
70
+
71
+ def edit(args):
72
+ editor = os.environ.get("EDITOR", "vim")
73
+ subprocess.run([editor, args.config])
74
+
75
+ console = Console()
76
+ msg = Text(args.config, style="bold magenta")
77
+ msg.append(" edited successfully.", style="bold green")
78
+ console.print(msg)
79
+
80
+
81
+ def show_dashboard(args):
82
+ config = load_config(args.config)
83
+ platform = PLATFORMS[args.platform](args.platform, config.token_path)
84
+ platform.show_dashboard(args)
85
+
86
+
87
+ def main():
88
+ parser = argparse.ArgumentParser(description="Hive CLI")
89
+ subparsers = parser.add_subparsers(dest="command", required=True)
90
+
91
+ # TODO:
92
+ # # init command
93
+ # parser_init = subparsers.add_parser("init", help="Initialize a repository")
94
+ # parser_init.set_defaults(func=init)
95
+
96
+ # create command
97
+ parser_create = subparsers.add_parser("create", help="Create resources")
98
+ create_subparsers = parser_create.add_subparsers(dest="create_target")
99
+
100
+ parser_create_exp = create_subparsers.add_parser(
101
+ "experiment", aliases=["exp"], help="Create a new experiment"
102
+ )
103
+ parser_create_exp.add_argument(
104
+ "name",
105
+ help="Name of the experiment, if it ends with '-', a timestamp will be appended. Example: 'exp-' will become 'exp-2023-10-01-123456'",
106
+ )
107
+ parser_create_exp.add_argument(
108
+ "-f",
109
+ "--config",
110
+ default=os.path.expandvars("$HOME/.hive/sandbox-config.yaml"),
111
+ help="Path to the config file, default to ~/.hive/sandbox-config.yaml",
112
+ )
113
+ parser_create_exp.set_defaults(func=create_experiment)
114
+
115
+ # TODO:
116
+ # update command
117
+ # parser_update = subparsers.add_parser("update", help="Update resources")
118
+ # update_subparsers = parser_update.add_subparsers(dest="update_target")
119
+
120
+ # parser_update_exp = update_subparsers.add_parser(
121
+ # "experiment", aliases=["exp"], help="Update an experiment"
122
+ # )
123
+ # parser_update_exp.add_argument("name", help="Name of the experiment")
124
+ # parser_update_exp.add_argument(
125
+ # "-f",
126
+ # "--config",
127
+ # default=os.path.expandvars("$HOME/.hive/sandbox-config.yaml"),
128
+ # help="Path to the config file, default to ~/.hive/sandbox-config.yaml",
129
+ # )
130
+ # parser_update_exp.set_defaults(func=update_experiment)
131
+
132
+ # delete command
133
+ parser_delete = subparsers.add_parser("delete", help="Delete resources")
134
+ delete_subparsers = parser_delete.add_subparsers(dest="delete_target")
135
+ parser_delete_exp = delete_subparsers.add_parser(
136
+ "experiment", aliases=["exp"], help="Delete an experiment"
137
+ )
138
+ parser_delete_exp.add_argument("name", help="Name of the experiment")
139
+ parser_delete_exp.add_argument(
140
+ "-p",
141
+ "--platform",
142
+ default="k8s",
143
+ choices=PLATFORMS.keys(),
144
+ help="Platform to use, k8s or on-prem, default to use k8s",
145
+ )
146
+ parser_delete_exp.add_argument(
147
+ "-f",
148
+ "--config",
149
+ default=os.path.expandvars("$HOME/.hive/sandbox-config.yaml"),
150
+ help="Path to the config file, default to ~/.hive/sandbox-config.yaml",
151
+ )
152
+ parser_delete_exp.set_defaults(func=delete_experiment)
153
+
154
+ # show command
155
+ parser_show = subparsers.add_parser("show", help="Show resources")
156
+ show_subparsers = parser_show.add_subparsers(dest="show_target")
157
+ parser_show_exp = show_subparsers.add_parser(
158
+ "experiments", aliases=["exp", "exps"], help="Show experiments"
159
+ )
160
+ parser_show_exp.add_argument(
161
+ "-p",
162
+ "--platform",
163
+ default="k8s",
164
+ choices=PLATFORMS.keys(),
165
+ help="Platform to use, k8s or on-prem, default to use k8s",
166
+ )
167
+ parser_show_exp.add_argument(
168
+ "-f",
169
+ "--config",
170
+ default=os.path.expandvars("$HOME/.hive/sandbox-config.yaml"),
171
+ help="Path to the config file, default to ~/.hive/sandbox-config.yaml",
172
+ )
173
+ parser_show_exp.set_defaults(func=show_experiment)
174
+
175
+ # edit command
176
+ parser_edit = subparsers.add_parser("edit", help="Edit Hive configuration")
177
+ edit_subparsers = parser_edit.add_subparsers(dest="edit_target")
178
+ parser_edit_config = edit_subparsers.add_parser(
179
+ "config", help="Edit the Hive configuration file"
180
+ )
181
+ parser_edit_config.add_argument(
182
+ "-f",
183
+ "--config",
184
+ default=os.path.expandvars("$HOME/.hive/sandbox-config.yaml"),
185
+ help="Path to the config file, defaults to ~/.hive/sandbox-config.yaml",
186
+ )
187
+ parser_edit_config.set_defaults(func=edit)
188
+
189
+ # dashboard command
190
+ parser_dashboard = subparsers.add_parser("dashboard", help="Open the Hive dashboard")
191
+ parser_dashboard.add_argument(
192
+ "--port",
193
+ default=8080,
194
+ type=int,
195
+ help="Port to run the dashboard on, default to 8080",
196
+ )
197
+ parser_dashboard.add_argument(
198
+ "-f",
199
+ "--config",
200
+ default=os.path.expandvars("$HOME/.hive/sandbox-config.yaml"),
201
+ help="Path to the config file, default to ~/.hive/sandbox-config.yaml",
202
+ )
203
+ parser_dashboard.add_argument(
204
+ "-p",
205
+ "--platform",
206
+ default="k8s",
207
+ choices=PLATFORMS.keys(),
208
+ help="Platform to use, k8s or on-prem, default to use k8s",
209
+ )
210
+ parser_dashboard.set_defaults(func=show_dashboard)
211
+
212
+ args = parser.parse_args()
213
+ if hasattr(args, "func"):
214
+ args.func(args)
215
+ else:
216
+ parser.print_help()
File without changes
@@ -0,0 +1,170 @@
1
+ import os
2
+ import shutil
3
+ import tempfile
4
+ from abc import ABC, abstractmethod
5
+ from pathlib import Path
6
+
7
+ from hive_cli.config import HiveConfig
8
+ from hive_cli.runtime.runtime import Runtime
9
+ from hive_cli.utils import git
10
+ from hive_cli.utils.image import build_image
11
+ from hive_cli.utils.logger import logger
12
+
13
+
14
+ class Platform(Runtime, ABC):
15
+ @abstractmethod
16
+ def create(self, config: HiveConfig):
17
+ pass
18
+
19
+ @abstractmethod
20
+ def update(self, name: str, config: HiveConfig):
21
+ pass
22
+
23
+ @abstractmethod
24
+ def delete(self, name: str):
25
+ pass
26
+
27
+ @abstractmethod
28
+ def login(self, args):
29
+ pass
30
+
31
+ @abstractmethod
32
+ def show_experiments(self, args):
33
+ pass
34
+
35
+ @abstractmethod
36
+ def show_dashboard(self, args):
37
+ pass
38
+
39
+ def __init__(self, name: str, token_path: str = None):
40
+ super().__init__(name, token_path)
41
+
42
+ # setup_environment function can be used to prepare the environment for the experiment,
43
+ # shared logic for both K8s and OnPrem platforms.
44
+ def setup_environment(self, config: HiveConfig) -> HiveConfig:
45
+ """
46
+ Set up the environment for the experiment.
47
+ This includes building the Docker image and preparing any necessary resources.
48
+
49
+ Args:
50
+ config (HiveConfig): The configuration for the experiment.
51
+
52
+ Returns:
53
+ HiveConfig: The updated configuration with the image name set.
54
+ """
55
+
56
+ logger.info(f"Setting up environment for experiment '{self.experiment_name}'")
57
+ logger.debug(f"The HiveConfig: {config}")
58
+
59
+ # Here you can add more setup logic, like initializing Kubernetes resources
60
+ # or configuring the environment based on the HiveConfig.
61
+ with tempfile.TemporaryDirectory(dir="./tmp") as temp_dir:
62
+ image_name = self.prepare_images(config, temp_dir, push=True)
63
+
64
+ # Populate related fields to the config, only allow to update here.
65
+ config.sandbox.image = image_name
66
+
67
+ logger.debug(f"The updated HiveConfig: {config}")
68
+ return config
69
+
70
+ def prepare_images(self, config: HiveConfig, temp_dir: str, push: bool = False) -> str:
71
+ """
72
+ Build the Docker image for the experiment.
73
+ If `push` is True, it will push the image to the registry.
74
+
75
+ Args:
76
+ config (HiveConfig): The configuration for the experiment.
77
+ temp_dir (str): The temporary directory to use for building the image.
78
+ push (bool): Whether to push the image to the registry.
79
+
80
+ Returns:
81
+ str: The name of the built image.
82
+ """
83
+
84
+ logger.debug(f"Preparing images for experiment '{self.experiment_name}' in {temp_dir}")
85
+
86
+ # TODO: refactor this part to use an image by default rather than build from the scratch.
87
+ shutil.copytree(
88
+ "./libs",
89
+ Path("./tmp") / temp_dir,
90
+ dirs_exist_ok=True,
91
+ )
92
+ dest = Path(temp_dir) / "repo"
93
+
94
+ git.clone_repo(config.repo.url, dest, config.repo.branch)
95
+ logger.debug(
96
+ f"Cloning repository {config.repo.url} to {dest}, the tree structure of the directory: {os.listdir('.')}, the tree structure of the {dest} directory: {os.listdir(dest)}"
97
+ )
98
+
99
+ if not (dest / "Dockerfile").exists():
100
+ logger.debug(f"No Dockerfile found in {dest}, generating one.")
101
+ # Generate Dockerfile for the experiment
102
+ generate_dockerfile(dest)
103
+
104
+ logger.debug(f"Building temporary repo image in {dest}")
105
+ # build the repository image first
106
+ build_image(
107
+ image="repo-image:latest",
108
+ context=dest,
109
+ dockerfile=dest / "Dockerfile",
110
+ # this is a temporary image, so we don't push it
111
+ push=False,
112
+ )
113
+
114
+ if config.cloud_provider.gcp and config.cloud_provider.gcp.enabled:
115
+ image_registry = config.cloud_provider.gcp.image_registry
116
+ elif config.cloud_provider.aws and config.cloud_provider.aws.enabled:
117
+ image_registry = config.cloud_provider.aws.image_registry
118
+ else:
119
+ raise ValueError("Unsupported cloud provider configuration. Please enable GCP or AWS.")
120
+
121
+ image_name = f"{image_registry}:{self.experiment_name}"
122
+
123
+ logger.debug(f"Building sandbox image {image_name} in {temp_dir} with push={push}")
124
+ # build the sandbox image
125
+ build_image(
126
+ image=image_name,
127
+ context=temp_dir,
128
+ dockerfile=f"{temp_dir}/Dockerfile",
129
+ push=push,
130
+ )
131
+
132
+ logger.debug(
133
+ f"Images {image_name} prepared for experiment '{self.experiment_name}' successfully."
134
+ )
135
+ return image_name
136
+
137
+
138
+ # copied from the original hiverge project.
139
+ def generate_dockerfile(dest: Path) -> None:
140
+ """Create a Dockerfile inside `dest`."""
141
+ lines = [
142
+ "FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim",
143
+ "",
144
+ "RUN apt-get update && apt-get install --no-install-recommends -y \\",
145
+ "cmake \\",
146
+ "build-essential \\",
147
+ "pkg-config \\",
148
+ "&& rm -rf /var/lib/apt/lists/*",
149
+ "",
150
+ "WORKDIR /app",
151
+ "",
152
+ "# Install sandbox server dependencies",
153
+ ]
154
+ if (dest / "pyproject.toml").exists():
155
+ lines.append("# Install repository dependencies from pyproject.toml")
156
+ lines.append("COPY pyproject.toml .")
157
+ lines.append("RUN uv pip install --system --requirement pyproject.toml")
158
+ elif (dest / "requirements.txt").exists():
159
+ lines.append("# Install repository dependencies from requirements.txt")
160
+ lines.append("COPY requirements.txt .")
161
+ lines.append("RUN uv pip install --system --requirement requirements.txt")
162
+
163
+ lines.extend(
164
+ [
165
+ "",
166
+ "# Copy server code and evaluation file",
167
+ "COPY . repo",
168
+ ]
169
+ )
170
+ (dest / "Dockerfile").write_text("\n".join(lines), encoding="utf-8")
@@ -0,0 +1,232 @@
1
+ import subprocess
2
+
3
+ from kubernetes import client
4
+ from kubernetes import config as k8s_config
5
+ from kubernetes.client.api_client import ApiClient
6
+ from kubernetes.client.rest import ApiException
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+ from rich.text import Text
10
+
11
+ from hive_cli.config import HiveConfig
12
+ from hive_cli.platform.base import Platform
13
+ from hive_cli.utils.logger import logger
14
+ from hive_cli.utils.time import humanize_time
15
+
16
+ GROUP = "core.hiverge.ai"
17
+ VERSION = "v1alpha1"
18
+ RESOURCE = "Experiment"
19
+ RESOURCE_PLURAL = "experiments"
20
+ # TODO: remove this once we support custom namespace
21
+ NAMESPACE = "default"
22
+
23
+
24
+ class K8sPlatform(Platform):
25
+ def __init__(self, name: str, token_path: str = None):
26
+ super().__init__(name, token_path)
27
+
28
+ k8s_config.load_kube_config(config_file=token_path)
29
+ self.client = client.CustomObjectsApi()
30
+
31
+ def create(self, config: HiveConfig):
32
+ logger.info(f"Creating experiment '{self.experiment_name}' on Kubernetes...")
33
+ config = self.setup_environment(config)
34
+ deploy("CREATE", self.client, self.experiment_name, config)
35
+
36
+ def update(self, name: str, config: HiveConfig):
37
+ logger.info(f"Updating experiment '{name}' on Kubernetes...")
38
+ deploy("UPDATE", self.client, name, config)
39
+
40
+ def delete(self, name: str):
41
+ logger.info(f"Deleting experiment '{name}' on Kubernetes...")
42
+ try:
43
+ # Attempt to delete the experiment by its name
44
+ self.client.delete_namespaced_custom_object(
45
+ group=GROUP,
46
+ version=VERSION,
47
+ namespace=NAMESPACE,
48
+ plural=RESOURCE_PLURAL,
49
+ name=name,
50
+ )
51
+ logger.info(f"Experiment '{name}' deleted successfully on Kubernetes.")
52
+ except ApiException as e:
53
+ logger.error(f"Failed to delete experiment '{name}' on Kubernetes: {e}")
54
+ except Exception as e:
55
+ logger.error(f"An unexpected error occurred while deleting experiment '{name}': {e}")
56
+
57
+ def login(self, args):
58
+ logger.info(f"Logging in to hive on {args.platform} platform...")
59
+
60
+ def show_experiments(self, args):
61
+ resp = self.client.list_namespaced_custom_object(
62
+ group=GROUP,
63
+ version=VERSION,
64
+ namespace=NAMESPACE,
65
+ plural=RESOURCE_PLURAL,
66
+ )
67
+
68
+ table = Table(show_header=True, header_style="bold", box=None, show_lines=False)
69
+ table.add_column("Name")
70
+ table.add_column("Status")
71
+ table.add_column("Sandboxes")
72
+ table.add_column("Age")
73
+
74
+ for item in resp.get("items", []):
75
+ metadata = item.get("metadata", {})
76
+ age = humanize_time(metadata.get("creationTimestamp"))
77
+ status = item.get("status", {}).get("phase", "Unknown")
78
+ replicas = item.get("status", {}).get("sandboxReplicas", 0)
79
+ unavailable_replicas = item.get("status", {}).get("sandboxUnavailableReplicas", 0)
80
+
81
+ table.add_row(
82
+ metadata.get("name", "Unknown"),
83
+ status,
84
+ f"{replicas - unavailable_replicas}/{replicas}",
85
+ age if age else "N/A",
86
+ )
87
+
88
+ console = Console()
89
+ console.print(table)
90
+
91
+ def show_dashboard(self, args):
92
+ resp = self.client.list_namespaced_custom_object(
93
+ group=GROUP,
94
+ version=VERSION,
95
+ namespace=NAMESPACE,
96
+ plural=RESOURCE_PLURAL,
97
+ )
98
+
99
+ for item in resp.get("items", []):
100
+ metadata = item.get("metadata", {})
101
+ name = metadata.get("name", "Unknown")
102
+
103
+ console = Console()
104
+ url = f"http://localhost:{args.port}"
105
+ msg = Text("Open Hive dashboard at ", style="bold green")
106
+ msg.append(url, style="bold magenta")
107
+ msg.append(" ...", style="dim")
108
+ console.print(msg)
109
+
110
+ commands = [
111
+ "kubectl",
112
+ f"--kubeconfig={self.token_path}",
113
+ "port-forward",
114
+ f"svc/{name}-dashboard-frontend",
115
+ f"{str(args.port)}:8080",
116
+ ]
117
+ process = subprocess.Popen(
118
+ commands, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
119
+ )
120
+ try:
121
+ process.wait()
122
+ except KeyboardInterrupt:
123
+ pass
124
+
125
+ return
126
+
127
+
128
+ def deploy(op: str, client: ApiClient, name: str, config: HiveConfig):
129
+ logger.info(f"Applying experiment '{name}' on Kubernetes...")
130
+
131
+ body = construct_experiment(name, NAMESPACE, config)
132
+
133
+ try:
134
+ if op == "CREATE":
135
+ resp = client.create_namespaced_custom_object(
136
+ group=GROUP, version=VERSION, namespace=NAMESPACE, plural=RESOURCE_PLURAL, body=body
137
+ )
138
+ logger.info(
139
+ f"Experiment '{name}' created successfully on Kubernetes with name {resp['metadata']['name']}."
140
+ )
141
+ # TODO: add validation for op, only replicas can be updated
142
+ elif op == "UPDATE":
143
+ current_exp = client.get_namespaced_custom_object(
144
+ group=GROUP, version=VERSION, namespace=NAMESPACE, plural=RESOURCE_PLURAL, name=name
145
+ )
146
+
147
+ # Populate some fields manually because they're generated in creation.
148
+ if body["spec"]["sandbox"].get("image") is None:
149
+ body["spec"]["sandbox"]["image"] = current_exp["spec"]["sandbox"]["image"]
150
+
151
+ resp = client.patch_namespaced_custom_object(
152
+ group=GROUP,
153
+ version=VERSION,
154
+ namespace=NAMESPACE,
155
+ plural=RESOURCE_PLURAL,
156
+ name=name,
157
+ body=body,
158
+ )
159
+ logger.info(
160
+ f"Experiment '{name}' updated successfully on Kubernetes with name {resp['metadata']['name']}."
161
+ )
162
+ else:
163
+ raise ValueError(
164
+ f"Unsupported operation: {op}. Supported operations are 'CREATE' and 'UPDATE'."
165
+ )
166
+ except ApiException as e:
167
+ logger.error(f"Failed to deploy experiment '{name}' on Kubernetes: {e}")
168
+ except Exception as e:
169
+ logger.error(f"An unexpected error occurred while deploying experiment '{name}': {e}")
170
+
171
+
172
+ def construct_experiment(name: str, namespace: str, config: HiveConfig) -> dict:
173
+ """
174
+ Constructs a Kubernetes custom resource definition (CRD) for an experiment.
175
+
176
+ Args:
177
+ name (str): The name of the experiment.
178
+ namespace (str): The Kubernetes namespace where the experiment will be deployed.
179
+
180
+ Returns:
181
+ dict: A dictionary representing the CRD for the experiment.
182
+ """
183
+
184
+ if config.cloud_provider.gcp and config.cloud_provider.gcp.enabled:
185
+ cloud_provider_name = "gcp"
186
+ elif config.cloud_provider.aws and config.cloud_provider.aws.enabled:
187
+ cloud_provider_name = "aws"
188
+ else:
189
+ cloud_provider_name = "unknown"
190
+
191
+ if config.sandbox.envs is not None:
192
+ envs = [env.model_dump() for env in config.sandbox.envs]
193
+ else:
194
+ envs = None
195
+
196
+ if config.sandbox.resources is not None:
197
+ resources = config.sandbox.resources.model_dump()
198
+ else:
199
+ resources = {}
200
+
201
+ result = {
202
+ "apiVersion": f"{GROUP}/{VERSION}",
203
+ "kind": RESOURCE,
204
+ "metadata": {
205
+ "name": name,
206
+ "namespace": namespace,
207
+ },
208
+ "spec": {
209
+ "projectName": config.project_name,
210
+ "coordinatorConfigName": config.coordinator_config_name,
211
+ "sandbox": {
212
+ "image": config.sandbox.image,
213
+ "replicas": config.sandbox.replicas,
214
+ "timeout": config.sandbox.timeout,
215
+ "resources": resources,
216
+ "envs": envs,
217
+ "preprocessor": config.sandbox.pre_processor,
218
+ },
219
+ "repo": {
220
+ "url": config.repo.url,
221
+ "branch": config.repo.branch,
222
+ "evaluationScript": config.repo.evaluation_script,
223
+ "evolveFilesAndRanges": config.repo.evolve_files_and_ranges,
224
+ },
225
+ "cloudProvider": {
226
+ "spot": config.cloud_provider.spot,
227
+ "name": cloud_provider_name,
228
+ },
229
+ },
230
+ }
231
+
232
+ return result
@@ -0,0 +1,26 @@
1
+ from hive_cli.config import HiveConfig
2
+
3
+ from .base import Platform
4
+
5
+
6
+ class OnPremPlatform(Platform):
7
+ def __init__(self, name: str):
8
+ super().__init__(name)
9
+
10
+ def create(self, config: HiveConfig):
11
+ print(f"Creating hive on-premise with name: {self.experiment_name} and config: {config}")
12
+
13
+ def update(self, name: str, config: HiveConfig):
14
+ print(f"Updating hive on-premise with name: {name} and config: {config}")
15
+
16
+ def delete(self, name: str):
17
+ print("Deleting hive on-premise...")
18
+
19
+ def login(self, args):
20
+ print("Logging in to hive on-premise...")
21
+
22
+ def show_experiments(self, args):
23
+ print("Showing experiments on-premise...")
24
+
25
+ def show_dashboard(self, args):
26
+ print("Showing dashboard on-premise...")
File without changes
@@ -0,0 +1,28 @@
1
+ import hashlib
2
+ from datetime import datetime, timezone
3
+
4
+
5
+ class Runtime:
6
+ def __init__(self, name: str, token_path: str = None):
7
+ """Initialize the Runtime with a name.
8
+ This can be used to set up any necessary runtime configurations.
9
+ """
10
+
11
+ self.token_path = token_path
12
+ self.experiment_name = generate_experiment_name(name)
13
+
14
+
15
+ def generate_experiment_name(base_name: str) -> str:
16
+ """
17
+ Generate a unique experiment name based on the base name and current timestamp.
18
+ If the base name ends with '-', it will be suffixed with a timestamp.
19
+ """
20
+
21
+ experiment_name = base_name
22
+
23
+ if base_name.endswith("-"):
24
+ timestamp = str(int(datetime.now(timezone.utc).timestamp()))
25
+ unique_hash = hashlib.sha1(timestamp.encode()).hexdigest()[:12]
26
+ experiment_name = f"{base_name}{unique_hash}"
27
+
28
+ return experiment_name
File without changes
@@ -0,0 +1,25 @@
1
+ import os
2
+ import shutil
3
+ from pathlib import Path
4
+
5
+ import git
6
+
7
+
8
+ def clone_repo(repo_dir: str, output_dir: str, branch: str = "main") -> None:
9
+ """Clone a repository into the output directory."""
10
+ dest = Path(output_dir)
11
+ dest.mkdir(parents=True, exist_ok=True)
12
+ if repo_dir.startswith("https://"):
13
+ token = os.getenv("GITHUB_TOKEN")
14
+ if token:
15
+ # Inject token into the URL for authentication
16
+ repo_dir = repo_dir.replace("https://", f"https://x-access-token:{token}@")
17
+ repo = git.Repo.clone_from(repo_dir, dest)
18
+ repo.git.checkout(branch)
19
+ else: # We assume `repo_dir` is a directory in this machine.
20
+ repo_path = Path(repo_dir).resolve()
21
+ if not repo_path.exists():
22
+ raise FileNotFoundError(f"Repository directory {repo_dir} does not exist")
23
+ if not repo_path.is_dir():
24
+ raise NotADirectoryError(f"{repo_dir} is not a directory")
25
+ shutil.copytree(repo_path, dest, dirs_exist_ok=True)
@@ -0,0 +1,44 @@
1
+ import logging
2
+ import subprocess
3
+
4
+ from hive_cli.utils.logger import logger
5
+
6
+
7
+ def build_image(
8
+ image: str,
9
+ platforms: str = "linux/amd64,linux/arm64",
10
+ context: str = ".",
11
+ dockerfile: str = "Dockerfile",
12
+ push: bool = False,
13
+ ):
14
+ cmd = [
15
+ "docker",
16
+ "buildx",
17
+ "build",
18
+ "--platform",
19
+ platforms,
20
+ "--file",
21
+ dockerfile,
22
+ "--tag",
23
+ image,
24
+ "--load",
25
+ context,
26
+ ]
27
+ if push:
28
+ cmd.append("--push")
29
+
30
+ try:
31
+ if logger.isEnabledFor(logging.DEBUG):
32
+ capture_output = False
33
+ else:
34
+ capture_output = True
35
+
36
+ subprocess.run(
37
+ cmd,
38
+ check=True,
39
+ capture_output=capture_output,
40
+ text=True,
41
+ )
42
+ except subprocess.CalledProcessError as e:
43
+ print("Build STDERR:\n", e.stderr)
44
+ raise
@@ -0,0 +1,32 @@
1
+ import logging
2
+ import os
3
+
4
+ from dotenv import load_dotenv
5
+ from rich.logging import RichHandler
6
+
7
+ # load the .env file.
8
+ load_dotenv()
9
+
10
+
11
+ def setup_logging(name: str) -> logging.Logger:
12
+ """
13
+ Set up a logger with the specified name and logging level.
14
+ If no level is provided, it defaults to INFO.
15
+ """
16
+
17
+ log_level = os.getenv("LOG_LEVEL", "INFO").upper() # Default log level
18
+
19
+ logging.basicConfig(
20
+ level=log_level,
21
+ format="%(name)s %(message)s",
22
+ handlers=[
23
+ RichHandler(
24
+ show_time=True, show_level=True, show_path=False, markup=True, rich_tracebacks=True
25
+ )
26
+ ],
27
+ )
28
+
29
+ return logging.getLogger(name)
30
+
31
+
32
+ logger = setup_logging("hive-cli")
@@ -0,0 +1,19 @@
1
+ import datetime
2
+
3
+
4
+ def humanize_time(timestamp: str) -> str:
5
+ creation_time = datetime.datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%SZ").replace(
6
+ tzinfo=datetime.UTC
7
+ )
8
+ t = datetime.datetime.now(datetime.UTC) - creation_time
9
+
10
+ if t.days > 0:
11
+ age = f"{t.days}d"
12
+ elif t.seconds >= 3600:
13
+ age = f"{t.seconds // 3600}h"
14
+ elif t.seconds >= 60:
15
+ age = f"{t.seconds // 60}m"
16
+ else:
17
+ age = f"{t.seconds}s"
18
+
19
+ return age
@@ -0,0 +1,101 @@
1
+ Metadata-Version: 2.4
2
+ Name: hiverge-cli
3
+ Version: 0.1.0
4
+ Summary: Universal Command Line Interface for Hive agent.
5
+ Author: Hiverge Team
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.7
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: PyYAML>=5.1
13
+ Requires-Dist: pydantic>=1.8.2
14
+ Requires-Dist: gitpython>=3.1.24
15
+ Requires-Dist: python-dotenv>=0.19.2
16
+ Requires-Dist: rich>=12.5.1
17
+ Requires-Dist: kubernetes>=32.0.0
18
+ Provides-Extra: test
19
+ Provides-Extra: dev
20
+ Requires-Dist: ruff>=0.12; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # Hive-CLI
24
+
25
+ Hive-CLI is a command-line interface for managing and deploying Hive agent and experiments on Kubernetes and other platforms.
26
+
27
+ ```bash
28
+ ███ █████ █████ ███
29
+ ░░░███ ░░███ ░░███ ░░░
30
+ ░░░███ ░███ ░███ ████ █████ █████ ██████
31
+ ░░░███ ░███████████ ░░███ ░░███ ░░███ ███░░███
32
+ ███░ ░███░░░░░███ ░███ ░███ ░███ ░███████
33
+ ███░ ░███ ░███ ░███ ░░███ ███ ░███░░░
34
+ ███░ █████ █████ █████ ░░█████ ░░██████
35
+ ░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░
36
+ ```
37
+
38
+ ## Installation
39
+
40
+ ### Install via pip (Not-Available Yet)
41
+
42
+ ```bash
43
+ pip install hive-cli
44
+ ```
45
+
46
+ ### Install from source
47
+
48
+ ```bash
49
+ source start.sh
50
+ ```
51
+
52
+ ## How to run
53
+
54
+ **Note**: Hive-CLI reads the configuration from a yaml file, by default it will look for the `~/.hive/sandbox-config.yaml`. You can also specify a different configuration file using the `-f` option. Refer to the [config.yaml](./config.yaml) for examples.
55
+
56
+ Below we assume that you have a `~/.hive/sandbox-config.yaml` file.
57
+
58
+ ### Edit the experiment
59
+
60
+ `Edit` command will open the configuration file in your default editor (e.g., vim, nano, etc.) for you to modify the experiment configuration. You can also specify a different editor using the `EDITOR` environment variable, by default it will use `vim`.
61
+
62
+ ```bash
63
+ hive edit config
64
+ ```
65
+
66
+ ### Create an experiment
67
+
68
+ ```bash
69
+ hive create exp my-experiment
70
+ ```
71
+
72
+ ### List experiments
73
+
74
+ ```bash
75
+ hive show exps
76
+ ```
77
+
78
+ ### Visit Dashboard
79
+
80
+ ```bash
81
+ hive dashboard
82
+ ```
83
+
84
+ ### Delete an experiment
85
+
86
+
87
+ ```bash
88
+ hive delete exp my-experiment
89
+ ```
90
+
91
+ ### More
92
+
93
+ See `hive -h` for more details.
94
+
95
+ ## Development
96
+
97
+ **Note**: Hive-CLI will read the `.env` file to load logging configurations. Refer to the `.env.example` file for examples.
98
+
99
+ ### Debugging
100
+
101
+ Change the log level in `.env` file to `DEBUG` to see more detailed logs.
@@ -0,0 +1,24 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/hive_cli/__init__.py
5
+ src/hive_cli/config.py
6
+ src/hive_cli/main.py
7
+ src/hive_cli/platform/__init__.py
8
+ src/hive_cli/platform/base.py
9
+ src/hive_cli/platform/k8s.py
10
+ src/hive_cli/platform/onprem.py
11
+ src/hive_cli/runtime/__init__.py
12
+ src/hive_cli/runtime/runtime.py
13
+ src/hive_cli/utils/__init__.py
14
+ src/hive_cli/utils/git.py
15
+ src/hive_cli/utils/image.py
16
+ src/hive_cli/utils/logger.py
17
+ src/hive_cli/utils/time.py
18
+ src/hiverge_cli.egg-info/PKG-INFO
19
+ src/hiverge_cli.egg-info/SOURCES.txt
20
+ src/hiverge_cli.egg-info/dependency_links.txt
21
+ src/hiverge_cli.egg-info/entry_points.txt
22
+ src/hiverge_cli.egg-info/requires.txt
23
+ src/hiverge_cli.egg-info/top_level.txt
24
+ tests/test_main.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ hive = hive_cli.main:main
@@ -0,0 +1,11 @@
1
+ PyYAML>=5.1
2
+ pydantic>=1.8.2
3
+ gitpython>=3.1.24
4
+ python-dotenv>=0.19.2
5
+ rich>=12.5.1
6
+ kubernetes>=32.0.0
7
+
8
+ [dev]
9
+ ruff>=0.12
10
+
11
+ [test]
@@ -0,0 +1,2 @@
1
+ def test_main():
2
+ pass