makerrepo-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.
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ import click
2
+ from click.core import Context
3
+
4
+ CMD_ALIAS_MAP = {
5
+ "ls": "list",
6
+ }
7
+
8
+
9
+ class AliasedGroup(click.Group):
10
+ def get_command(self, ctx: Context, cmd_name: str):
11
+ if cmd_name in CMD_ALIAS_MAP:
12
+ cmd_name = CMD_ALIAS_MAP[cmd_name]
13
+ rv = click.Group.get_command(self, ctx, cmd_name)
14
+ if rv is not None:
15
+ return rv
16
+ matches = [x for x in self.list_commands(ctx) if x.startswith(cmd_name)]
17
+ if not matches:
18
+ return None
19
+ elif len(matches) == 1:
20
+ return click.Group.get_command(self, ctx, matches[0])
21
+ ctx.fail(f"Too many matches: {', '.join(sorted(matches))}")
22
+
23
+ def resolve_command(self, ctx: Context, args: list[str]):
24
+ # always return the full command name
25
+ _, cmd, args = super().resolve_command(ctx, args)
26
+ return cmd.name, cmd, args
File without changes
@@ -0,0 +1,231 @@
1
+ import asyncio
2
+ import base64
3
+ import logging
4
+ import os
5
+ import pathlib
6
+ import uuid
7
+ from typing import Any
8
+
9
+ from playwright.async_api import async_playwright
10
+ from playwright.async_api import Browser
11
+ from playwright.async_api import BrowserContext
12
+ from playwright.async_api import Page
13
+
14
+
15
+ DATA_FOLDER = pathlib.Path(__file__).parent.parent.parent / "data"
16
+ DEFAULT_CONFIG = {
17
+ # Display options
18
+ "cadWidth": 1200,
19
+ "height": 1200,
20
+ "treeWidth": 0,
21
+ "theme": "light",
22
+ "glass": True,
23
+ "tools": False,
24
+ # Render options
25
+ "ambientIntensity": 1.0,
26
+ "directIntensity": 1.1,
27
+ "metalness": 0.3,
28
+ "roughness": 0.65,
29
+ "edgeColor": 0x707070,
30
+ # Viewer options
31
+ "ortho": True,
32
+ "control": "trackball",
33
+ "up": "Z",
34
+ }
35
+ DEFAULT_ARGS = (
36
+ "--no-sandbox",
37
+ "--disable-setuid-sandbox",
38
+ "--disable-dev-shm-usage",
39
+ "--use-gl=angle",
40
+ "--use-angle=swiftshader",
41
+ "--enable-webgl",
42
+ "--ignore-gpu-blocklist",
43
+ "--enable-unsafe-swiftshader",
44
+ )
45
+
46
+
47
+ class CADViewerService:
48
+ """Service class for loading and interacting with CAD viewer HTML using Playwright."""
49
+
50
+ def __init__(
51
+ self,
52
+ data_dir: pathlib.Path | None = None,
53
+ chrome_executable_path: pathlib.Path | None = None,
54
+ args: tuple[str, ...] | None = None,
55
+ logger: logging.Logger | None = None,
56
+ ):
57
+ if data_dir is None:
58
+ data_dir = DATA_FOLDER
59
+ self.data_dir = pathlib.Path(data_dir)
60
+ self.chrome_executable_path = chrome_executable_path
61
+ if self.chrome_executable_path is None:
62
+ self.chrome_executable_path = os.environ.get(
63
+ "PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH"
64
+ )
65
+ self.args = args
66
+ if self.args is None:
67
+ self.args = DEFAULT_ARGS
68
+ self.html_file = self.data_dir / "cad_viewer.html"
69
+ self.browser: Browser | None = None
70
+ self.context: BrowserContext | None = None
71
+ self.page: Page | None = None
72
+ self._playwright = None
73
+ self.logger = logger or logging.getLogger(__name__)
74
+
75
+ async def start(self):
76
+ """Start the Playwright browser and load the HTML file."""
77
+ self._playwright = await async_playwright().start()
78
+ self.logger.info(
79
+ "Starting CAD viewer service with executable_path=%s, args=%s",
80
+ self.chrome_executable_path,
81
+ self.args,
82
+ )
83
+ self.browser = await self._playwright.chromium.launch(
84
+ headless=True,
85
+ args=self.args,
86
+ executable_path=self.chrome_executable_path,
87
+ )
88
+ self.context = await self.browser.new_context()
89
+ self.page = await self.context.new_page()
90
+
91
+ # Set up message handler for postMessage from the page
92
+ async def handle_console(msg):
93
+ self.logger.info("Console: %s", msg)
94
+
95
+ self.page.on("console", handle_console)
96
+
97
+ # Load the HTML file
98
+ html_path = f"file://{self.html_file.absolute()}"
99
+ await self.page.goto(html_path, wait_until="networkidle")
100
+
101
+ async def load_cad_data(
102
+ self, data: dict[str, Any], config: dict[str, Any] | None = None
103
+ ):
104
+ """
105
+ Load CAD model data into the viewer.
106
+
107
+ Args:
108
+ data: CAD model data dictionary
109
+ config: Optional configuration dictionary
110
+ """
111
+ if self.page is None:
112
+ raise RuntimeError("Service not started. Call start() first.")
113
+
114
+ if config is None:
115
+ config = DEFAULT_CONFIG
116
+
117
+ message = {
118
+ "model": data["data"],
119
+ "config": config,
120
+ }
121
+
122
+ await self.page.evaluate(
123
+ """(data) => {
124
+ if (window.viewer && window.viewer.clear) {
125
+ window.viewer.clear();
126
+ }
127
+ window.loadModel(data);
128
+ }""",
129
+ message,
130
+ )
131
+
132
+ # Wait for viewer to be ready and data to be processed
133
+ await asyncio.sleep(1.0)
134
+ await self.page.wait_for_function(
135
+ "window.viewer !== null && window.viewer !== undefined",
136
+ timeout=5 * 1_000,
137
+ )
138
+
139
+ async def take_screenshot(self) -> bytes:
140
+ """
141
+ Take a screenshot of the CAD viewer.
142
+
143
+ """
144
+ if self.page is None:
145
+ raise RuntimeError("Service not started. Call start() first.")
146
+
147
+ task_id = uuid.uuid4().hex
148
+ # Call getImage directly and wait for the promise to resolve
149
+ result = await self.page.evaluate(
150
+ """
151
+ async (filename) => {
152
+ if (!window.getImage) {
153
+ throw new Error('getImage function not available');
154
+ }
155
+ return await window.getImage(filename);
156
+ }
157
+ """,
158
+ task_id,
159
+ )
160
+
161
+ if not isinstance(result, dict):
162
+ raise RuntimeError(f"Invalid screenshot response type: {type(result)}")
163
+
164
+ if "dataUrl" not in result:
165
+ raise RuntimeError(f"Unexpected screenshot response: {result}")
166
+
167
+ data_url = result["dataUrl"]
168
+ if not data_url.startswith("data:image"):
169
+ raise RuntimeError(f"Invalid screenshot data format: {data_url[:50]}...")
170
+
171
+ base64_data = data_url.split(",")[1]
172
+ return base64.b64decode(base64_data)
173
+
174
+ async def stop(self):
175
+ """Stop the browser and clean up resources."""
176
+ if self.page:
177
+ await self.page.close()
178
+ if self.context:
179
+ await self.context.close()
180
+ if self.browser:
181
+ await self.browser.close()
182
+ if self._playwright:
183
+ await self._playwright.stop()
184
+
185
+ self.page = None
186
+ self.context = None
187
+ self.browser = None
188
+ self._playwright = None
189
+
190
+ async def __aenter__(self):
191
+ """Async context manager entry."""
192
+ await self.start()
193
+ return self
194
+
195
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
196
+ """Async context manager exit."""
197
+ await self.stop()
198
+
199
+
200
+ async def main():
201
+ """Download model file and take a screenshot."""
202
+ import httpx
203
+
204
+ logger = logging.getLogger(__name__)
205
+ model_url = "https://makerrepo.com/r/fangpenlin/open-models/artifact/master/5081ffa2-d61f-4925-87e3-573ec291b53c/model.json"
206
+ output_path = pathlib.Path("model_screenshot.png")
207
+
208
+ logger.info("Downloading model from %s...", model_url)
209
+ async with httpx.AsyncClient(follow_redirects=True) as client:
210
+ response = await client.get(model_url)
211
+ response.raise_for_status()
212
+ model_data = response.json()
213
+
214
+ logger.info("Model downloaded successfully")
215
+ logger.info("Starting CAD viewer service...")
216
+
217
+ async with CADViewerService(logger=logger) as viewer:
218
+ logger.info("Loading CAD model data...")
219
+ await viewer.load_cad_data(model_data)
220
+
221
+ logger.info("Taking screenshot...")
222
+ screenshot_bytes = await viewer.take_screenshot()
223
+
224
+ # Save screenshot to file
225
+ output_path.write_bytes(screenshot_bytes)
226
+ logger.info("Screenshot saved to %s", output_path.absolute())
227
+
228
+
229
+ if __name__ == "__main__":
230
+ logging.basicConfig(level=logging.INFO)
231
+ asyncio.run(main())
@@ -0,0 +1,11 @@
1
+ from ..aliase import AliasedGroup
2
+ from ..cli import cli as root_cli
3
+
4
+
5
+ @root_cli.group(
6
+ name="artifacts",
7
+ help="Operations for artifacts.",
8
+ cls=AliasedGroup,
9
+ )
10
+ def cli():
11
+ pass
@@ -0,0 +1,157 @@
1
+ import logging
2
+ import pathlib
3
+
4
+ import click
5
+ import rich
6
+ from mr.artifacts.registry import collect
7
+ from mr.artifacts.registry import Registry
8
+ from mr.artifacts.utils import load_module
9
+ from rich import box
10
+ from rich.markup import escape
11
+ from rich.padding import Padding
12
+ from rich.table import Table
13
+
14
+ from ..environment import Environment
15
+ from ..environment import pass_env
16
+ from .cli import cli
17
+ from .utils import convert
18
+
19
+ logger = logging.getLogger(__name__)
20
+ TABLE_HEADER_STYLE = "yellow"
21
+ TABLE_COLUMN_STYLE = "cyan"
22
+
23
+
24
+ def collect_artifacts(module_spec: str) -> Registry:
25
+ registry = collect([load_module(module_spec)])
26
+ if not registry.artifacts:
27
+ logger.error("No artifacts found")
28
+ return registry
29
+
30
+
31
+ @cli.command(name="list", help="List artifacts")
32
+ @click.argument("MODULE")
33
+ @pass_env
34
+ def list_artifacts(env: Environment, module: str):
35
+ registry = collect_artifacts(module)
36
+
37
+ env.logger.info(
38
+ "Listing artifacts for [blue]%s[/]",
39
+ module,
40
+ extra={"markup": True, "highlighter": None},
41
+ )
42
+
43
+ table = Table(
44
+ title="Artifacts",
45
+ box=box.SIMPLE,
46
+ header_style=TABLE_HEADER_STYLE,
47
+ expand=True,
48
+ )
49
+ table.add_column("Module", style=TABLE_COLUMN_STYLE)
50
+ table.add_column("Name", style=TABLE_COLUMN_STYLE)
51
+ table.add_column("Sample", style=TABLE_COLUMN_STYLE)
52
+ for module, artifacts in registry.artifacts.items():
53
+ for i, (name, artifact) in enumerate(artifacts.items()):
54
+ table.add_row(
55
+ escape(module) if i == 0 else "", escape(name), str(artifact.sample)
56
+ )
57
+ rich.print(Padding(table, (1, 0, 0, 4)))
58
+
59
+
60
+ @cli.command(help="View artifact")
61
+ @click.argument("MODULE")
62
+ @click.argument("ARTIFACTS", nargs=-1)
63
+ @click.option(
64
+ "-p", "--port", help="OCP Viewer port to send the model data to", default=3939
65
+ )
66
+ @pass_env
67
+ def view(env: Environment, module: str, artifacts: tuple[str, ...], port: int):
68
+ registry = collect_artifacts(module)
69
+ if not artifacts:
70
+ target_artifact = list(list(registry.artifacts.values())[0].values())[0]
71
+ logger.info(
72
+ "No artifacts provided, use the first one %s/%s",
73
+ target_artifact.module,
74
+ target_artifact.name,
75
+ )
76
+ target_artifacts = [target_artifact]
77
+ else:
78
+ if len(registry.artifacts) > 1:
79
+ raise ValueError("Unexpected more than one modules found")
80
+ module_artifacts = list(registry.artifacts.values())[0]
81
+ target_artifacts = [
82
+ module_artifacts[artifact_name] for artifact_name in artifacts
83
+ ]
84
+
85
+ # TODO: this is going to be a bit slow, provide a progress bar & cache?
86
+ realized_artifacts = [artifact.func() for artifact in target_artifacts]
87
+ # defer the import to make testing mocking much easier
88
+ from ocp_vscode import show
89
+
90
+ # TODO: pass in some args
91
+ show(realized_artifacts, port=port)
92
+
93
+
94
+ @cli.command(help="Export artifact")
95
+ @click.argument("MODULE")
96
+ @pass_env
97
+ def export(env: Environment, module: str):
98
+ registry = collect_artifacts(module)
99
+ # TODO:
100
+
101
+
102
+ @cli.command(name="snapshot", help="Capture a snapshot from artifacts")
103
+ @click.argument("MODULE")
104
+ @click.argument("ARTIFACTS", nargs=-1)
105
+ @click.option(
106
+ "-o",
107
+ "--output",
108
+ help="Output image file path",
109
+ default="snapshot.png",
110
+ type=click.Path(path_type=pathlib.Path),
111
+ )
112
+ @pass_env
113
+ def snapshot(
114
+ env: Environment, module: str, artifacts: tuple[str, ...], output: pathlib.Path
115
+ ):
116
+ """Capture a screenshot from artifacts."""
117
+ import asyncio
118
+
119
+ from .capture_image import CADViewerService
120
+
121
+ registry = collect_artifacts(module)
122
+ if not artifacts:
123
+ target_artifact = list(list(registry.artifacts.values())[0].values())[0]
124
+ env.logger.info(
125
+ "No artifacts provided, use the first one %s/%s",
126
+ target_artifact.module,
127
+ target_artifact.name,
128
+ )
129
+ target_artifacts = [target_artifact]
130
+ else:
131
+ if len(registry.artifacts) > 1:
132
+ raise ValueError("Unexpected more than one modules found")
133
+ module_artifacts = list(registry.artifacts.values())[0]
134
+ target_artifacts = [
135
+ module_artifacts[artifact_name] for artifact_name in artifacts
136
+ ]
137
+
138
+ # Realize artifacts
139
+ realized_artifacts = [artifact.func() for artifact in target_artifacts]
140
+
141
+ # Convert to model data format using convert from utils
142
+ model_data = convert(realized_artifacts)
143
+
144
+ async def capture_snapshot():
145
+ env.logger.info("Starting CAD viewer service...")
146
+ async with CADViewerService(logger=env.logger) as viewer:
147
+ env.logger.info("Loading CAD model data...")
148
+ await viewer.load_cad_data(model_data.model_dump(mode="json"))
149
+
150
+ env.logger.info("Taking screenshot...")
151
+ screenshot_bytes = await viewer.take_screenshot()
152
+
153
+ # Save screenshot to file
154
+ output.write_bytes(screenshot_bytes)
155
+ env.logger.info("Screenshot saved to %s", output.absolute())
156
+
157
+ asyncio.run(capture_snapshot())
@@ -0,0 +1,72 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class Buffer(BaseModel):
7
+ shape: list[int]
8
+ dtype: str
9
+ buffer: str
10
+ codec: str
11
+
12
+
13
+ class Instance(BaseModel):
14
+ vertices: Buffer
15
+ triangles: Buffer
16
+ normals: Buffer
17
+ edges: Buffer
18
+ obj_vertices: Buffer
19
+ face_types: Buffer
20
+ edge_types: Buffer
21
+ triangles_per_face: Buffer
22
+ segments_per_edge: Buffer
23
+
24
+
25
+ class BoundingBox(BaseModel):
26
+ xmin: float
27
+ xmax: float
28
+ ymin: float
29
+ ymax: float
30
+ zmin: float
31
+ zmax: float
32
+
33
+
34
+ class ShapeRef(BaseModel):
35
+ ref: int
36
+
37
+
38
+ class Part(BaseModel):
39
+ id: str
40
+ type: str
41
+ subtype: str
42
+ name: str
43
+ shape: ShapeRef
44
+ state: list[int]
45
+ color: str
46
+ alpha: float
47
+ texture: Any | None = None
48
+ loc: list[list[float]]
49
+ renderback: bool
50
+ accuracy: Any | None = None
51
+ bb: BoundingBox | None = None
52
+
53
+
54
+ class Shapes(BaseModel):
55
+ version: int
56
+ parts: list[Part]
57
+ loc: list[list[float]]
58
+ name: str
59
+ id: str
60
+ normal_len: int
61
+ bb: BoundingBox
62
+
63
+
64
+ class OcpData(BaseModel):
65
+ instances: list[Instance]
66
+ shapes: Shapes
67
+
68
+
69
+ class OcpPayload(BaseModel):
70
+ data: OcpData
71
+ type: str
72
+ count: int
@@ -0,0 +1,31 @@
1
+ from ocp_tessellate import OcpGroup
2
+ from ocp_tessellate.convert import tessellate_group
3
+ from ocp_tessellate.convert import to_ocpgroup
4
+ from ocp_tessellate.utils import numpy_to_buffer_json
5
+
6
+ from .ocp_data_types import OcpData
7
+ from .ocp_data_types import OcpPayload
8
+
9
+
10
+ def convert(*cad_objs, names=None) -> OcpPayload:
11
+ part_group, instances = to_ocpgroup(
12
+ *cad_objs,
13
+ names=names,
14
+ )
15
+ if len(part_group.objects) == 1 and isinstance(part_group.objects[0], OcpGroup):
16
+ loc = part_group.loc
17
+ part_group = part_group.objects[0]
18
+ part_group.loc = loc * part_group.loc
19
+ instances, shapes, mapping = tessellate_group(
20
+ group=part_group,
21
+ instances=instances,
22
+ )
23
+
24
+ data = numpy_to_buffer_json(
25
+ dict(instances=instances, shapes=shapes),
26
+ )
27
+ return OcpPayload(
28
+ data=OcpData.model_validate(data),
29
+ type="data",
30
+ count=part_group.count_shapes(),
31
+ )
@@ -0,0 +1,57 @@
1
+ import logging
2
+ import os
3
+
4
+ import click
5
+ from rich.logging import RichHandler
6
+
7
+ from .environment import Environment
8
+ from .environment import LOG_LEVEL_MAP
9
+ from .environment import LogLevel
10
+ from .environment import pass_env
11
+
12
+
13
+ @click.group(help="Command line tools for MakerRepo")
14
+ @click.option(
15
+ "-l",
16
+ "--log-level",
17
+ type=click.Choice(
18
+ list(map(lambda key: key.value, LOG_LEVEL_MAP.keys())), case_sensitive=False
19
+ ),
20
+ default=lambda: os.environ.get("LOG_LEVEL", "INFO"),
21
+ )
22
+ @click.option(
23
+ "--log-format",
24
+ type=str,
25
+ default="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
26
+ help="logging format (only used when rich log is disabled)",
27
+ )
28
+ @click.option(
29
+ "--disable-rich-log",
30
+ is_flag=True,
31
+ help="disable rich log handler",
32
+ )
33
+ @click.version_option(prog_name="mr", package_name="mr")
34
+ @pass_env
35
+ def cli(
36
+ env: Environment,
37
+ log_level: str,
38
+ log_format: str,
39
+ disable_rich_log: bool,
40
+ ):
41
+ env.log_level = LogLevel(log_level)
42
+
43
+ if disable_rich_log:
44
+ logging.basicConfig(
45
+ level=LOG_LEVEL_MAP[env.log_level],
46
+ format=log_format,
47
+ force=True,
48
+ )
49
+ else:
50
+ FORMAT = "%(message)s"
51
+ logging.basicConfig(
52
+ level=LOG_LEVEL_MAP[env.log_level],
53
+ format=FORMAT,
54
+ datefmt="[%X]",
55
+ handlers=[RichHandler()],
56
+ force=True,
57
+ )
@@ -0,0 +1,33 @@
1
+ import dataclasses
2
+ import enum
3
+ import logging
4
+
5
+ import click
6
+
7
+
8
+ @enum.unique
9
+ class LogLevel(enum.Enum):
10
+ VERBOSE = "verbose"
11
+ DEBUG = "debug"
12
+ INFO = "info"
13
+ WARNING = "warning"
14
+ ERROR = "error"
15
+ FATAL = "fatal"
16
+
17
+
18
+ LOG_LEVEL_MAP = {
19
+ LogLevel.DEBUG: logging.DEBUG,
20
+ LogLevel.INFO: logging.INFO,
21
+ LogLevel.WARNING: logging.WARNING,
22
+ LogLevel.ERROR: logging.ERROR,
23
+ LogLevel.FATAL: logging.FATAL,
24
+ }
25
+
26
+
27
+ @dataclasses.dataclass
28
+ class Environment:
29
+ log_level: LogLevel = LogLevel.INFO
30
+ logger: logging.Logger = logging.getLogger("mr")
31
+
32
+
33
+ pass_env = click.make_pass_decorator(Environment, ensure=True)
@@ -0,0 +1,7 @@
1
+ from .artifacts import main # no qa
2
+ from .cli import cli
3
+
4
+ __ALL__ = [cli]
5
+
6
+ if __name__ == "__main__":
7
+ cli()
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: makerrepo-cli
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: click>=8.3.1
9
+ Requires-Dist: makerrepo>=0.2.0
10
+ Requires-Dist: ocp-vscode>=3.0.1
11
+ Requires-Dist: playwright>=1.58.0
12
+ Requires-Dist: rich>=14.3.2
13
+ Dynamic: license-file
14
+
15
+ # makerrepo-cli
16
+
17
+ Command line tool for MakerRepo
@@ -0,0 +1,18 @@
1
+ makerrepo_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ makerrepo_cli/cmds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ makerrepo_cli/cmds/aliase.py,sha256=OQ-E171TpBfRoX9D93DqBqXJkAWdtikprJsjK_UyXyA,863
4
+ makerrepo_cli/cmds/cli.py,sha256=iIGRynFXvebDIQ8_qGPc9tyMkUUvlIE9C_YtxABLhRg,1457
5
+ makerrepo_cli/cmds/environment.py,sha256=Bdg7bU7AoUBA_podtv7Vse2zxvq7QoFHEH184Fj7SfU,662
6
+ makerrepo_cli/cmds/main.py,sha256=mw9s-fWcQtobXUZdXJtbWVEvgx4-tdof3Cjb3o96ry0,120
7
+ makerrepo_cli/cmds/artifacts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ makerrepo_cli/cmds/artifacts/capture_image.py,sha256=Ioww0R6hmXGxpFFuZbXT-wLEdXtlBnDKXOVDWlM28kU,7172
9
+ makerrepo_cli/cmds/artifacts/cli.py,sha256=yutaVOJkT6NMMewYIcf-h9ytzKFGItXtHpcrrFABnro,202
10
+ makerrepo_cli/cmds/artifacts/main.py,sha256=rYO8M3ouhVC7n8RRYaC7CrZHQVsOGDUwMjzQK3AUQMo,5050
11
+ makerrepo_cli/cmds/artifacts/ocp_data_types.py,sha256=-YeAio5godIYcJPzoDIdu67MbqiJDMBkq8QcJJXQvMA,1175
12
+ makerrepo_cli/cmds/artifacts/utils.py,sha256=ZSLfm-S2uYk7Y7mq2fhK2Ki4OC2C6O0bNCZ5nDFLLEo,943
13
+ makerrepo_cli-0.1.0.dist-info/licenses/LICENSE,sha256=e1FWoucPP7MUG3AOBwb3R6PhN4N3KomjFLvu7CWar5Q,1072
14
+ makerrepo_cli-0.1.0.dist-info/METADATA,sha256=hM9A_2yXPgyOPBUr1CgrJHjdagGUWxWz9htm4-GwA6U,405
15
+ makerrepo_cli-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
16
+ makerrepo_cli-0.1.0.dist-info/entry_points.txt,sha256=Wcv5tkqI8NIiXZXJAZxTlVwy__9nT0ZfIktwNpPYjWE,95
17
+ makerrepo_cli-0.1.0.dist-info/top_level.txt,sha256=0sONY8MHgWTad63qvsjR1KXe7OOCwF66pDWm6iITrNM,14
18
+ makerrepo_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ makerrepo-cli = makerrepo_cli.cmds.main:cli
3
+ mr = makerrepo_cli.cmds.main:cli
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Launch Platform
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ makerrepo_cli